Add the slopbot scaffold and discord.js dependency, unused

This commit is contained in:
Fisto
2026-09-16 07:59:51 +02:00
parent 148dd0a082
commit 92429f1e83
10 changed files with 315 additions and 6 deletions
+3
View File
@@ -47,6 +47,9 @@ before you stop.
- Triggers come from trusted users only: one-to-one DMs and direct mentions.
- Test with throwaway accounts and private test guilds only.
- Never commit `media-scrape/db/`, scrape databases, downloaded media, or the slopbot token.
- Owner interjections become `TODO.md` items unless marked "add this now". Add the item, then
continue the current task.
- No sudo. Record apt or system-level installs in `TODO.md`; the owner runs them.
- Live send tests: only DMs to user **zzib** (`177117296140091393`).
- Run `pnpm testTypes` and `pnpm lint` before you report done. Run `pnpm build` to compile.
+3 -1
View File
@@ -19,7 +19,9 @@ mobile draft at 390px.
bridge for group DMs; one self-contained SQLite + media dir per scrape under `media-scrape/db/`
(gitignored); links indexed by default, downloads optional, third-party embeds never downloaded;
30 GiB default limit with a 90% warning and 5% no-delete overrun, usage bar and clean button in
Settings → Bot; Media and Bot top-level tabs; MCP hooks designed for C7.
Settings → Bot; Media and Bot top-level tabs; MCP hooks designed for C7. The slopbot scaffold
(config loader, invite helper) and its discord.js dependency were added 2026-09-16 and stay
unused until C4.
Checkpoints, push after each:
- [x] C1 design docs, AGENTS/INDEX pointers, `.gitignore`
+6 -3
View File
@@ -29,7 +29,7 @@ libraries stands.
`attachments`, and `embeds` in both gateway events and REST history responses. Unverified apps
in under 100 servers can enable it in the portal.
3. Invite the bot with the `bot` and `applications.commands` scopes and the minimum permissions:
View Channels, Read Message History, Send Messages (for status replies), Embed Links.
View Channels and Read Message History. The bot never sends, so it needs no more.
4. Store the token in the daemon state file `slopbot.json` (mode 600) or `VESKSLOP_SLOPBOT_TOKEN`.
Never commit it. The action log redacts token-shaped strings as before.
@@ -41,7 +41,8 @@ channels.
| Setting | Default | Purpose |
| --- | --- | --- |
| `VESKSLOP_SLOPBOT_ENABLED` | `0` | Kill switch, off by default |
| `VESKSLOP_SLOPBOT_TOKEN` / `slopbot.json` | unset | Bot token |
| `VESKSLOP_SLOPBOT_TOKEN` | unset | Bot token (preferred) |
| `VESKSLOP_SLOPBOT_STATE` | unset | JSON file with `token` and `clientId` |
| `VESKSLOP_SLOPBOT_CLIENT_ID` | unset | For the invite URL helper |
## Code
@@ -68,4 +69,6 @@ Slopbot runs with the faster bot budget; the puppet backend uses the slower user
## Status
Design approved 2026-09-16. Implementation at checkpoint C4 in [CURRENT.md](../CURRENT.md).
Design approved 2026-09-16. Scaffold added 2026-09-16: `media-scrape/slopbot/` (config loader and
invite helper) and the discord.js dependency in `media-scrape/package.json`. Unused until
checkpoint C4 in [CURRENT.md](../CURRENT.md).
+2 -1
View File
@@ -18,7 +18,8 @@ media-scrape/
configurable with `VESKSLOP_MEDIA_DB_DIR` and never committed. See
[../docs/media.md](../docs/media.md).
The daemon imports this folder. discord.js is declared in `poc/package.json`, like the MCP SDK.
The daemon imports this folder. `media-scrape/` is its own pnpm workspace root (like `poc/`), so
`discord.js` is declared in `media-scrape/package.json` and resolves from `media-scrape/node_modules`.
## Docs
+10
View File
@@ -0,0 +1,10 @@
{
"name": "veskslop-media-scrape",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "Media archive module for Veskslop: scraper engine, store, Web UI routes, and slopbot.",
"dependencies": {
"discord.js": "^14.27.0"
}
}
+218
View File
@@ -0,0 +1,218 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
discord.js:
specifier: ^14.27.0
version: 14.27.0
packages:
'@discordjs/builders@1.14.1':
resolution: {integrity: sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ==}
engines: {node: '>=16.11.0'}
'@discordjs/collection@1.5.3':
resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==}
engines: {node: '>=16.11.0'}
'@discordjs/collection@2.1.1':
resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==}
engines: {node: '>=18'}
'@discordjs/formatters@0.6.2':
resolution: {integrity: sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==}
engines: {node: '>=16.11.0'}
'@discordjs/rest@2.6.3':
resolution: {integrity: sha512-wvOylxNYJkwKjctS/Mn5GP1w9r3/rzyH+ThD1JlAca6zEdlHs8QWBBUQJpU5Q+W6DoIj/Ljh1IPlZs7hTU+UAg==}
engines: {node: '>=18'}
'@discordjs/util@1.2.0':
resolution: {integrity: sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==}
engines: {node: '>=18'}
'@discordjs/ws@1.2.3':
resolution: {integrity: sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==}
engines: {node: '>=16.11.0'}
'@sapphire/async-queue@1.5.5':
resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
'@sapphire/shapeshift@4.0.0':
resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==}
engines: {node: '>=v16'}
'@sapphire/snowflake@3.5.5':
resolution: {integrity: sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
'@types/node@26.5.1':
resolution: {integrity: sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==}
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@vladfrangu/async_event_emitter@2.4.7':
resolution: {integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
discord-api-types@0.38.55:
resolution: {integrity: sha512-ytuaRTzdnHUCXJ6KjL9MrItQX0xKncBKEeYI1Bst4+ud47eejH3cG6gaesYakjpPcUhh68XYS2YwGq3mmujFsA==}
discord.js@14.27.0:
resolution: {integrity: sha512-qHbFlFG2N7y3LjPySYsL6A1+BnX6bkTVgo842EX0CqVPk/KTMwZkojPHEXKsQUpWZNyz5BISNHK1cPpQw0+m4A==}
engines: {node: '>=18'}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
lodash.snakecase@4.1.1:
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
lodash@4.18.1:
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
magic-bytes.js@1.13.1:
resolution: {integrity: sha512-x5sn4UX2k5gCWlcfmoFwG4TPie8+dctESyqOBdhB5p6MsgWXdBKGmt9nXPObj/JI50TTL928lc5Yt1WntMn1bw==}
ts-mixer@6.0.4:
resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
undici-types@8.9.0:
resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
undici@6.28.1:
resolution: {integrity: sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==}
engines: {node: '>=18.17'}
ws@8.21.3:
resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
snapshots:
'@discordjs/builders@1.14.1':
dependencies:
'@discordjs/formatters': 0.6.2
'@discordjs/util': 1.2.0
'@sapphire/shapeshift': 4.0.0
discord-api-types: 0.38.55
fast-deep-equal: 3.1.3
ts-mixer: 6.0.4
tslib: 2.8.1
'@discordjs/collection@1.5.3': {}
'@discordjs/collection@2.1.1': {}
'@discordjs/formatters@0.6.2':
dependencies:
discord-api-types: 0.38.55
'@discordjs/rest@2.6.3':
dependencies:
'@discordjs/collection': 2.1.1
'@discordjs/util': 1.2.0
'@sapphire/async-queue': 1.5.5
'@sapphire/snowflake': 3.5.5
'@vladfrangu/async_event_emitter': 2.4.7
discord-api-types: 0.38.55
magic-bytes.js: 1.13.1
tslib: 2.8.1
undici: 6.28.1
'@discordjs/util@1.2.0':
dependencies:
discord-api-types: 0.38.55
'@discordjs/ws@1.2.3':
dependencies:
'@discordjs/collection': 2.1.1
'@discordjs/rest': 2.6.3
'@discordjs/util': 1.2.0
'@sapphire/async-queue': 1.5.5
'@types/ws': 8.18.1
'@vladfrangu/async_event_emitter': 2.4.7
discord-api-types: 0.38.55
tslib: 2.8.1
ws: 8.21.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@sapphire/async-queue@1.5.5': {}
'@sapphire/shapeshift@4.0.0':
dependencies:
fast-deep-equal: 3.1.3
lodash: 4.18.1
'@sapphire/snowflake@3.5.5': {}
'@types/node@26.5.1':
dependencies:
undici-types: 8.9.0
'@types/ws@8.18.1':
dependencies:
'@types/node': 26.5.1
'@vladfrangu/async_event_emitter@2.4.7': {}
discord-api-types@0.38.55: {}
discord.js@14.27.0:
dependencies:
'@discordjs/builders': 1.14.1
'@discordjs/collection': 1.5.3
'@discordjs/formatters': 0.6.2
'@discordjs/rest': 2.6.3
'@discordjs/util': 1.2.0
'@discordjs/ws': 1.2.3
'@sapphire/snowflake': 3.5.5
discord-api-types: 0.38.55
fast-deep-equal: 3.1.3
lodash.snakecase: 4.1.1
magic-bytes.js: 1.13.1
tslib: 2.8.1
undici: 6.28.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
fast-deep-equal@3.1.3: {}
lodash.snakecase@4.1.1: {}
lodash@4.18.1: {}
magic-bytes.js@1.13.1: {}
ts-mixer@6.0.4: {}
tslib@2.8.1: {}
undici-types@8.9.0: {}
undici@6.28.1: {}
ws@8.21.3: {}
+2
View File
@@ -0,0 +1,2 @@
# Keep media-scrape its own workspace root, separate from the Vesktop app.
packages: []
+46
View File
@@ -0,0 +1,46 @@
/*
* Slopbot — the dedicated bot core for media scraping.
*
* Scaffold only: nothing imports this yet. The scrape backend lands at
* checkpoint C4 (see ../CURRENT.md). Setup, limits, and security: docs/slopbot.md.
*/
import { readFileSync } from "node:fs";
export interface SlopbotConfig {
/** Kill switch. Off unless VESKSLOP_SLOPBOT_ENABLED=1 and a token exists. */
enabled: boolean;
/** Bot token. Never logged, never committed. */
token: string | null;
/** Application ID, used to render the invite URL. */
clientId: string | null;
}
interface SlopbotStateFile {
token?: string;
clientId?: string;
}
function readStateFile(path: string): SlopbotStateFile {
try {
return JSON.parse(readFileSync(path, "utf8")) as SlopbotStateFile;
} catch {
return {};
}
}
/** Read the slopbot settings from the environment, then from the state file. */
export function loadSlopbotConfig(
env: NodeJS.ProcessEnv = process.env,
stateFilePath = env.VESKSLOP_SLOPBOT_STATE
): SlopbotConfig {
const state = stateFilePath ? readStateFile(stateFilePath) : {};
const token = env.VESKSLOP_SLOPBOT_TOKEN ?? state.token ?? null;
const clientId = env.VESKSLOP_SLOPBOT_CLIENT_ID ?? state.clientId ?? null;
return {
enabled: env.VESKSLOP_SLOPBOT_ENABLED === "1" && token !== null,
token,
clientId
};
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Slopbot entry point.
*
* Scaffold only: the daemon does not import this yet. Checkpoint C4 adds the
* scrape backend; docs/slopbot.md is the module contract.
*/
import type { Client } from "discord.js";
import { loadSlopbotConfig, type SlopbotConfig } from "./config.js";
/** Kept as a type so the discord.js dependency is type-checked before C4 uses it. */
export type SlopbotClient = Client;
export { loadSlopbotConfig, type SlopbotConfig };
/** View Channels + Read Message History. The bot reads only; it never sends. */
const PERMISSIONS = String((1 << 10) | (1 << 16));
/** The invite URL the Bot tab renders once a client ID is configured. */
export function slopbotInviteUrl(clientId: string): string {
return `https://discord.com/oauth2/authorize?client_id=${encodeURIComponent(
clientId
)}&scope=bot%20applications.commands&permissions=${PERMISSIONS}`;
}
+1 -1
View File
@@ -12,5 +12,5 @@
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src"]
"include": ["src", "../media-scrape/slopbot"]
}