Skip to content

Commit c65a75c

Browse files
committed
made the first couple of commands
1 parent 6b815e4 commit c65a75c

7 files changed

Lines changed: 210 additions & 17 deletions

File tree

ideas/newproject.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,9 @@ More/better art pieces
7474

7575
daily coin flip?
7676

77-
sfx with button press/emote?
77+
sfx with button press/emote?
78+
79+
Way to urls to open on user end somehow
80+
81+
make it so that it reacts to certain emojis/reactions
82+

slack-bot/data/state.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"version": 1,
3+
"users": {
4+
"U0828FYS2UC": {
5+
"id": "U0828FYS2UC",
6+
"play": true,
7+
"see": false,
8+
"createdAt": "2025-10-05T19:54:33.726Z",
9+
"updatedAt": "2025-10-05T20:10:33.069Z",
10+
"stats": {
11+
"currentStreak": 0,
12+
"longestStreak": 0
13+
}
14+
}
15+
},
16+
"balances": {
17+
"U0828FYS2UC": {
18+
"userId": "U0828FYS2UC",
19+
"amount": 0,
20+
"updatedAt": "2025-10-05T19:54:33.726Z"
21+
}
22+
},
23+
"inventory": {},
24+
"transactions": [],
25+
"games": {},
26+
"announcements": {
27+
"dailyTopEnabled": false,
28+
"weeklyResetEnabled": false
29+
},
30+
"secretCoins": {
31+
"globalCap": 3,
32+
"awards": []
33+
},
34+
"idempotency": {},
35+
"featureFlags": {
36+
"optIn": true,
37+
"economy": true,
38+
"shop": true,
39+
"streaks": true,
40+
"games": true,
41+
"leaderboard": true,
42+
"secretCoins": true,
43+
"aiDebate": true
44+
},
45+
"createdAt": "2025-10-05T18:28:11.113Z",
46+
"updatedAt": "2025-10-05T20:10:33.069Z"
47+
}

slack-bot/src/app.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
import "dotenv/config";
2-
import { App } from "@slack/bolt";
31
import { CONFIG } from "./config.js";
42
import { setLogLevel, logger } from "./logger.js";
53
import { store } from "./storage/fileStore.js"
64
import { scheduleDailyEt, scheduleWeeklyMondayEt } from "./scheduler.js";
75
import { nowEt } from "./time.js";
6+
import { buildSlackApp, startSlackApp } from "./slackApp.js";
87

98
async function init() {
109
setLogLevel(CONFIG.logLevel);
1110
await store.init();
1211

13-
logger.info("Backend done", {
12+
logger.info("Inital functions", {
1413
dataDir: CONFIG.dataDir,
1514
stateFile: CONFIG.stateFile,
1615
tz: CONFIG.etTz,
1716
flags: store.get().featureFlags,
1817
});
18+
19+
const app = buildSlackApp();
20+
await startSlackApp(app);
1921

2022
// add payout and other stuff later
2123
scheduleDailyEt("daily-midnight-et", async () => {

slack-bot/src/idempotency.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { store } from "./storage/fileStore.js";
22
import { logger } from "./logger.js";
33

4-
/** Run an operation only once per key (until TTL expiration, if provided). */
4+
// run once per key - more if needed
55
export async function runOnce<T>(
66
key: string,
77
fn: () => Promise<T>,

slack-bot/src/ledger.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { randomUUID } from "crypto";
2-
import { State, Transaction, TransactionType } from "./types"; // Remove .js
3-
import { store } from "./storage/fileStore"; // Remove .js
4-
import { logger } from "./logger"; // Remove .js
5-
import { withLock } from "./locking"; // Remove .js
6-
import { runOnce } from "./idempotency"; // Remove .js
2+
import { State, Transaction, TransactionType } from "./types";
3+
import { store } from "./storage/fileStore";
4+
import { logger } from "./logger";
5+
import { withLock } from "./locking";
6+
import { runOnce } from "./idempotency";
77

88
function ensureUserBalance(state: State, userId: string) {
99
if (!state.balances[userId]) {
@@ -28,15 +28,14 @@ export async function addTransaction(
2828
if (opts?.idemKey) {
2929
const idem = await runOnce(key, async () => "ok");
3030
if (!idem.ok) {
31-
// Already applied
3231
const existing = store.get().transactions.find((t) => t.userId === userId && t.idemKey === opts!.idemKey);
3332
if (!existing) throw new Error("Idempotency claimed but transaction not found");
3433
return existing;
3534
}
3635
}
3736

38-
let created: Transaction | undefined; // Fix: Properly type the variable
39-
let finalBalance: number = 0; // Fix: Store the final balance
37+
let created: Transaction | undefined;
38+
let finalBalance: number = 0;
4039

4140
await store.update((s) => {
4241
ensureUserBalance(s, userId);
@@ -56,19 +55,19 @@ export async function addTransaction(
5655
s.transactions.push(tx);
5756
s.balances[userId] = { ...bal, amount: newBal, updatedAt: tx.createdAt };
5857
created = tx;
59-
finalBalance = newBal; // Store the balance
58+
finalBalance = newBal;
6059
});
6160

6261
if (!created) {
6362
throw new Error("Transaction creation failed");
6463
}
6564

66-
logger.info("Transaction", { userId, type, amount, balanceAfter: finalBalance }); // Fix: Use stored balance
65+
logger.info("Transaction", { userId, type, amount, balanceAfter: finalBalance });
6766
return created;
6867
});
6968
}
7069

71-
// Helpers for Phase 0 testing / dry runs
70+
// helpers
7271
export async function grantDaily(userId: string, amount: number, idemKey: string) {
7372
return addTransaction(userId, "grant", amount, { idemKey, refId: "daily_grant" });
7473
}

slack-bot/src/locking.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export async function withLock<T>(key: string, fn: () => Promise<T>): Promise<T>
1616
resolveNext!(null);
1717
throw err;
1818
} finally {
19-
// cleanup if chain finished
2019
if (queues.get(key) === next) queues.delete(key);
2120
}
2221
}

slack-bot/src/slackApp.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { App } from "@slack/bolt";
2+
import { store } from "./storage/fileStore";
3+
import { logger } from "./logger";
4+
import { StatementResultingChanges } from "node:sqlite";
5+
6+
function nowIso() { return new Date().toISOString(); }
7+
8+
function ensureUserInState(userId: string) {
9+
const s = store.get();
10+
if (!s.users[userId]) {
11+
s.users[userId] = {
12+
id: userId,
13+
play: false,
14+
see: false,
15+
createdAt: nowIso(),
16+
updatedAt: nowIso(),
17+
stats: { currentStreak: 0, longestStreak: 0 },
18+
};
19+
}
20+
if (!s.balances[userId]) {
21+
s.balances[userId] = { userId, amount: 0, updatedAt: nowIso() };
22+
}
23+
}
24+
25+
async function setPlay(userId: string, on: boolean) {
26+
await store.update((s) => {
27+
ensureUserInState(userId);
28+
s.users[userId].play = on;
29+
s.users[userId].updatedAt = nowIso();
30+
});
31+
}
32+
33+
async function setSee(userId: string, on: boolean) {
34+
await store.update((s) => {
35+
ensureUserInState(userId);
36+
s.users[userId].see = on;
37+
s.users[userId].updatedAt = nowIso();
38+
});
39+
}
40+
41+
export function buildSlackApp() {
42+
const app = new App({
43+
token: process.env.SLACK_BOT_TOKEN,
44+
socketMode: true,
45+
appToken: process.env.SLACK_APP_TOKEN,
46+
signingSecret: process.env.SLACK_SIGNING_SECRET,
47+
});
48+
49+
//add special emoji reaction
50+
app.event("reaction_added", async ({ event, client, logger: boltLogger }) => {
51+
const ev: any = event;
52+
try {
53+
if (ev.reaction !== "siege-coin") return; //change based on reaction name i forgor name
54+
const userId: string = ev.user;
55+
if (!userId || userId === "USLACKBOT") return;
56+
57+
await setPlay(userId, true);
58+
logger.info("Opt-in (PLAY) via :ssiege-coin: reaction", { userId });
59+
60+
const channelId: string | undefined = ev.itemn?.channel;
61+
if (channelId) {
62+
await client.chat.postEphemeral({
63+
channel: channelId,
64+
user: userId,
65+
text: "Welcome to the gamblers. You can now use commands! Toggle the activity feed with `/see on` or `/see off`. Opt out anytime with `/stopgambeling`.",
66+
});
67+
}
68+
} catch (e: any) {
69+
boltLogger.error(e);
70+
}
71+
});
72+
73+
app.command("/see", async ({ ack, respond, command }) => {
74+
await ack();
75+
const userId = command.user_id;
76+
const arg = (command.text || "").trim().toLowerCase();
77+
78+
if (arg !== "on" && arg !== "off") {
79+
await respond({
80+
response_type: "ephemeral",
81+
text: "Usage: `/see on` or `/see off` "
82+
});
83+
return;
84+
}
85+
86+
const on = arg === "on";
87+
await setSee(userId, on);
88+
89+
await respond({
90+
response_type: "ephemeral",
91+
text: on
92+
? "👀 Activity feed is now **ON**. You’ll see public game posts."
93+
: "🙈 Activity feed is now **OFF**. You won’t see public game posts."
94+
});
95+
});
96+
97+
app.command("/stopgambling", async ({ ack, respond, command }) => {
98+
await ack();
99+
const userId = command.user_id;
100+
await store.update((s) => {
101+
ensureUserInState(userId);
102+
s.users[userId].play = false;
103+
s.users[userId].see = false;
104+
s.users[userId].updatedAt = nowIso();
105+
});
106+
107+
await respond ({
108+
response_type: "ephemeral",
109+
text: "🛑 You’re opted out. The bot won’t react to you or show you game activity. Re-opt-in by reacting with :siege-coin: on the intro post."
110+
});
111+
});
112+
113+
//because socket is being stupid
114+
app.event("app_mention", async ({event, say, client}) => {
115+
const ev = event as any;
116+
const text = String((event as any).text || "").toLowerCase();
117+
118+
if (text.includes("hello")) {
119+
await client.chat.postMessage({
120+
channel: ev.channel,
121+
text: `hello <@${ev.user}>, start gambling RIGHT NOW!`,
122+
});
123+
return;
124+
}
125+
126+
if (text.includes("help")) {
127+
await client.chat.postMessage({
128+
channel: ev.channel,
129+
text: "Hi ! Opt in by reacting with :siege-coin:. Toggle feed with `/see on|off`. Opt out with `/stopgambling`.",
130+
thread_ts: ev.thread_ts || ev.ts,
131+
});
132+
}
133+
});
134+
return app;
135+
}
136+
137+
export async function startSlackApp(app: ReturnType<typeof buildSlackApp>) {
138+
const port = Number(process.env.PORT) || 3000;
139+
await app.start({ port });
140+
logger.info("Slack app runnin (SOCKET)", { port });
141+
}

0 commit comments

Comments
 (0)