jillo-bot/src/web/web.ts

164 lines
4.8 KiB
TypeScript

import express, { NextFunction, Response } from 'express';
import { create } from 'express-handlebars';
import * as log from '../lib/log';
import { CustomItem, Counter, CustomCraftingRecipe, db } from '../lib/db';
import { defaultItems } from '../lib/rpg/items';
import { Client, CDN } from 'discord.js';
import { getToken, getSessionString, getSession, setSession, updateCookie } from './oauth2';
import { getUser, getGuilds } from './user';
import { docs, formatTitle } from './docs';
async function getGuildInfo(bot: Client, id: string) {
const guild = await bot.guilds.cache.get(id);
if (!guild) return;
const items = await db<CustomItem>('customItems')
.where('guild', guild.id)
.count({count: '*'});
const counters = await db<Counter>('counters')
.where('guild', guild.id)
.count({count: '*'});
const recipes = await db<CustomCraftingRecipe>('customCraftingRecipes')
.where('guild', guild.id)
.count({count: '*'});
return {
items: items[0].count as number,
counters: counters[0].count as number,
recipes: recipes[0].count as number,
};
}
export async function startServer(bot: Client, port: number) {
const app = express();
const cdn = new CDN();
const hbs = create({
helpers: {
avatar: (id: string, hash: string) => (id && hash) ? cdn.avatar(id, hash, { size: 128 }) : '/assets/avatar.png',
icon: (id: string, hash: string) => (id && hash) ? cdn.icon(id, hash, { size: 128, forceStatic: true }) : '/assets/avatar.png',
}
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.set('views', './views');
app.get('/api/items', async (req, res) => {
const guildID = req.query.guild;
let customItems : Partial<CustomItem>[];
if (guildID) {
customItems = await db<CustomItem>('customItems')
.select('emoji', 'name', 'id', 'description')
.where('guild', guildID)
.limit(25);
} else {
customItems = [];
}
res.json([...defaultItems, ...customItems]);
});
app.get('/api/status', async (_, res) => {
res.json({
guilds: bot.guilds.cache.size,
uptime: bot.uptime
});
});
app.get('/', async (req, res, next) => {
const code = req.query.code as string;
if (code) {
try {
const resp = await getToken(bot, code);
if (!resp) return next({ http: 400, message: 'Invalid code provided' });
const sessionId = await getSessionString(decodeURIComponent(req.headers.cookie || ''));
setSession(sessionId, {
tokenType: resp.token_type,
accessToken: resp.access_token,
refreshToken: resp.refresh_token,
expiresAt: Date.now() + resp.expires_in * 1000,
});
updateCookie(res, sessionId);
return res.redirect('/profile');
} catch (err) {
return next(err);
}
}
const session = await getSession(bot, req.headers);
const user = await getUser(session);
res.render('home', {
signedIn: session !== undefined,
user: user,
layout: false,
});
});
app.get('/profile', async (req, res) => {
const session = await getSession(bot, req.headers);
if (!session) return res.redirect(`https://discord.com/api/oauth2/authorize?client_id=${bot.config.clientId}&redirect_uri=${encodeURIComponent(bot.config.siteURL)}&response_type=code&scope=identify%20guilds`);
const user = await getUser(session);
if (!user) return;
const guilds = await getGuilds(session);
if (!guilds) return;
//res.sendFile('profile/index.html', { root: 'static/' });
res.render('profile', {
title: 'profile',
user,
guilds: await Promise.all(
guilds.map(async guild =>
({...guild, jillo: await getGuildInfo(bot, guild.id)})
)
),
});
});
app.get('/docs/:name', async (req, res, next) => {
const { name } = req.params;
if (!name) return res.redirect('/docs/introduction');
const content = docs[name];
if (!content) return next();
res.render('docs', {
name: formatTitle(name),
content: content,
sidebar: Object.keys(docs).map(d => ({ name: formatTitle(d), value: d })),
});
});
app.use(express.static('static/'));
// error handling
app.use((req, res, next) => {
// since this is the last non-error-handling middleware, assume 404
next({ http: '404', message: `${req.path} not found` });
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
app.use((err: Error | any, req: any, res: Response, next: NextFunction) => {
log.error(err);
const status = err.http ? err.http : 500;
res.status(status).render('error', {
path: req.path,
status,
message: err.message,
});
});
app.listen(port, () => log.info(`web interface listening on ${port}`));
}