Skip to content

Commit 3b2e213

Browse files
committed
feat: dash recognizing ama bot
1 parent 0c96d03 commit 3b2e213

10 files changed

Lines changed: 80 additions & 21 deletions

File tree

apps/website/next.config.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ export default {
4040
permanent: true,
4141
},
4242
{
43-
source: '/invites/automoderator',
43+
source: '/invites/ama',
4444
destination:
45-
'https://discord.com/api/oauth2/authorize?client_id=847081327950168104&permissions=1100451531910&scope=applications.commands%20bot',
45+
'https://discord.com/oauth2/authorize?client_id=872022469081448489&permissions=274878024704&scope=applications.commands+bot',
4646
permanent: true,
4747
},
4848
];

apps/website/src/app/dashboard/_components/GuildCard.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { MeGuild } from '@chatsift/api';
22
import { GuildIcon } from '@/components/common/GuildIcon';
3-
import { SvgAutoModerator } from '@/components/icons/SvgAutoModerator';
3+
import { SvgAMA } from '@/components/icons/SvgAMA';
44
import { cn } from '@/utils/util';
55

66
interface GuildCardProps {
@@ -26,9 +26,9 @@ export default function GuildCard({ data }: GuildCardProps) {
2626

2727
{hasBots ? (
2828
<ul className="flex flex-row gap-1">
29-
{data.bots.includes('automoderator') && (
29+
{data.bots.includes('AMA') && (
3030
<li>
31-
<SvgAutoModerator />
31+
<SvgAMA />
3232
</li>
3333
)}
3434
</ul>
@@ -41,8 +41,8 @@ export default function GuildCard({ data }: GuildCardProps) {
4141
<p className="text-lg font-medium text-primary dark:text-primary-dark">Invite a bot:</p>
4242
<ul className="flex flex-row gap-3">
4343
<li>
44-
<a href="/invites/automoderator">
45-
<SvgAutoModerator />
44+
<a href="/invites/ama">
45+
<SvgAMA />
4646
</a>
4747
</li>
4848
</ul>

apps/website/src/components/common/GuildIcon.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,21 @@ export function GuildIcon({ data, hasBots }: GuildIconProps) {
1919

2020
return (
2121
<div className="flex flex-row items-center">
22-
{icon ? (
23-
<a href={url}>
22+
<a href={url}>
23+
{icon ? (
2424
<Image
2525
alt="Guild icon"
2626
className="flex h-12 w-12 items-center justify-center rounded-full border-on-secondary bg-on-tertiary dark:border-on-secondary-dark dark:bg-on-tertiary-dark"
2727
height={48}
2828
src={icon}
2929
width={48}
3030
/>
31-
</a>
32-
) : (
33-
<p className="flex h-12 w-12 items-center justify-center overflow-hidden text-ellipsis whitespace-nowrap rounded-full border-on-secondary bg-on-tertiary after:max-w-[70%] dark:border-on-secondary-dark dark:bg-on-tertiary-dark">
34-
{getGuildAcronym(data.name)}
35-
</p>
36-
)}
31+
) : (
32+
<p className="flex h-12 w-12 items-center justify-center overflow-hidden text-ellipsis whitespace-nowrap rounded-full border-on-secondary bg-on-tertiary after:max-w-[70%] dark:border-on-secondary-dark dark:bg-on-tertiary-dark">
33+
{getGuildAcronym(data.name)}
34+
</p>
35+
)}
36+
</a>
3737
</div>
3838
);
3939
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function SvgAMA({ width, height }: { readonly height?: number; readonly width?: number }) {
2+
return (
3+
<svg fill="none" height={height ?? 24} viewBox="0 0 24 24" width={width ?? 24} xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
className="stroke-misc-accent"
6+
d="M4.5 4.5V18H9L12 21L15 18H19.5V4.5H4.5Z"
7+
strokeLinejoin="round"
8+
strokeWidth="1.875"
9+
/>
10+
<path
11+
className="fill-primary stroke-primary dark:fill-primary-dark dark:stroke-primary-dark"
12+
d="M9.75 8.0625C9.23223 8.0625 8.8125 8.48223 8.8125 9C8.8125 9.51777 9.23223 9.9375 9.75 9.9375V8.0625ZM14.25 9H15.1875C15.1875 8.48223 14.7678 8.0625 14.25 8.0625V9ZM14.25 11.3333L14.5163 12.2322C14.9144 12.1143 15.1875 11.7485 15.1875 11.3333H14.25ZM11.7337 11.1011C11.2372 11.2482 10.954 11.7699 11.1011 12.2663C11.2482 12.7628 11.7699 13.046 12.2663 12.8989L11.7337 11.1011ZM12.9375 15C12.9375 14.4822 12.5178 14.0625 12 14.0625C11.4822 14.0625 11.0625 14.4822 11.0625 15H12.9375ZM11.0625 15.0015C11.0625 15.5193 11.4822 15.939 12 15.939C12.5178 15.939 12.9375 15.5193 12.9375 15.0015H11.0625ZM9.75 9.9375H14.25V8.0625H9.75V9.9375ZM13.3125 9V11.3333H15.1875V9H13.3125ZM13.9837 10.4345L11.7337 11.1011L12.2663 12.8989L14.5163 12.2322L13.9837 10.4345ZM11.0625 15V15.0015H12.9375V15H11.0625Z"
13+
/>
14+
</svg>
15+
);
16+
}

packages/private/backend-core/src/lib/data/bots.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const BOTS = ['AMA'] as const;
44

55
export type BotId = (typeof BOTS)[number];
66

7+
// TODO: Abstract
78
export const GlobalCaches = {
89
/**
910
* Not to be used in such a way to risk race conditions. There should really be one place that sets this per bot,
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import { Buffer } from 'node:buffer';
12
import type { Logger } from 'pino';
2-
import { createClient } from 'redis';
3+
import { createClient, RESP_TYPES } from 'redis';
34
import { ENV } from './env.js';
45

56
export async function createRedis(logger: Logger) {
67
return createClient({ url: ENV.REDIS_URL })
8+
.withTypeMapping({
9+
[RESP_TYPES.BLOB_STRING]: Buffer,
10+
})
711
.on('error', (err) => logger.error(err, 'redis error'))
812
.connect();
913
}

packages/private/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './lib/constants.js';
22
export * from './lib/discordPermissions.js';
3+
export * from './lib/promiseAllObject.js';
34

45
export type * from './types/entities.js';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint-disable tsdoc/syntax */
2+
3+
/**
4+
* Transforms an object of promises into a promise of an object where all the values are awaited, much like
5+
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all | Promise.all}.
6+
*
7+
* @remarks
8+
* This is the flow we follow:
9+
*
10+
* { a: Promise<X>, b: Promise<Y> }
11+
*
12+
* => [ ['a', Promise<X>], ['b', Promise<Y>] ]
13+
*
14+
* => [ Promise<['a', X]>, Promise<'b', Y>] ]
15+
*
16+
* => (via awaited Promise.all) [ ['a', X], ['b', Y] ]
17+
*
18+
* => Promise<{ a: X, b: Y }>
19+
*/
20+
export async function promiseAllObject<TRecord extends Record<string, Promise<any>>>(
21+
obj: TRecord,
22+
): Promise<{ [K in keyof TRecord]: Awaited<TRecord[K]> }> {
23+
return Object.fromEntries(await Promise.all(Object.entries(obj).map(async ([key, value]) => [key, await value])));
24+
}

services/api/src/middleware/__tests__/isAuthed.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ vi.mock('@chatsift/backend-core', async (importActual) => {
3434
...actual,
3535
// @ts-expect-error - Arg forwarding
3636
createContext: (...args: any[]) => ({ ...actual.createContext(...args), UP_SINCE: Date.now() - 1_000 * 60 * 5 }),
37-
createRedis: () => null,
37+
createRedis: () => ({
38+
get: vi.fn(async () => null),
39+
}),
3840
};
3941
});
4042

services/api/src/util/me.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { setTimeout } from 'node:timers';
2-
import { PermissionsBitField } from '@chatsift/backend-core';
2+
import type { BotId } from '@chatsift/backend-core';
3+
import { BOTS, GlobalCaches, PermissionsBitField, promiseAllObject } from '@chatsift/backend-core';
34
import type { APIUser, RESTAPIPartialCurrentUserGuild } from '@discordjs/core';
45
import { PermissionFlagsBits } from '@discordjs/core';
56
import { context } from '../context.js';
67
import { discordAPIOAuth } from './discordAPI.js';
78

8-
// TODO: Proper bots type
99
export type MeGuild = Pick<RESTAPIPartialCurrentUserGuild, 'icon' | 'id' | 'name'> & {
10-
bots: string[];
10+
bots: BotId[];
1111
meCanManage: boolean;
1212
};
1313
export type Me = APIUser & { guilds: MeGuild[]; isGlobalAdmin: boolean };
@@ -28,6 +28,17 @@ export async function fetchMe(discordAccessToken: string, force = false): Promis
2828
const discordUser = await discordAPIOAuth.users.getCurrent({ auth });
2929
const guildsRaw = await discordAPIOAuth.users.getGuilds({}, { auth });
3030

31+
const guildsByBot = await promiseAllObject(
32+
Object.fromEntries(
33+
BOTS.map((bot) => [
34+
bot,
35+
context.redis
36+
.get(GlobalCaches.GuildList.key(bot))
37+
.then((data) => (data ? GlobalCaches.GuildList.recipe.decode(data).guilds : [])),
38+
]),
39+
) as Record<BotId, Promise<string[]>>,
40+
);
41+
3142
const guilds = guildsRaw.map<MeGuild>(({ id, name, icon, owner, permissions }) => ({
3243
id,
3344
name,
@@ -39,7 +50,7 @@ export async function fetchMe(discordAccessToken: string, force = false): Promis
3950
BigInt(permissions),
4051
PermissionFlagsBits.ManageGuild | PermissionFlagsBits.Administrator,
4152
) || owner,
42-
bots: [],
53+
bots: BOTS.filter((bot) => guildsByBot[bot]?.includes(id)),
4354
}));
4455

4556
const me: Me = {

0 commit comments

Comments
 (0)