Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions SHA256SUMS
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ ecd8294e24a1cd55cc1aa0b3c5d6cde68c956524cfb1d7707766b4f6287b5e3e activity/index
2c22d3a5e0aff57971932e24cbdf4dc93c12f589b060203cc20417fd97a75142 activity/legal/legal.css
6c8b74d4d57bff38a393ab038418c8f410627dc66721bc416905289110f151ac activity/legal/privacy.html
297c926eb462285e52e196af2d23f2ac70cb280324bfd30aeff74836e02f159c activity/legal/terms.html
92481a623badff677f19c6eb71a34038931ce059a1b5f6c4e652cb745e590b35 activity/src/connection.ts
d2c03ccd185041e743b25a9c000bececdd2fc6464ed66b78a4d9e390070dbf3d activity/src/connection.ts
8e1c4a409e52f6677d2fb51c3afabae5de8de05be606b83a44271c6d31f35992 activity/src/main.ts
8010da4164c86394863cb645eaf4567f066822114c40edf1d2762c1a5da3806a activity/src/onboarding.ts
66988416b7ab216ad9d3947ed528bfe9f2e45f74943971d506a686ff0b044508 activity/style.css
Expand Down Expand Up @@ -397,12 +397,12 @@ a44e797125685b375a99f8ea8311972906255b05cdc4df91ea9beb0c8860e185 scripts/verify
b5e028d6a57cf4c6458f70c8411d2b95f561b48b212bf88cb8d04e3ea3efeeee server/content-evaluate-cli.ts
c7678b37fef2837eece5de6da59b919a9d00bbec87c44c677930b5ad717ebd04 server/content.ts
c3a5c6c6c01a10e2682f827d04f43fcb72f50fb8f3eb70690f28d78e130fd7b0 server/database.ts
e08839ca4a25c1087e39f2d98c5bc97ca9c2f4366cf27f5404088c5e00471d6c server/discord.ts
bc116f5d23abe9b47bf28b328f360c5176b50eb4e8907783ba2f841f531db0cc server/discord.ts
ba6fb59bda1bf219d1e09d787a52437bf4a0ae6bfabfbda2bcfb37bced886adc server/durable.ts
89e116491a077a347c18b0b0c6a2801f867f0005c224ab5ed6e2dd3d783c047a server/evaluate-cli.ts
6e00b4f2c76c2b68165105b9ca9a00dbc74183043d15ba4d5ffb82967b84130f server/gateway.ts
f72b36e636d98f35f21d9e185aaf5ca7376ef20de2769e5c4a8a107022bcf832 server/historical.ts
78955eb8836d39e72cc5be4d95938bd4805ef6913f8c697cf0feb359f0f09b5a server/http.ts
af65a397602bd3b3907c59a542e44e8047010766016471696e926b839e29b48b server/http.ts
98a99e3407ace650bc8b1c9140d31548c378e0b2e1069e43dbed6325f5acf26b server/images.ts
6af6aa0e5917a1ec062d2076c6ae60a4cb94af546266e9fd4b746502a7a743a3 server/jev.ts
b556bb54b61153d824e83a5a931c7031974736e065dc63633e259090c3e7eb0d server/leadership.ts
Expand Down Expand Up @@ -440,12 +440,12 @@ f379be142cd234fb82c6b55ebb66e5ea94c184cb972cda576606d8dd872187ff starters/combi
d1611476abf0bc8ab0e1d4ccfea0abaa232183aa144390d56a8cf00f7468d984 starters/custom-template.typecheck.json
79ba12d8b758c4bcabc7c79b357cebc584442d3f2a368c2142576ef6a95975dd starters/image-scam-analysis.typecheck.json
20868d9d3d3760a4f19cc5aa97ef6496dd3e076507d9eb1b333a8f5ea9691d1e starters/reviewed-scam-references.typecheck.json
e6ffc74de5d3e48bed4ab821bedee76b7d8e2a4df887df00a7a5d52b28c08354 tests/activity-connection.test.ts
a791c7dc4bbecce78b91dc309287b2533156f11be43742fcbb89cf8ec285d542 tests/activity-connection.test.ts
2257548bf1aa1de3113925dbdbd367db1211f5692ea6e0e69dae9830ef6ca611 tests/analytics.test.ts
977ac57a09795d86d5d5211ca91fa42db9e4f2b63442b0daadfb16025d42ef1c tests/compose.env
f9c22e2e2b1b12d1463e433966978b3cffb6f57e6903aae9d97d2d47a13eaed7 tests/compose.validation.yml
67cd5970cfecef6cd92fb9b0be95c00bb106bcac1cc4a0f8f368aa95905ef4a8 tests/content.test.ts
8868687c82a15c5fe4ef3d1dc13bfa280b201769a9f21655d7c64c1c429423de tests/core.test.ts
f42285940327aa27a801178df3b9cdc4da8b1eb3cbe00ff72f0a0f3f7091c42e tests/core.test.ts
672cef009e0e6eacfc213d6ebadee6b8e5a5687ff0323ea7492d6ca12bae12c7 tests/evaluate.test.ts
3093de7974045706da57d933b802999a8642677b2d52e6048a4d4c8f276470e3 tests/exact-hash.test.ts
4b9b9b42437ba56db989269d4f46b5d25a9a54d89b70ed3cb15bcc66a0281366 tests/gateway.test.ts
Expand Down
2 changes: 2 additions & 0 deletions activity/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export async function apiResponse<T = any>(response: Response): Promise<T> {
return data as T;
}
export function connectionError(stage: string, error: unknown): Error {
if (error instanceof HttpError && error.status === 409 && error.message === 'bot_not_in_server')
return new Error('The TypeCheck bot is not in this server yet. A member with Manage Server needs to add it: open TypeCheck in a browser, click Add to your server, choose this server and authorize. Then click Connect in Discord.');
if (error instanceof HttpError && error.status === 403 && error.message === 'no_delegated_access')
return new Error('You need permission to use TypeCheck in this server. Ask a server owner or member with Manage Server to open TypeCheck → Permissions and grant access to your Discord role, then click Connect in Discord. Server owners and members with Administrator or Manage Server have access automatically.');
const detail = error instanceof HttpError ? ` (HTTP ${error.status}: ${error.message}${error.reference ? `; reference ${error.reference}` : ''})` :
Expand Down
6 changes: 4 additions & 2 deletions server/discord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ export class DiscordRest implements DiscordAdapter {
return auth.user.id;
}
async memberInfo(guild: string, user: string): Promise<MemberInfo> {
const g: DiscordGuild = await this.request(`/guilds/${guild}`);
const m: DiscordMember = await this.request(`/guilds/${guild}/members/${user}`);
// Discord answers 404 Unknown Guild when the bot is not installed; an Activity can still be launched there.
const notFound = (code: string, status: number) => (e: unknown) => { throw e instanceof AppError && e.code === 'discord_http_404' ? new AppError(code, status) : e; };
const g: DiscordGuild = await this.request(`/guilds/${guild}`).catch(notFound('bot_not_in_server', 409));
const m: DiscordMember = await this.request(`/guilds/${guild}/members/${user}`).catch(notFound('not_a_guild_member', 403));
assert(g.id === guild && m.user?.id === user, 'discord_membership_mismatch', 403);
const permissions = guildPermissions(g, m);
const administrator = (permissions & P.ADMIN) !== 0n;
Expand Down
3 changes: 2 additions & 1 deletion server/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export function createHttp(services: Services, config: Config, root = process.cw
if (v.at < now - 60000)
limits.delete(k); }
// Operational log lines carry codes, timings and request IDs only: never tokens, bodies or member data.
const log = (event: Record<string, unknown>) => console.log(JSON.stringify(event));
// The plain-text prefix keeps every field: the hosting log processor collapses bare JSON to a few keys.
const log = (event: Record<string, unknown>) => console.log('TYPECHECK_EVENT_V1 ' + JSON.stringify(event));
// Starter references are copied after the sign-in reply. The copy normalizes 33 images in one transaction,
// which can outlast the hosting/Discord proxy timeout and turn a completed sign-in into a 502.
const seeding = new Map<string, Promise<void>>();
Expand Down
6 changes: 6 additions & 0 deletions tests/activity-connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,9 @@ test('renewal permission or network failures do not start another OAuth flow', a
await assert.rejects(f.connect(), /Session renewal failed.*server could not be reached/);
assert.equal(f.calls.filter(c => c.startsWith('authorize:')).length, 1);
});
test('a server without the bot explains how to add it instead of reporting a server error', () => {
const error = connectionError('Authorization code exchange', new HttpError('bot_not_in_server', 409));
assert.match(error.message, /bot is not in this server yet/);
assert.match(error.message, /Add to your server/);
assert.doesNotMatch(error.message, /HTTP 409/);
});
8 changes: 8 additions & 0 deletions tests/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,11 @@ test('message reader permission checks apply live channel overwrites and private
deny = '0'; privateThread = true; await rest.assertCanReadMessage(G, MOD, CHANNEL);
joined = false; await assert.rejects(rest.assertCanReadMessage(G, MOD, CHANNEL), /message_read_permission_required/);
});
test('Discord Unknown Guild means the bot is not installed, not an upstream failure', async () => {
const respond = (guild: number, member: number) => (async (url: string | URL) => String(url).includes('/members/')
? Response.json(member === 404 ? { message: 'Unknown Member', code: 10007 } : { user: { id: MOD }, roles: [] }, { status: member })
: Response.json(guild === 404 ? { message: 'Unknown Guild', code: 10004 } : { id: G, owner_id: '1', roles: [] }, { status: guild })) as typeof fetch;
await assert.rejects(new DiscordRest('bot', 'id', 'secret', respond(404, 200)).memberInfo(G, MOD), (e: unknown) => e instanceof AppError && e.code === 'bot_not_in_server' && e.status === 409);
await assert.rejects(new DiscordRest('bot', 'id', 'secret', respond(200, 404)).memberInfo(G, MOD), (e: unknown) => e instanceof AppError && e.code === 'not_a_guild_member' && e.status === 403);
await assert.rejects(new DiscordRest('bot', 'id', 'secret', respond(500, 200)).memberInfo(G, MOD), (e: unknown) => e instanceof AppError && e.code === 'discord_http_500' && e.status === 502);
});
Loading