@serverkgg/bridge 0.0.21 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/bot/botDeploy.test.ts +363 -0
- package/src/bot/botDeploy.ts +136 -0
- package/src/bot/botEnvironment.test.ts +189 -0
- package/src/bot/botEnvironment.ts +141 -0
- package/src/bot/botIdentity.test.ts +251 -0
- package/src/bot/botIdentity.ts +140 -0
- package/src/bot/botPanel.test.ts +216 -0
- package/src/bot/botPanel.ts +121 -0
- package/src/bot/botState.test.ts +45 -0
- package/src/bot/botState.ts +54 -0
- package/src/bot/botTerminal.test.ts +79 -0
- package/src/bot/botTerminal.ts +62 -0
- package/src/bot/botVariables.test.ts +193 -0
- package/src/bot/botVariables.ts +73 -0
- package/src/bot/detect/botFramework.ts +23 -0
- package/src/bot/detect/botFrameworks.ts +198 -0
- package/src/bot/detect/botInspectWarning.ts +15 -0
- package/src/bot/detect/botPackageManager.ts +6 -0
- package/src/bot/detect/botPlatform.ts +10 -0
- package/src/bot/detect/botRuntime.ts +15 -0
- package/src/bot/detect/botToken.test.ts +71 -0
- package/src/bot/detect/botToken.ts +126 -0
- package/src/bot/detect/detect.test.ts +543 -0
- package/src/bot/detect/detect.ts +842 -0
- package/src/bot/detect/detectResult.ts +40 -0
- package/src/bot/detect/detectTable.test.ts +72 -0
- package/src/bot/detect/detectTable.ts +166 -0
- package/src/bot/detect/envExample.test.ts +29 -0
- package/src/bot/detect/envExample.ts +48 -0
- package/src/bot/detect/envKey.ts +7 -0
- package/src/bot/detect/envScan.test.ts +39 -0
- package/src/bot/detect/envScan.ts +87 -0
- package/src/bot/detect/index.ts +17 -0
- package/src/bot/detect/inferBotPlatform.test.ts +60 -0
- package/src/bot/detect/inferBotPlatform.ts +28 -0
- package/src/bot/detect/keys.test.ts +28 -0
- package/src/bot/detect/keys.ts +64 -0
- package/src/bot/detect/sensitiveKey.test.ts +91 -0
- package/src/bot/detect/sensitiveKey.ts +44 -0
- package/src/bot/detect/serverkJson.test.ts +88 -0
- package/src/bot/detect/serverkJson.ts +174 -0
- package/src/bot/index.ts +8 -0
- package/src/cli/detectTable.test.ts +66 -0
- package/src/cli/detectTable.ts +43 -0
- package/src/cli/main.ts +12 -4
- package/src/cli/renderedFile.ts +35 -0
- package/src/cli/schema.ts +7 -26
- package/src/cli/validate.test.ts +210 -2
- package/src/cli/validate.ts +71 -7
- package/src/context/bridgeContext.ts +11 -0
- package/src/core/bridgePort.test.ts +45 -0
- package/src/core/bridgePort.ts +26 -0
- package/src/guides/compile.ts +2 -2
- package/src/manifest/index.ts +1 -0
- package/src/manifest/kind.ts +5 -0
- package/src/manifest/manifest.test.ts +630 -3
- package/src/manifest/manifest.ts +391 -87
- package/src/manifest/runtime.ts +2 -2
- package/src/manifest/source.ts +4 -4
- package/src/modules/bridgeLifecycleSubject.ts +8 -0
- package/src/modules/bridgeModule.ts +6 -0
- package/src/modules/bridgeStopSignal.ts +13 -0
- package/src/modules/index.ts +2 -0
- package/src/panel/bridgeDetail.ts +2 -0
- package/src/protocol/bridgeConfig.ts +2 -0
- package/src/runtime/bridgeConfigReader.test.ts +80 -0
- package/src/runtime/bridgeConfigReader.ts +20 -0
- package/src/runtime/bridgeContext.test.ts +241 -0
- package/src/runtime/bridgeContext.ts +35 -9
- package/src/runtime/bridgeDriver.test.ts +132 -1
- package/src/runtime/bridgeDriver.ts +54 -1
- package/src/runtime/bridgeFiles.test.ts +62 -0
- package/src/runtime/bridgeFiles.ts +12 -2
- package/src/runtime/bridgeLiveConfig.ts +18 -0
- package/src/runtime/bridgeRedaction.test.ts +410 -0
- package/src/runtime/bridgeRedaction.ts +214 -0
- package/src/runtime/bridgeRegistry.test.ts +3 -0
- package/src/runtime/bridgeRegistry.ts +15 -0
- package/src/runtime/bridgeServer.test.ts +130 -0
- package/src/runtime/bridgeServer.ts +24 -7
- package/src/runtime/bridgeSetupHandlers.test.ts +88 -0
- package/src/runtime/bridgeSetupHandlers.ts +16 -6
- package/src/runtime/bridgeSupervisor.test.ts +670 -2
- package/src/runtime/bridgeSupervisor.ts +219 -67
- package/src/runtime/serve.ts +47 -13
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_REPO_BRANCH,
|
|
4
|
+
DEPLOYMENT_ID_PATTERN,
|
|
5
|
+
isRootDirectory,
|
|
6
|
+
REPO_BRANCH_PATTERN,
|
|
7
|
+
REPO_REF_PATTERN,
|
|
8
|
+
REPO_URL_PATTERN,
|
|
9
|
+
ROOT_DIRECTORY_LENGTH_LIMIT,
|
|
10
|
+
readBotConfig,
|
|
11
|
+
SNAPSHOT_ID_PATTERN,
|
|
12
|
+
} from "./botVariables";
|
|
13
|
+
import {
|
|
14
|
+
BOT_PLATFORM,
|
|
15
|
+
BotPlatform,
|
|
16
|
+
DEPLOYMENT_ID,
|
|
17
|
+
GIT_TOKEN,
|
|
18
|
+
INSTALL_COMMAND,
|
|
19
|
+
REPO_BRANCH,
|
|
20
|
+
REPO_REF,
|
|
21
|
+
REPO_URL,
|
|
22
|
+
ROOT_DIRECTORY,
|
|
23
|
+
SNAPSHOT_ID,
|
|
24
|
+
START_COMMAND,
|
|
25
|
+
} from "./detect";
|
|
26
|
+
|
|
27
|
+
const SHA = "0123456789abcdef0123456789abcdef01234567";
|
|
28
|
+
|
|
29
|
+
const contextWith = (variables: Record<string, string>, secrets: Record<string, string> = {}) => {
|
|
30
|
+
return {
|
|
31
|
+
variable: (key: string) => variables[key] ?? null,
|
|
32
|
+
secret: (key: string) => secrets[key] ?? null,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe("readBotConfig", () => {
|
|
37
|
+
test("reads the platform-owned keys, trims them and takes the git token from the secrets", () => {
|
|
38
|
+
expect(
|
|
39
|
+
readBotConfig(
|
|
40
|
+
contextWith(
|
|
41
|
+
{
|
|
42
|
+
[BOT_PLATFORM]: BotPlatform.Telegram,
|
|
43
|
+
[REPO_URL]: " https://github.com/you/bot ",
|
|
44
|
+
[REPO_REF]: SHA,
|
|
45
|
+
[REPO_BRANCH]: "dev",
|
|
46
|
+
[START_COMMAND]: "node src/bot.js",
|
|
47
|
+
[INSTALL_COMMAND]: "npm install --legacy-peer-deps",
|
|
48
|
+
[ROOT_DIRECTORY]: " apps/bot ",
|
|
49
|
+
[DEPLOYMENT_ID]: "dep_a1b2c3d4",
|
|
50
|
+
[SNAPSHOT_ID]: "snap_1",
|
|
51
|
+
[GIT_TOKEN]: "ignored-in-variables",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
[GIT_TOKEN]: "ghs_installation",
|
|
55
|
+
},
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
).toEqual({
|
|
59
|
+
platform: BotPlatform.Telegram,
|
|
60
|
+
repo: "https://github.com/you/bot",
|
|
61
|
+
ref: SHA,
|
|
62
|
+
branch: "dev",
|
|
63
|
+
startCommand: "node src/bot.js",
|
|
64
|
+
installCommand: "npm install --legacy-peer-deps",
|
|
65
|
+
rootDirectory: "apps/bot",
|
|
66
|
+
deployment: "dep_a1b2c3d4",
|
|
67
|
+
snapshot: "snap_1",
|
|
68
|
+
gitToken: "ghs_installation",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("answers nothing for empty values, main for the branch and null for an unknown platform", () => {
|
|
73
|
+
expect(
|
|
74
|
+
readBotConfig(
|
|
75
|
+
contextWith({
|
|
76
|
+
[BOT_PLATFORM]: "slack",
|
|
77
|
+
[REPO_URL]: " ",
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
).toEqual({
|
|
81
|
+
platform: null,
|
|
82
|
+
repo: null,
|
|
83
|
+
ref: null,
|
|
84
|
+
branch: DEFAULT_REPO_BRANCH,
|
|
85
|
+
startCommand: null,
|
|
86
|
+
installCommand: null,
|
|
87
|
+
rootDirectory: null,
|
|
88
|
+
deployment: null,
|
|
89
|
+
snapshot: null,
|
|
90
|
+
gitToken: null,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("the patterns", () => {
|
|
96
|
+
test("pin a ref only to a full sha-1 or sha-256", () => {
|
|
97
|
+
expect(REPO_REF_PATTERN.test(SHA)).toBe(true);
|
|
98
|
+
expect(REPO_REF_PATTERN.test("a".repeat(64))).toBe(true);
|
|
99
|
+
expect(REPO_REF_PATTERN.test(SHA.slice(0, 12))).toBe(false);
|
|
100
|
+
expect(REPO_REF_PATTERN.test(SHA.toUpperCase())).toBe(false);
|
|
101
|
+
expect(REPO_REF_PATTERN.test("main")).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("keep deployment and snapshot ids to url-safe words", () => {
|
|
105
|
+
for (const pattern of [
|
|
106
|
+
DEPLOYMENT_ID_PATTERN,
|
|
107
|
+
SNAPSHOT_ID_PATTERN,
|
|
108
|
+
]) {
|
|
109
|
+
expect(pattern.test("dep_a1b2c3d4")).toBe(true);
|
|
110
|
+
expect(pattern.test("0f8e-uuid-like")).toBe(true);
|
|
111
|
+
expect(pattern.test("a".repeat(65))).toBe(false);
|
|
112
|
+
expect(pattern.test("../escape")).toBe(false);
|
|
113
|
+
expect(pattern.test("")).toBe(false);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("take a root directory only as a plain relative folder path, the shape the api enforces", () => {
|
|
118
|
+
for (const root of [
|
|
119
|
+
"bot",
|
|
120
|
+
"apps/bot",
|
|
121
|
+
"packages/my-bot_v2",
|
|
122
|
+
"src.bot/v1.2",
|
|
123
|
+
"a".repeat(ROOT_DIRECTORY_LENGTH_LIMIT),
|
|
124
|
+
]) {
|
|
125
|
+
expect(isRootDirectory(root)).toBe(true);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const root of [
|
|
129
|
+
"",
|
|
130
|
+
"/bot",
|
|
131
|
+
"./bot",
|
|
132
|
+
".bot",
|
|
133
|
+
"..",
|
|
134
|
+
"apps/../bot",
|
|
135
|
+
"apps/./bot",
|
|
136
|
+
"apps/..",
|
|
137
|
+
"apps//bot",
|
|
138
|
+
"apps/bot/",
|
|
139
|
+
"apps\\bot",
|
|
140
|
+
"apps\u0000bot",
|
|
141
|
+
"apps bot",
|
|
142
|
+
"a".repeat(ROOT_DIRECTORY_LENGTH_LIMIT + 1),
|
|
143
|
+
]) {
|
|
144
|
+
expect(isRootDirectory(root)).toBe(false);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("accept https repositories and git branch names", () => {
|
|
149
|
+
expect(REPO_URL_PATTERN.test("https://github.com/you/bot.git")).toBe(true);
|
|
150
|
+
expect(REPO_URL_PATTERN.test("https://user:pass@github.com/you/bot")).toBe(false);
|
|
151
|
+
expect(REPO_URL_PATTERN.test("git@github.com:you/bot.git")).toBe(false);
|
|
152
|
+
expect(REPO_BRANCH_PATTERN.test("feature/new-bot")).toBe(true);
|
|
153
|
+
expect(REPO_BRANCH_PATTERN.test("-rf")).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("accept only branch names git check-ref-format accepts", () => {
|
|
157
|
+
for (const branch of [
|
|
158
|
+
"main",
|
|
159
|
+
"release-1.2",
|
|
160
|
+
"a.locked",
|
|
161
|
+
"deps/HEAD",
|
|
162
|
+
"x".repeat(128),
|
|
163
|
+
]) {
|
|
164
|
+
expect(REPO_BRANCH_PATTERN.test(branch), branch).toBe(true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const branch of [
|
|
168
|
+
"HEAD",
|
|
169
|
+
"a..b",
|
|
170
|
+
"a//b",
|
|
171
|
+
"/main",
|
|
172
|
+
"main/",
|
|
173
|
+
"main.",
|
|
174
|
+
"a/.hidden",
|
|
175
|
+
".hidden",
|
|
176
|
+
"main.lock",
|
|
177
|
+
"a.lock/b",
|
|
178
|
+
"a@{1}",
|
|
179
|
+
"a\\b",
|
|
180
|
+
"a~1",
|
|
181
|
+
"a^2",
|
|
182
|
+
"a:b",
|
|
183
|
+
"a?b",
|
|
184
|
+
"a*b",
|
|
185
|
+
"a[b",
|
|
186
|
+
"with space",
|
|
187
|
+
"tab\there",
|
|
188
|
+
"x".repeat(129),
|
|
189
|
+
]) {
|
|
190
|
+
expect(REPO_BRANCH_PATTERN.test(branch), JSON.stringify(branch)).toBe(false);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { BridgeContext } from "../context";
|
|
2
|
+
import {
|
|
3
|
+
BOT_PLATFORM,
|
|
4
|
+
type BotPlatform,
|
|
5
|
+
DEPLOYMENT_ID,
|
|
6
|
+
GIT_TOKEN,
|
|
7
|
+
INSTALL_COMMAND,
|
|
8
|
+
isBotPlatform,
|
|
9
|
+
REPO_BRANCH,
|
|
10
|
+
REPO_REF,
|
|
11
|
+
REPO_URL,
|
|
12
|
+
ROOT_DIRECTORY,
|
|
13
|
+
SNAPSHOT_ID,
|
|
14
|
+
START_COMMAND,
|
|
15
|
+
} from "./detect";
|
|
16
|
+
|
|
17
|
+
export const REPO_URL_PATTERN = /^https:\/\/[^\s@/]+\/\S+$/;
|
|
18
|
+
|
|
19
|
+
export const REPO_BRANCH_PATTERN =
|
|
20
|
+
/^(?!HEAD$)(?!.*(?:\.\.|\/\/|\/\.|\.lock(?:\/|$)|[/.]$))[A-Za-z0-9_][A-Za-z0-9._/-]{0,127}$/;
|
|
21
|
+
|
|
22
|
+
export const REPO_REF_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
23
|
+
|
|
24
|
+
export const DEPLOYMENT_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
25
|
+
|
|
26
|
+
export const SNAPSHOT_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
27
|
+
|
|
28
|
+
export const ROOT_DIRECTORY_PATTERN =
|
|
29
|
+
/^(?![./])(?!.*(?:^|\/)\.{1,2}(?:\/|$))(?!.*\/\/)[A-Za-z0-9._\-/]*[A-Za-z0-9._-]$/;
|
|
30
|
+
|
|
31
|
+
export const ROOT_DIRECTORY_LENGTH_LIMIT = 255;
|
|
32
|
+
|
|
33
|
+
export const DEFAULT_REPO_BRANCH = "main";
|
|
34
|
+
|
|
35
|
+
export const isRootDirectory = (value: string) => {
|
|
36
|
+
return value.length <= ROOT_DIRECTORY_LENGTH_LIMIT && ROOT_DIRECTORY_PATTERN.test(value);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export interface BotConfig {
|
|
40
|
+
platform: BotPlatform | null;
|
|
41
|
+
repo: string | null;
|
|
42
|
+
ref: string | null;
|
|
43
|
+
branch: string;
|
|
44
|
+
startCommand: string | null;
|
|
45
|
+
installCommand: string | null;
|
|
46
|
+
rootDirectory: string | null;
|
|
47
|
+
deployment: string | null;
|
|
48
|
+
snapshot: string | null;
|
|
49
|
+
gitToken: string | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const present = (value: string | null) => {
|
|
53
|
+
const trimmed = value?.trim() ?? "";
|
|
54
|
+
|
|
55
|
+
return trimmed.length === 0 ? null : trimmed;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const readBotConfig = (context: Pick<BridgeContext, "variable" | "secret">): BotConfig => {
|
|
59
|
+
const platform = present(context.variable(BOT_PLATFORM));
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
platform: isBotPlatform(platform) ? platform : null,
|
|
63
|
+
repo: present(context.variable(REPO_URL)),
|
|
64
|
+
ref: present(context.variable(REPO_REF)),
|
|
65
|
+
branch: present(context.variable(REPO_BRANCH)) ?? DEFAULT_REPO_BRANCH,
|
|
66
|
+
startCommand: present(context.variable(START_COMMAND)),
|
|
67
|
+
installCommand: present(context.variable(INSTALL_COMMAND)),
|
|
68
|
+
rootDirectory: present(context.variable(ROOT_DIRECTORY)),
|
|
69
|
+
deployment: present(context.variable(DEPLOYMENT_ID)),
|
|
70
|
+
snapshot: present(context.variable(SNAPSHOT_ID)),
|
|
71
|
+
gitToken: present(context.secret(GIT_TOKEN)),
|
|
72
|
+
};
|
|
73
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export enum BotFramework {
|
|
2
|
+
DiscordJs = "discord-js",
|
|
3
|
+
Eris = "eris",
|
|
4
|
+
OceanicJs = "oceanic-js",
|
|
5
|
+
Grammy = "grammy",
|
|
6
|
+
Telegraf = "telegraf",
|
|
7
|
+
NodeTelegramBotApi = "node-telegram-bot-api",
|
|
8
|
+
DiscordPy = "discord-py",
|
|
9
|
+
PyCord = "py-cord",
|
|
10
|
+
Nextcord = "nextcord",
|
|
11
|
+
Disnake = "disnake",
|
|
12
|
+
Hikari = "hikari",
|
|
13
|
+
PythonTelegramBot = "python-telegram-bot",
|
|
14
|
+
Aiogram = "aiogram",
|
|
15
|
+
Pyrogram = "pyrogram",
|
|
16
|
+
Telethon = "telethon",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const FRAMEWORKS: ReadonlySet<string> = new Set(Object.values(BotFramework));
|
|
20
|
+
|
|
21
|
+
export const isBotFramework = (value: string | null | undefined): value is BotFramework => {
|
|
22
|
+
return typeof value === "string" && FRAMEWORKS.has(value);
|
|
23
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { BotFramework } from "./botFramework";
|
|
2
|
+
import { BotPlatform } from "./botPlatform";
|
|
3
|
+
import { BotRuntime } from "./botRuntime";
|
|
4
|
+
|
|
5
|
+
export interface BotFrameworkSpec {
|
|
6
|
+
framework: BotFramework;
|
|
7
|
+
name: string;
|
|
8
|
+
runtime: BotRuntime;
|
|
9
|
+
platform: BotPlatform;
|
|
10
|
+
packages: readonly string[];
|
|
11
|
+
modules: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const BOT_FRAMEWORKS: readonly BotFrameworkSpec[] = [
|
|
15
|
+
{
|
|
16
|
+
framework: BotFramework.DiscordJs,
|
|
17
|
+
name: "discord.js",
|
|
18
|
+
runtime: BotRuntime.Node,
|
|
19
|
+
platform: BotPlatform.Discord,
|
|
20
|
+
packages: [
|
|
21
|
+
"discord.js",
|
|
22
|
+
],
|
|
23
|
+
modules: [
|
|
24
|
+
"discord.js",
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
framework: BotFramework.Eris,
|
|
29
|
+
name: "Eris",
|
|
30
|
+
runtime: BotRuntime.Node,
|
|
31
|
+
platform: BotPlatform.Discord,
|
|
32
|
+
packages: [
|
|
33
|
+
"eris",
|
|
34
|
+
],
|
|
35
|
+
modules: [
|
|
36
|
+
"eris",
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
framework: BotFramework.OceanicJs,
|
|
41
|
+
name: "Oceanic.js",
|
|
42
|
+
runtime: BotRuntime.Node,
|
|
43
|
+
platform: BotPlatform.Discord,
|
|
44
|
+
packages: [
|
|
45
|
+
"oceanic.js",
|
|
46
|
+
],
|
|
47
|
+
modules: [
|
|
48
|
+
"oceanic.js",
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
framework: BotFramework.Grammy,
|
|
53
|
+
name: "grammY",
|
|
54
|
+
runtime: BotRuntime.Node,
|
|
55
|
+
platform: BotPlatform.Telegram,
|
|
56
|
+
packages: [
|
|
57
|
+
"grammy",
|
|
58
|
+
],
|
|
59
|
+
modules: [
|
|
60
|
+
"grammy",
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
framework: BotFramework.Telegraf,
|
|
65
|
+
name: "Telegraf",
|
|
66
|
+
runtime: BotRuntime.Node,
|
|
67
|
+
platform: BotPlatform.Telegram,
|
|
68
|
+
packages: [
|
|
69
|
+
"telegraf",
|
|
70
|
+
],
|
|
71
|
+
modules: [
|
|
72
|
+
"telegraf",
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
framework: BotFramework.NodeTelegramBotApi,
|
|
77
|
+
name: "node-telegram-bot-api",
|
|
78
|
+
runtime: BotRuntime.Node,
|
|
79
|
+
platform: BotPlatform.Telegram,
|
|
80
|
+
packages: [
|
|
81
|
+
"node-telegram-bot-api",
|
|
82
|
+
],
|
|
83
|
+
modules: [
|
|
84
|
+
"node-telegram-bot-api",
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
framework: BotFramework.DiscordPy,
|
|
89
|
+
name: "discord.py",
|
|
90
|
+
runtime: BotRuntime.Python,
|
|
91
|
+
platform: BotPlatform.Discord,
|
|
92
|
+
packages: [
|
|
93
|
+
"discord.py",
|
|
94
|
+
"discord",
|
|
95
|
+
],
|
|
96
|
+
modules: [
|
|
97
|
+
"discord",
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
framework: BotFramework.PyCord,
|
|
102
|
+
name: "Pycord",
|
|
103
|
+
runtime: BotRuntime.Python,
|
|
104
|
+
platform: BotPlatform.Discord,
|
|
105
|
+
packages: [
|
|
106
|
+
"py-cord",
|
|
107
|
+
],
|
|
108
|
+
modules: [],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
framework: BotFramework.Nextcord,
|
|
112
|
+
name: "nextcord",
|
|
113
|
+
runtime: BotRuntime.Python,
|
|
114
|
+
platform: BotPlatform.Discord,
|
|
115
|
+
packages: [
|
|
116
|
+
"nextcord",
|
|
117
|
+
],
|
|
118
|
+
modules: [
|
|
119
|
+
"nextcord",
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
framework: BotFramework.Disnake,
|
|
124
|
+
name: "disnake",
|
|
125
|
+
runtime: BotRuntime.Python,
|
|
126
|
+
platform: BotPlatform.Discord,
|
|
127
|
+
packages: [
|
|
128
|
+
"disnake",
|
|
129
|
+
],
|
|
130
|
+
modules: [
|
|
131
|
+
"disnake",
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
framework: BotFramework.Hikari,
|
|
136
|
+
name: "hikari",
|
|
137
|
+
runtime: BotRuntime.Python,
|
|
138
|
+
platform: BotPlatform.Discord,
|
|
139
|
+
packages: [
|
|
140
|
+
"hikari",
|
|
141
|
+
],
|
|
142
|
+
modules: [
|
|
143
|
+
"hikari",
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
framework: BotFramework.PythonTelegramBot,
|
|
148
|
+
name: "python-telegram-bot",
|
|
149
|
+
runtime: BotRuntime.Python,
|
|
150
|
+
platform: BotPlatform.Telegram,
|
|
151
|
+
packages: [
|
|
152
|
+
"python-telegram-bot",
|
|
153
|
+
],
|
|
154
|
+
modules: [
|
|
155
|
+
"telegram",
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
framework: BotFramework.Aiogram,
|
|
160
|
+
name: "aiogram",
|
|
161
|
+
runtime: BotRuntime.Python,
|
|
162
|
+
platform: BotPlatform.Telegram,
|
|
163
|
+
packages: [
|
|
164
|
+
"aiogram",
|
|
165
|
+
],
|
|
166
|
+
modules: [
|
|
167
|
+
"aiogram",
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
framework: BotFramework.Pyrogram,
|
|
172
|
+
name: "Pyrogram",
|
|
173
|
+
runtime: BotRuntime.Python,
|
|
174
|
+
platform: BotPlatform.Telegram,
|
|
175
|
+
packages: [
|
|
176
|
+
"pyrogram",
|
|
177
|
+
],
|
|
178
|
+
modules: [
|
|
179
|
+
"pyrogram",
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
framework: BotFramework.Telethon,
|
|
184
|
+
name: "Telethon",
|
|
185
|
+
runtime: BotRuntime.Python,
|
|
186
|
+
platform: BotPlatform.Telegram,
|
|
187
|
+
packages: [
|
|
188
|
+
"telethon",
|
|
189
|
+
],
|
|
190
|
+
modules: [
|
|
191
|
+
"telethon",
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
export const botFrameworkSpec = (framework: BotFramework): BotFrameworkSpec | null => {
|
|
197
|
+
return BOT_FRAMEWORKS.find((spec) => spec.framework === framework) ?? null;
|
|
198
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export enum BotInspectWarning {
|
|
2
|
+
NoLockfile = "no-lockfile",
|
|
3
|
+
NoStart = "no-start",
|
|
4
|
+
NoManifest = "no-manifest",
|
|
5
|
+
TypescriptEntry = "typescript-entry",
|
|
6
|
+
EnvCommitted = "env-committed",
|
|
7
|
+
NodeModulesCommitted = "node-modules-committed",
|
|
8
|
+
VenvCommitted = "venv-committed",
|
|
9
|
+
UnpinnedRequirements = "unpinned-requirements",
|
|
10
|
+
TwoRuntimes = "two-runtimes",
|
|
11
|
+
TwoPlatforms = "two-platforms",
|
|
12
|
+
ManifestDisagrees = "manifest-disagrees",
|
|
13
|
+
ManifestInvalid = "manifest-invalid",
|
|
14
|
+
ReservedEnvKey = "reserved-env-key",
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export enum BotPlatform {
|
|
2
|
+
Discord = "discord",
|
|
3
|
+
Telegram = "telegram",
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const PLATFORMS: ReadonlySet<string> = new Set(Object.values(BotPlatform));
|
|
7
|
+
|
|
8
|
+
export const isBotPlatform = (value: string | null | undefined): value is BotPlatform => {
|
|
9
|
+
return typeof value === "string" && PLATFORMS.has(value);
|
|
10
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export enum BotRuntime {
|
|
2
|
+
Node = "node",
|
|
3
|
+
Python = "python",
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const BOT_RUNTIME_PACKAGES: Readonly<Record<BotRuntime, string>> = {
|
|
7
|
+
[BotRuntime.Node]: "bot-node",
|
|
8
|
+
[BotRuntime.Python]: "bot-python",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const RUNTIMES: ReadonlySet<string> = new Set(Object.values(BotRuntime));
|
|
12
|
+
|
|
13
|
+
export const isBotRuntime = (value: string | null | undefined): value is BotRuntime => {
|
|
14
|
+
return typeof value === "string" && RUNTIMES.has(value);
|
|
15
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { BotPlatform } from "./botPlatform";
|
|
3
|
+
import { discordApplicationId, findBotToken, platformOfKey, platformOfToken, telegramBotId } from "./botToken";
|
|
4
|
+
|
|
5
|
+
const DISCORD_ID = "1136470478027245683";
|
|
6
|
+
|
|
7
|
+
const DISCORD_TOKEN = `${btoa(DISCORD_ID).replaceAll("=", "")}.GxYz12.${"a".repeat(38)}`;
|
|
8
|
+
|
|
9
|
+
const TELEGRAM_TOKEN = "7123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw";
|
|
10
|
+
|
|
11
|
+
describe("token shapes", () => {
|
|
12
|
+
test("read the ids hidden in each platform's token", () => {
|
|
13
|
+
expect(telegramBotId(TELEGRAM_TOKEN)).toBe("7123456789");
|
|
14
|
+
expect(discordApplicationId(` ${DISCORD_TOKEN} `)).toBe(DISCORD_ID);
|
|
15
|
+
expect(platformOfToken(TELEGRAM_TOKEN)).toBe(BotPlatform.Telegram);
|
|
16
|
+
expect(platformOfToken(DISCORD_TOKEN)).toBe(BotPlatform.Discord);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("do not mistake a JSON web token or a random key for a bot token", () => {
|
|
20
|
+
const jwt = `${btoa('{"alg":"HS256"}').replaceAll("=", "")}.eyJzdWIiOiIxIn0.${"b".repeat(43)}`;
|
|
21
|
+
|
|
22
|
+
expect(platformOfToken(jwt)).toBeNull();
|
|
23
|
+
expect(platformOfToken("sk-live-0123456789abcdef")).toBeNull();
|
|
24
|
+
expect(platformOfToken("")).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("read the platform a key name points at", () => {
|
|
28
|
+
expect(platformOfKey("DISCORD_TOKEN")).toBe(BotPlatform.Discord);
|
|
29
|
+
expect(platformOfKey("my_telegram_chat")).toBe(BotPlatform.Telegram);
|
|
30
|
+
expect(platformOfKey("TOKEN")).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("findBotToken", () => {
|
|
35
|
+
test("prefers the tutorial names, then any value shaped like the platform's token", () => {
|
|
36
|
+
expect(
|
|
37
|
+
findBotToken(BotPlatform.Discord, {
|
|
38
|
+
variables: {},
|
|
39
|
+
secrets: {
|
|
40
|
+
OTHER: DISCORD_TOKEN,
|
|
41
|
+
DISCORD_TOKEN,
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
).toEqual({
|
|
45
|
+
key: "DISCORD_TOKEN",
|
|
46
|
+
value: DISCORD_TOKEN,
|
|
47
|
+
});
|
|
48
|
+
expect(
|
|
49
|
+
findBotToken(BotPlatform.Telegram, {
|
|
50
|
+
variables: {
|
|
51
|
+
TG: TELEGRAM_TOKEN,
|
|
52
|
+
},
|
|
53
|
+
secrets: {},
|
|
54
|
+
}),
|
|
55
|
+
).toEqual({
|
|
56
|
+
key: "TG",
|
|
57
|
+
value: TELEGRAM_TOKEN,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("ignores a named key whose value is not the platform's token", () => {
|
|
62
|
+
expect(
|
|
63
|
+
findBotToken(BotPlatform.Telegram, {
|
|
64
|
+
variables: {},
|
|
65
|
+
secrets: {
|
|
66
|
+
TELEGRAM_TOKEN: "not-a-token",
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
});
|