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
+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}`;
}