Skip to content

Commit

Permalink
feat: bot support
Browse files Browse the repository at this point in the history
  • Loading branch information
sosweetham committed May 12, 2024
1 parent 338d8ae commit 5cd584b
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 7 deletions.
39 changes: 39 additions & 0 deletions app/src/controllers/bot-banner.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Elysia, t } from "elysia"
import { botBannerBucket, guildedBotProfileScrape, streamToBuffer } from "../libs"

export const botBannerController = new Elysia()
.get('/:id', async ({ params }) => {
if (await botBannerBucket.checkAssetExists(params.id)) {
const lastModified = await botBannerBucket.getAssetLastModified(params.id)
if (Date.now() - lastModified.valueOf() > 24 * 60 * 60 * 1000) {
(async () => guildedBotProfileScrape(params.id, 'banner'))().catch((e) => console.error(e))
}
const banner = await botBannerBucket.getAsset(params.id)
return new Response(await streamToBuffer(banner), { headers: { 'Content-Type': 'image/webp' } })
}
const imageBlob = await guildedBotProfileScrape(params.id, 'banner')
if (imageBlob instanceof Error || !imageBlob) {
return new Response('Bot not found', { status: 404 })
}
return new Response(imageBlob, { headers: { 'Content-Type': 'image/webp' } })
}, {
params: t.Object({
id: t.String()
}),
body: t.Undefined(),
response: t.Object({
body: t.File({
type: 'image/webp',
maxItems: 1,
minItems: 0,
maxSize: '10m',
minSize: '0k'
}),
},
{description: 'The bot banner image.'}),
detail: {
description: 'Get the banner of a bot by its ID.',
summary: 'Get bot banner by ID.',
tags: ['bot'],
}
})
42 changes: 42 additions & 0 deletions app/src/controllers/bot-icon.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Elysia, t } from "elysia"
import { botIconBucket, guildedBotProfileScrape, streamToBuffer } from "../libs"

export const botIconController = new Elysia()
.get('/:id', async ({ params }) => {
if (await botIconBucket.checkAssetExists(params.id)) {
const lastModified = await botIconBucket.getAssetLastModified(params.id)
if (Date.now() - lastModified.valueOf() > 24 * 60 * 60 * 1000) {
(async () => guildedBotProfileScrape(params.id, 'icon'))().catch((e) => console.error(e))
}
const avatar = await botIconBucket.getAsset(params.id)
const res = new Response(await streamToBuffer(avatar), { headers: { 'Content-Type': 'image/webp' } })
return res
}
const imageBlob = await guildedBotProfileScrape(params.id, 'icon')
if (imageBlob instanceof Error || !imageBlob) {
return new Response('Bot not found', { status: 404 })
}
const res = new Response(imageBlob, { headers: { 'Content-Type': 'image/webp' } })
return res
}, {
params: t.Object({
id: t.String()
}),
body: t.Undefined(),
response: t.Object({
body: t.File({
type: 'image/webp',
maxItems: 1,
minItems: 0,
maxSize: '10m',
minSize: '0k'
}),
},
{description: 'The bot icon image.'}),
detail: {
description: "Get the bot icon of a bot by it's ID.",
summary: 'Get bot icon by ID.',
tags: ['bot'],
}
})

4 changes: 3 additions & 1 deletion app/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './user-avatar.controller'
export * from './user-banner.controller'
export * from './server-icon.controller'
export * from './server-banner.controller'
export * from './server-banner.controller'
export * from './bot-icon.controller'
export * from './bot-banner.controller'
2 changes: 1 addition & 1 deletion app/src/controllers/server-banner.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const serverBannerController = new Elysia()
}
const imageBlob = await guildedServerProfileScrape(params.id, 'banner')
if (imageBlob instanceof Error || !imageBlob) {
return new Response('User not found', { status: 404 })
return new Response('Server not found', { status: 404 })
}
return new Response(imageBlob, { headers: { 'Content-Type': 'image/webp' } })
}, {
Expand Down
2 changes: 1 addition & 1 deletion app/src/controllers/server-icon.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const serverIconController = new Elysia()
}
const imageBlob = await guildedServerProfileScrape(params.id, 'icon')
if (imageBlob instanceof Error || !imageBlob) {
return new Response('User not found', { status: 404 })
return new Response('Server not found', { status: 404 })
}
const res = new Response(imageBlob, { headers: { 'Content-Type': 'image/webp' } })
return res
Expand Down
7 changes: 6 additions & 1 deletion app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Elysia } from "elysia";
import cors from '@elysiajs/cors'
import swagger from "@elysiajs/swagger";
import { ErrorMessages, bootLogger, gracefulShutdown, requestLogger } from "./libs";
import { userAvatarController, userBannerController, serverIconController, serverBannerController } from "./controllers";
import { userAvatarController, userBannerController, serverIconController, serverBannerController, botIconController, botBannerController } from "./controllers";

if (!process.env.PORT) {
console.log('PORT is not defined');
Expand Down Expand Up @@ -48,6 +48,11 @@ try {
app.group('/banner', (app) => app.use(serverBannerController))
return app
})
app.group('/bot', (app) => {
app.group('/icon', (app) => app.use(botIconController))
app.group('/banner', (app) => app.use(botBannerController))
return app
})
app.listen(process.env.PORT!, bootLogger);
} catch (e) {
console.log('error booting the server');
Expand Down
33 changes: 30 additions & 3 deletions app/src/libs/guilded-scrape.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { userAvatarBucket, userBannerBucket, serverBannerBucket, serverIconBucket } from "./minio"
import { userAvatarBucket, userBannerBucket, serverBannerBucket, serverIconBucket, botIconBucket, botBannerBucket } from "./minio"

export const guildedMediaLink = (awsUrl?: string) => {
// replace https://s3-us-west-2.amazonaws.com/www.guilded.gg with https://cdn.gilcdn.com
Expand All @@ -8,11 +8,11 @@ export const guildedMediaLink = (awsUrl?: string) => {
}

export const guildedUserProfileScrape: (id: string, getElement: 'avatar' | 'banner') => Promise<Blob|Error> = async (id: string, getElement: 'avatar' | 'banner') => {
const profile = await (await fetch(`https://www.guilded.gg/api/users/${id}/profilev3`, {keepalive: false})).json()
const profile = await (await fetch(`https://www.guilded.gg/api/users/${id}`, {keepalive: false})).json()
if (!profile) {
return new Error('User not found')
}
const src = getElement === 'avatar' ? guildedMediaLink(profile.profilePictureLg) : guildedMediaLink(profile.profileBannerLg)
const src = getElement === 'avatar' ? guildedMediaLink(profile.user.profilePictureLg) : guildedMediaLink(profile.user.profileBannerLg)
const signed = await(await fetch(`https://www.guilded.gg/api/v1/url-signatures`, {
method: 'POST',
headers: {
Expand All @@ -37,6 +37,33 @@ export const guildedUserProfileScrape: (id: string, getElement: 'avatar' | 'bann
return await (await fetch(signedSrc)).blob()
}

export const guildedBotProfileScrape: (id: string, getElement: 'icon' | 'banner') => Promise<Blob | Error> = async (id, getElement) => {
const botUserProfile = await (await fetch(`https://www.guilded.gg/api/users/${id}`, {keepalive: false})).json()
const src = getElement === 'icon' ? guildedMediaLink(botUserProfile.user.profilePicture) : guildedMediaLink(botUserProfile.user.profileBannerLg)
const signed = await(await fetch(`https://www.guilded.gg/api/v1/url-signatures`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${process.env.G_TOKEN}`
},
body: JSON.stringify({
urls: [src]
}),
keepalive: false
})).json()
if (!signed || !signed.urlSignatures || !signed.urlSignatures[0] || !signed.urlSignatures[0].url) {
return new Error('Failed to sign URL')
}
const signedSrc = signed.urlSignatures[0].url
if (getElement === 'icon') {
await botIconBucket.uploadImage(id, signedSrc)
} else if (getElement === 'banner') {
await botBannerBucket.uploadImage(id, signedSrc)
}
return await (await fetch(signedSrc)).blob()
}

export const guildedServerProfileScrape: (id: string, getElement: 'icon' | 'banner') => Promise<Blob | Error> = async (id, getElement) => {
try {
const server = await (await fetch(`https://www.guilded.gg/api/teams/${id}/info`, {keepalive: false})).json()
Expand Down
2 changes: 2 additions & 0 deletions app/src/libs/minio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,6 @@ export class BucketManager {
export const userAvatarBucket = new BucketManager('user-avatars');
export const userBannerBucket = new BucketManager('user-banners');
export const serverIconBucket = new BucketManager('server-icons');
export const botIconBucket = new BucketManager('bot-icons');
export const botBannerBucket = new BucketManager('bot-banners');
export const serverBannerBucket = new BucketManager('server-banners');

0 comments on commit 5cd584b

Please sign in to comment.