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('customItems') .where('guild', guild.id) .count({count: '*'}); const counters = await db('counters') .where('guild', guild.id) .count({count: '*'}); const recipes = await db('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[]; if (guildID) { customItems = await db('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}`)); }