import { fastify } from 'fastify' import { mongoose } from 'mongoose' import * as Hooks from './hooks.js' import * as AuthenticateRoutings from './routes/authenticate.js' import * as SessionServerRoutings from './routes/session.js' import * as WebAPIRoutings from './routes/web-api.js' import { config } from './config.js' import { readFileSync } from 'fs' import { Scenes, session, Telegraf } from 'telegraf' import { registerAllPlayerCommands } from './telegram/player-commands.js' import { Player } from './models/player.js' import fastifySwagger from '@fastify/swagger' import { S3Client } from '@aws-sdk/client-s3' import pino from 'pino' String.prototype._split = String.prototype.split String.prototype.split = function(separator, limit) { if (separator === undefined && limit === 0) return [] if(limit === undefined) { return String.prototype._split.call(this, separator, limit) } const arr = [] let lastBegin = -1 for(let i = 0; i < limit - 1; i++) { const end = String.prototype.indexOf.call(this, separator, ++lastBegin) if(end == -1) { arr.push(undefined) continue } arr.push(String.prototype.substring.call(this, lastBegin, end)) lastBegin = end } arr.push(String.prototype.substring.call(this, ++lastBegin)) return arr } for(let i = 0; i < process.argv.length; i++) { const curr = process.argv[i] if(curr.startsWith('--')) { switch(curr.substring(2)){ case 'override': { const [next, value] = process.argv[i + 1].split(":", 2) if(next || !next.startsWith('--')) { eval(`config.${next} = '${value}'`) } continue } default: { continue } } } } export const serverLogger = pino({ transport: { target: 'pino-pretty' } }) export const server = fastify({ logger: serverLogger }) export const telegraf = new Telegraf(config.telegram.token) export const s3Instance = new S3Client({ credentials: { accessKeyId: config.storage.key, secretAccessKey: config.storage.secret, }, endpoint: config.storage.endpoint, ...config.storage.extra }) export const setup = async () => { server.log.info("老色批世界树 > 初始化中...") await mongoose.connect(config.database.url) const publicKey = readFileSync(config.signing.public).toString() const privateKey = readFileSync(config.signing.private).toString() server.decorate('keys', { publicKey, privateKey }) config.custom.preHooks(server) server.addHook('preHandler', Hooks.headerValidation) server.setErrorHandler(Hooks.handleError) server.addContentTypeParser('image/png', (_, payload, done) => { done(null, payload) }) server.register(fastifySwagger, { routePrefix: '/docs', swagger: { title: "lsp-yggdrasil 接口文档", version: "1.0.0", tags: [ { name: "Authserver", description: "Yggdrasil Authserver 协议定义的接口"}, { name: "Sessionserver", description: "Yggdrasil Authserver 协议定义的接口"}, { name: "api", description: "Yggdrasil Authserver 协议定义的接口"}, { name: "webapi", description: "web前端接口"}, ] }, exposeRoute: true, }) config.custom.preRouting(server) // Authserver routings server.route(AuthenticateRoutings.authenticate) server.route(AuthenticateRoutings.refresh) server.route(AuthenticateRoutings.validate) server.route(AuthenticateRoutings.invalidate) server.route(AuthenticateRoutings.signout) server.route(SessionServerRoutings.join) server.route(SessionServerRoutings.hasJoined) server.route(SessionServerRoutings.profile) server.route(SessionServerRoutings.profiles) server.route(WebAPIRoutings.CORS_BYPASS) server.route(WebAPIRoutings.meta) server.route(WebAPIRoutings.status) server.route(WebAPIRoutings.telegramBind) server.route(WebAPIRoutings.login) server.route(WebAPIRoutings.register) server.route(WebAPIRoutings.textures) server.route(WebAPIRoutings.uploadTexture) config.custom.postRouting(server) if(process.env["UNIT_TEST"] || process.env["DEVEL_FIRST_RUN"]) { // Create a test player await new Player({ username: 'test', password: '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92', email: 'i@lama.icu', uuid: '098f6bcd-4621-3373-8ade-4e832627b4f6', textures: { skin: 'assets.lama.icu/textures/skin/steve.png', cape: 'assets.lama.icu/textures/cape/default.png' }, registerDate: Date.now(), permissions: ['login'], binding: { platform: 'telegram', username: 'Qumolama', verified: true, } }).save() } registerAllPlayerCommands() } const launch = async () => { process.on('SIGINT', shutdown) process.on('SIGTERM', shutdown) await telegraf.launch() await server.listen({ port: config.server.port, url: config.server.url }) server.log.info("老色批世界树 > 基于 fastify 的高性能 HTTP 服务器已启动") } export const shutdown = async () => { await server.close() server.log.info("老色批世界树 > HTTP 服务器已关闭") try { telegraf.stop() server.log.info("老色批世界树 > Telegram Bot 已关闭") } catch(err) { server.log.info("老色批世界树 > Telegram Bot 未运行,已跳过") } mongoose.disconnect() server.log.info("老色批世界树 > 数据库连接已断开,服务器已关闭") } (async () => { if(!process.env["UNIT_TEST"]) { console.log(` ================================================================ __ _____ ______ __ __ _ __ / / / ___// __ \\ \\/ /___ _____ _____/ /________ ______(_) / / / \\__ \\/ /_/ /\\ / __ \`/ __ \`/ __ / ___/ __ \`/ ___/ / / / /______/ / ____/ / / /_/ / /_/ / /_/ / / / /_/ (__ ) / / /_____/____/_/ /_/\\__, /\\__, /\\__,_/_/ \\__,_/____/_/_/ /____//____/ ================================================================\n`) if(typeof PROGRAM_PRODUCTION === 'undefined') { console.warn("⚠ 警告: 您运行的不是正式版本,可能会不稳定,仅限开发环境运行!\n") } await setup() await launch() } })()