diff --git a/package.json b/package.json index e161834..b2eb761 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,6 @@ }, "scripts": { "dev": "node launch-development.js", - "test": "cross-env NODE_OPTIONS=--experimental-vm-modules UNIT_TEST=1 NODE_NO_WARNINGS=1 jest", + "test": "cross-env NODE_OPTIONS=--experimental-vm-modules UNIT_TEST=1 NODE_NO_WARNINGS=1 jest" } } diff --git a/src/config.js b/src/config.js index 51a0f8e..82cacf8 100644 --- a/src/config.js +++ b/src/config.js @@ -3,7 +3,7 @@ export const config = { url: '您的mongodb链接', }, server: { - port: 3000, + port: 30, skinDomain: [ "assets.lama.icu", 'textures.minecraft.net' ], serverName: "老色批世界树", advanced: { // 详情可见 -> https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#meta-%E4%B8%AD%E7%9A%84%E5%85%83%E6%95%B0%E6%8D%AE diff --git a/src/index.js b/src/index.js index d684ce3..17ddd98 100644 --- a/src/index.js +++ b/src/index.js @@ -3,17 +3,14 @@ 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 AdvancedRoutings from './routes/advanced.js' -import * as APIRoutings from './routes/api.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 { allScenes, registerAllPlayerCommands } from './telegram/player-commands.js' +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 multipart from '@fastify/multipart' import pino from 'pino' String.prototype._split = String.prototype.split @@ -99,12 +96,6 @@ export const setup = async () => { server.addContentTypeParser('image/png', (_, payload, done) => { done(null, payload) }) - - server.register(multipart, { - limit: { - fileSize: 5 * 1024 * 1024 // 5MB/40Mb - } - }) server.register(fastifySwagger, { routePrefix: '/docs', swagger: { @@ -132,12 +123,11 @@ export const setup = async () => { server.route(SessionServerRoutings.join) server.route(SessionServerRoutings.hasJoined) server.route(SessionServerRoutings.profile) + server.route(SessionServerRoutings.profiles) - server.route(AdvancedRoutings.meta) - server.route(AdvancedRoutings.status) - - server.route(APIRoutings.profiles) - + server.route(WebAPIRoutings.meta) + server.route(WebAPIRoutings.status) + server.route(WebAPIRoutings.telegramBind) server.route(WebAPIRoutings.login) server.route(WebAPIRoutings.register) server.route(WebAPIRoutings.textures) @@ -157,20 +147,14 @@ export const setup = async () => { cape: 'assets.lama.icu/textures/cape/default.png' }, registerDate: Date.now(), - permissions: [{ node: 'login', allowed: true, duration: 0, eternal: true, startDate: Date.now(), highPriority: false }], - telegramBind: { - username: 'test', + permissions: ['login'], + binding: { + platform: 'telegram', + username: 'Qumolama', verified: true, } }).save() } - - const stage = new Scenes.Stage([ ...allScenes ]) - telegraf.use(session()) - telegraf.use(stage.middleware()) - - // server.log.info("老色批世界树 > 初始化中...") - registerAllPlayerCommands() } diff --git a/src/models/player.js b/src/models/player.js index fd4dcaa..353ae18 100644 --- a/src/models/player.js +++ b/src/models/player.js @@ -16,9 +16,12 @@ export const Player = mongoose.model("Player", new mongoose.Schema({ cape: String, }, registerDate: Number, - permissions: [{ node: String, allowed: Boolean, duration: Number, startDate: Number, highPriority: Boolean }], // ban -> true - telegramBind: { + permissions: [{ + type: String + }], + binding: { username: String, + platform: String, verified: Boolean, } })) diff --git a/src/routes/advanced.js b/src/routes/advanced.js deleted file mode 100644 index d6225dd..0000000 --- a/src/routes/advanced.js +++ /dev/null @@ -1,130 +0,0 @@ -import { config, getOverrideHandler, getOverridePreHandler } from "../config.js" - -export const meta = { - method: "GET", - url: "/", - schema: { - summary: "获取 Meta 信息", - description: "获取服务器元数据。详情请见 authlib-injector 文档。", - tags: [ "api" ], - response: { - 200: { - "type": "object", - "properties": { - "meta": { - "type": "object", - "properties": { - "serverName": { - "type": "string" - }, - "implementationName": { - "type": "string" - }, - "implementationVersion": { - "type": "string" - } - } - }, - "skinDomains": { - "type": "array", - "items": { - "type": "string" - } - }, - "signaturePublickey": { - "type": "string" - } - } - } - } - }, - preHandler: getOverridePreHandler("/"), - handler: getOverrideHandler("/") ?? async function(req, rep) { - rep.code(200).send({ - meta: { - serverName: config.server.serverName, - implementationName: "lsp-yggdrasil", - implementationVersion: "1.0", - - }, - skinDomains: config.server.skinDomain, - signaturePublickey: this.keys.publicKey - }) - } -} - -export const status = { - method: "GET", - url: "/status", - schema: { - summary: "获取服务器信息", - description: "获取服务器基本信息", - tags: [ "webapi" ], - response: { - 200: { - "type": "object", - "properties": { - "public": { - "type": "string" - }, - "version": { - "type": "string" - }, - "api": { - "type": "object", - "properties": { - "yggdrasil": { - "type": "string" - }, - "webapi": { - "type": "string" - } - } - }, - "name": { - "type": "string" - }, - "server": { - "type": "string" - }, - "env": { - "type": "object", - "properties": { - "node": { - "type": "string" - }, - "os": { - "type": "string" - }, - "arch": { - "type": "string" - }, - "t": { - "type": "number" - }, - } - } - } - } - } - }, - preHandler: getOverridePreHandler("/status"), - handler: getOverrideHandler("/status") ?? async function(req, rep) { - rep.code(200).send({ - public: this.keys.publicKey, - version: "1.0", - name: config.server.serverName, - api: { - yggdrasil: "authlib-injector", - webapi: "standard-1.0" - }, - server: "lsp-yggdrasil", - env: { - "node": process.version, - "os": process.platform, - "arch": process.arch, - "t": Date.now(), - } - }) - } -} \ No newline at end of file diff --git a/src/routes/api.js b/src/routes/api.js deleted file mode 100644 index ef412b3..0000000 --- a/src/routes/api.js +++ /dev/null @@ -1,73 +0,0 @@ -import { getOverrideHandler, getOverridePreHandler } from "../config.js" -import { uuidToNoSymboUUID } from "../generator.js" -import { Player } from "../models/player.js" - -export const profiles = { - method: 'POST', - url: '/api/profiles/minecraft', - schema: { - summary: "获取玩家信息", - description: "获取玩家信息。详情请见 authlib-injector 文档。", - tags: [ "api" ], - body: { - type: 'array', - items: { - type: 'string' - } - }, - response: { - 200: { - type: 'array', - items: { - type: 'object', - properties: { - id: { - type: 'string' - }, - name: { - type: 'string' - } - } - } - } - } - }, - preHandler: getOverridePreHandler('/api/profiles/minecraft') ?? async function(req, rep) { - if(req.body.length >= 3) { - await rep.code(400).send({ - error: "IllegalArgumentException", - errorMessage: "请求内容过长, 最大请求玩家数: 2", - cause: "请求内容过长, 最大请求玩家数: 2" - }) - } - }, - handler: getOverrideHandler('/api/profiles/minecraft') ?? async function (req, rep) { - const { body } = req - const profiles = await Player.find({ username: { $in: body } }) - return await rep.code(200).send(profiles.map(profile => ({ - id: uuidToNoSymboUUID(profile.uuid), - name: profile.username - }))) - } -} - -export const telegramBind = { - method: 'GET', - url: '/api/telegram/bind/:uuid', - schema: { - summary: "获取 Telegram 绑定", - description: "获取玩家对应 Telegram 绑定,返回纯文本。", - tags: [ "webapi" ], - response: { - 200: { - type: 'string' - } - } - }, - preHandler: getOverridePreHandler('/api/telegram/bind/:uuid'), - handler: getOverrideHandler('/api/telegram/bind/:uuid') ?? async function (req, rep) { - const { uuid } = req.params - const player = await Player.findOne({ uuid: uuidToNoSymboUUID(uuid) }) - await rep.code(200).send(player?.telegram ?? "") - } -} \ No newline at end of file diff --git a/src/routes/authenticate.js b/src/routes/authenticate.js index aa4c048..027925a 100644 --- a/src/routes/authenticate.js +++ b/src/routes/authenticate.js @@ -72,9 +72,7 @@ export const authenticate = { } const player = await PlayerModel.Player.findOne({ email: username, password: createHash('sha256').update(password).digest().toString('hex').toLowerCase() }) - if(!player || !player.permissions.some((it) => { - return it.node === 'login' && it.allowed && (it.duration === 0 || it.startDate + it.duration > Date.now()) - })) { + if(!player || player.permissions.indexOf("login") === -1) { return await rep.code(401).send({ error: "Unauthorized", errorMessage: "用户名或密码错误", @@ -82,11 +80,11 @@ export const authenticate = { }) } - if(!player.telegramBind || !player.telegramBind.verified) { + if(!player.binding || !player.binding.verified) { return await rep.code(401).send({ error: "Unauthorized", - errorMessage: "未绑定 Telegram 账号或账号验证未通过,登录请求已禁止", - cause: "未绑定 Telegram 账号或账号验证未通,登录请求已禁止" + errorMessage: "您的账号未验证,登录请求已禁止", + cause: "您的账号未验证,登录请求已禁止" }) } diff --git a/src/routes/session.js b/src/routes/session.js index 8a12e5a..10bdb0d 100644 --- a/src/routes/session.js +++ b/src/routes/session.js @@ -1,5 +1,5 @@ import { getOverrideHandler, getOverridePreHandler } from '../config.js' -import { toSymboUUID } from '../generator.js' +import { toSymboUUID, uuidToNoSymboUUID } from '../generator.js' import { getPlayerSerialization, Player, PlayerSeriliazationSchema } from '../models/player.js' import { Token } from '../models/token.js' @@ -177,3 +177,52 @@ export const profile = { } } + +export const profiles = { + method: 'POST', + url: '/api/profiles/minecraft', + schema: { + summary: "获取玩家信息", + description: "获取玩家信息。详情请见 authlib-injector 文档。", + tags: [ "api" ], + body: { + type: 'array', + items: { + type: 'string' + } + }, + response: { + 200: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string' + }, + name: { + type: 'string' + } + } + } + } + } + }, + preHandler: getOverridePreHandler('/api/profiles/minecraft') ?? async function(req, rep) { + if(req.body.length >= 3) { + await rep.code(400).send({ + error: "IllegalArgumentException", + errorMessage: "请求内容过长, 最大请求玩家数: 2", + cause: "请求内容过长, 最大请求玩家数: 2" + }) + } + }, + handler: getOverrideHandler('/api/profiles/minecraft') ?? async function (req, rep) { + const { body } = req + const profiles = await Player.find({ username: { $in: body } }) + return await rep.code(200).send(profiles.map(profile => ({ + id: uuidToNoSymboUUID(profile.uuid), + name: profile.username + }))) + } +} \ No newline at end of file diff --git a/src/routes/web-api.js b/src/routes/web-api.js index 7cea453..f67d3d6 100644 --- a/src/routes/web-api.js +++ b/src/routes/web-api.js @@ -3,9 +3,12 @@ import { Player } from "../models/player.js" import { createHash } from "crypto" import { generateToken, uuid } from "../generator.js" import { Token } from "../models/token.js" -import { s3Instance } from "../index.js" +import { s3Instance, server } from "../index.js" import { ImageSecurity } from "../secure.js" import { PutObjectCommand } from "@aws-sdk/client-s3" +import crypto from 'crypto' + +const defaultSkin = "https://textures.minecraft.net/texture/ddb8684e59f771666bde5f411fcb2e495c452f2ecabc31981bc132ac71bdd394" const BASE_RESPONSE = { err: { @@ -149,9 +152,7 @@ export const login = { }); } - if(!user.permissions.some((it) => { - return it.node === 'login' && it.allowed && (it.duration === 0 || it.startDate + it.duration > Date.now()) - })) { + if(user.permissions.indexOf("login") === -1) { return await rep.code(401).send({ err: 0.337187, msg: "泻药,宁滴账号已被封禁" @@ -211,9 +212,13 @@ export const register = { type: 'string', description: '邮箱' }, - telegramId: { + invitationCode: { type: 'string', - description: 'telegramId' + description: 'invitationCode' + }, + validationCode: { + type: 'string', + description: 'invitationCode' }, textureMigrations: { anyOf: [ @@ -244,68 +249,27 @@ export const register = { 200: { type: 'object', properties: { - ...BASE_RESPONSE, - extra: { - username: { - type: 'string', - description: '用户名' - }, - password: { - type: 'string', - description: '密码' - }, - email: { - type: 'string', - description: '邮箱' - }, - telegramId: { - type: 'string', - description: 'telegramId' - }, - textureMigrations: { - anyOf: [ - { - type: 'object', - description: '纹理迁移', - properties: { - skin: { - anyOf: [ - { type: 'string', description: '皮肤' }, - { type: 'null' } - ] - }, - cape: { - anyOf: [ - { type: 'string', description: '披风' }, - { type: 'null' } - ] - } - } - }, - { type: 'null' } - ] - } - } + ...BASE_RESPONSE } }, }, }, preHandler: getOverridePreHandler('/api/register'), handler: getOverrideHandler('/api/register') ?? async function(req, rep) { - const { username, password, email, telegramId, textureMigrations } = req.body + const { username, password, email, invitationCode, textureMigrations, validationCode } = req.body const user = await Player.findOne({ $or: [ { email: email }, { username: username } ] }) if (user) { return await rep.code(401).send({ - err: 1, + err: 1.143688, msg: "用户名已存在" }) } if(username == 0 || password == 0 || email == 0 || telegramId == 0) { return await rep.code(401).send({ - err: 1, + err: 1.143688, msg: "用户名/密码/邮箱/telegramId不能为空" }) } @@ -313,15 +277,41 @@ export const register = { const textues = { } if(textureMigrations) { - if(textureMigrations.skin != 0 && textureMigrations.skin) { - textues.skin = textureMigrations.skin - } + textues.skin = textureMigrations.skin ?? defaultSkin if(textureMigrations.cape != 0 && textureMigrations.cape) { textues.cape = textureMigrations.cape } } + /* + Examples: + { + p(latform): "name", + n(ame): "name", + t(o): "email", + } + + + v -> Signature + + */ + const { p, n, t, v } = JSON.parse(crypto.privateDecrypt(server.keys.privateKey, Buffer.from(invitationCode)).toString()) + + if(!crypto.createVerify('rsa-sha1').update(Buffer.from(invitationCode)).verify(server.keys.publicKey, Buffer.from(v, 'hex'))) { + return await rep.code(401).send({ + err: 1.143688, + msg: "邀请码验证失败!非法邀请码!" + }) + } + + if(t !== email) { + return await rep.code(401).send({ + err: 1.143688, + msg: "邀请码验证失败!这邀请码不属于你!" + }) + } + const newUser = new Player({ username, password: createHash("sha256").update(password).digest('hex'), @@ -329,10 +319,11 @@ export const register = { uuid: uuid('LSPlayer:' + email), textues, registerDate: Date.now(), - permissions: [{ node: 'login', allowed: true, duration: -1, startDate: Date.now(), highPriority: false }], - telegramBind: { - username: telegramId, - verified: false + permissions: ['login'], + binding: { + platform: p, + username: n, + verified: true, } }); await newUser.save() @@ -464,4 +455,164 @@ export const uploadTexture = { msg: "" }) } +} + +export const telegramBind = { + method: 'GET', + url: '/api/binding/:uuid', + schema: { + summary: "获取 Telegram 绑定", + description: "获取玩家对应 Telegram 绑定,返回纯文本。", + tags: [ "webapi" ], + response: { + 200: { + type: 'object', + properties: { + username: { + type: 'string' + }, + platform: { + type: 'string' + }, + verified: { + type: 'boolean' + } + } + } + } + }, + preHandler: getOverridePreHandler('/api/binding/:uuid'), + handler: getOverrideHandler('/api/binding/:uuid') ?? async function (req, rep) { + const { uuid } = req.params + return await rep.code(200).send((await Player.findOne({ uuid }))?.binding ?? { username: '', platform: '', verified: false }) + } +} + +export const meta = { + method: "GET", + url: "/", + schema: { + summary: "获取 Meta 信息", + description: "获取服务器元数据。详情请见 authlib-injector 文档。", + tags: [ "api" ], + response: { + 200: { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "serverName": { + "type": "string" + }, + "implementationName": { + "type": "string" + }, + "implementationVersion": { + "type": "string" + } + } + }, + "skinDomains": { + "type": "array", + "items": { + "type": "string" + } + }, + "signaturePublickey": { + "type": "string" + } + } + } + } + }, + preHandler: getOverridePreHandler("/"), + handler: getOverrideHandler("/") ?? async function(req, rep) { + rep.code(200).send({ + meta: { + serverName: config.server.serverName, + implementationName: "lsp-yggdrasil", + implementationVersion: "1.0", + + }, + skinDomains: config.server.skinDomain, + signaturePublickey: this.keys.publicKey + }) + } +} + +export const status = { + method: "GET", + url: "/status", + schema: { + summary: "获取服务器信息", + description: "获取服务器基本信息", + tags: [ "webapi" ], + response: { + 200: { + "type": "object", + "properties": { + "public": { + "type": "string" + }, + "version": { + "type": "string" + }, + "api": { + "type": "object", + "properties": { + "yggdrasil": { + "type": "string" + }, + "webapi": { + "type": "string" + } + } + }, + "name": { + "type": "string" + }, + "server": { + "type": "string" + }, + "env": { + "type": "object", + "properties": { + "node": { + "type": "string" + }, + "os": { + "type": "string" + }, + "arch": { + "type": "string" + }, + "t": { + "type": "number" + }, + } + } + } + } + } + }, + preHandler: getOverridePreHandler("/status"), + handler: getOverrideHandler("/status") ?? async function(req, rep) { + rep.code(200).send({ + public: this.keys.publicKey, + version: "1.0", + name: config.server.serverName, + api: { + yggdrasil: "authlib-injector", + webapi: "standard-1.0" + }, + server: "lsp-yggdrasil", + env: { + "node": process.version, + "os": process.platform, + "arch": process.arch, + "t": Date.now(), + } + }) + } } \ No newline at end of file diff --git a/src/telegram/player-commands.js b/src/telegram/player-commands.js index b3d3d2f..d2fb215 100644 --- a/src/telegram/player-commands.js +++ b/src/telegram/player-commands.js @@ -1,205 +1,108 @@ -import { Scenes } from 'telegraf' import { telegraf, server } from '../index.js' -import { createHash } from 'crypto' -import axios from 'axios' -import { uuid } from '../generator.js' import { Player } from '../models/player.js' +import crypto from 'crypto' -export const registerAllPlayerCommands = async () => { - await register() +export const registerAllPlayerCommands = () => { + adminCreateInvitation() + adminBan() + adminRevokeBan() + + userCreateInvitation() } - - -const defaultSkin = "http://textures.minecraft.net/texture/ddb8684e59f771666bde5f411fcb2e495c452f2ecabc31981bc132ac71bdd394" - -const registerWizard = new Scenes.WizardScene('REGISTRIATION_WIZARD', - (ctx) => { - ctx.reply('欢迎来到注册流程,如果想中途取消使用指令 "/cancle" ,请先告诉我您的用户名') - ctx.scene.session.data = { - username: ctx.from.username - } - return ctx.wizard.next() - }, - (ctx) => { - if(!ctx.message.text || ctx.message.text < 3) { - return ctx.reply('用户名不能少于 3 个字符!请重新输入') +const adminCreateInvitation = () => { + telegraf.command('invite', async (ctx) => { + const args = ctx.update.message.text.split(' ').slice(1) + if(args.length < 2) { + ctx.reply('Usage: /invite [platform]') + return } - const username = ctx.from.username === 'Lapis_Apple' ? 'LSP' : ctx.message.text - ctx.reply(`请设置玩家 ${username} 的密码`) - ctx.scene.session.data.playerUsername = username + const player = await Player.findOne({ 'binding.platform': 'telegram', 'binding.username': ctx.message.from.username }) - return ctx.wizard.next() - }, - async (ctx) => { - if(!ctx.message.text || ctx.message.text < 6) { - return ctx.reply('密码不能少于 6 个字符!请重新输入') + if(!player || player.permissions.indexOf('admin') === -1) { + return ctx.reply('配钥匙吗?什么?你配?哦不你不配!') } - ctx.scene.session.data.password = createHash('sha256').update(ctx.message.text).digest('hex') - - const duplicated = await Player.findOne({ password: ctx.scene.session.data.password }) - if(duplicated) { - return ctx.reply(`该密码hash为:${ctx.scene.session.data.password.substring(3,10)} 已检测到数据库中存在重复密码(${JSON.stringify({d: duplicated})}),为了安全请重新输入`) - } - - ctx.reply(`该密码hash为:${ctx.scene.session.data.password.substring(3,10)} 请确认是否正确,若正确请输入 'confirm'`) - - return ctx.wizard.next() - }, - (ctx) => { - if(!ctx.message.text || ctx.message.text !== 'confirm') { - return ctx.reply('请输入 "confirm" 确认您的密码,或者输入 /cancle 取消注册') - } - - ctx.replyWithMarkdownV2("请输入您的邮箱。**注意:我们并不会向您发送注册确认,邮箱仅作为符合 authlib\\-injector 登陆规范在登陆时使用**") - return ctx.wizard.next() - }, - (ctx) => { - if(!ctx.message.text || ctx.message.text.length < 6) { - return ctx.reply('邮箱不能少于 6 个字符!请重新输入') - } - - ctx.scene.session.data.email = ctx.message.text - ctx.reply(`是否需要同步同用户名正版玩家的皮肤和披风? 输入 'yes' 或者 'no'`) - return ctx.wizard.next() - }, - (ctx) => { - if(!ctx.message.text || ctx.message.text !== 'yes' && ctx.message.text !== 'no') { - return ctx.reply('请输入 "yes" 或者 "no"') - } - - ctx.scene.session.data.sync = ctx.message.text === 'yes' - ctx.reply(`请问您是否有注册邀请码? 有请输入邀请码,没有输入 'skip'`) - return ctx.wizard.next() - }, - (ctx) => { - if(!ctx.message.text || ctx.message.text === 'skip') { - ctx.scene.session.data.inviteCode = null - } else { - if(!ctx.message.text || ctx.message.text.length < 6) { - return ctx.reply('邀请码不能少于 6 个字符!请重新输入') - } - ctx.scene.session.data.inviteCode = ctx.message.text - } - - ctx.reply( - `好的现在来确认一下信息:\n`+ - `用户名:${ctx.scene.session.data.playerUsername}\n`+ - `密码:${ctx.scene.session.data.password.substring(3,10)}\n`+ - `邮箱:${ctx.scene.session.data.email}\n`+ - `同步皮肤和披风:${ctx.scene.session.data.sync ? '是' : '否'}\n`+ - `邀请码:${ctx.scene.session.data.inviteCode ?? '无'}\n`+ - `是否确认? 输入 'yes' 或者 'no'` - ) - return ctx.wizard.next() - }, - async (ctx) => { - if(!ctx.message.text || ctx.message.text !== 'yes' && ctx.message.text !== 'no') { - return ctx.reply('请输入 "yes" 或者 "no"') - } - - if(ctx.message.text === 'no') { - ctx.reply("已取消注册") - return ctx.scene.leave() - } else { - let message = await ctx.reply("正在注册...") - - let skin = defaultSkin - let cape = "" - - if(ctx.scene.session.data.sync) { - try{ - message = await telegraf.telegram.editMessageText(ctx.chat.id, message.message_id, null, '获取对应正版玩家皮肤中...') - const uuid = await axios({ - method: 'GET', - url: "https://api.mojang.com/users/profiles/minecraft/" + ctx.scene.session.data.playerUsername, - }) - - if(uuid.status === 400) { - message = await telegraf.telegram.editMessageText(ctx.chat.id, message.message_id, null, '对应正版玩家不存在!已跳过皮肤获取') - } else { - const skinResponse = await axios({ - method: 'GET', - url: `https://sessionserver.mojang.com/session/minecraft/profile/${uuid.data.id}`, - responseType: 'json', - }) - - const skinData = JSON.parse(Buffer.from(skinResponse.data.properties[0].value, 'base64').toString()) - skin = skinData.textures.SKIN.url - cape = skinData.textures.CAPE?.url - - message = await telegraf.telegram.editMessageText(ctx.chat.id, message.message_id, null, '成功同步皮肤和披风') - } - } catch(err) { - server.log.info(err.toJSON()) - } - } - - let verified = false - if(ctx.scene.session.data.inviteCode) { - await telegraf.telegram.editMessageText(ctx.chat.id, message.message_id, null, message.text + '\n验证邀请码中...') - const invi = Invitation.findOne({ - token: ctx.scene.session.data.inviteCode - }) - if(invi) { - message = await telegraf.telegram.editMessageText(ctx.chat.id, message.message_id, null, message.text + '\n邀请码验证成功!邀请人: ' + invi.by) - verified = true - } else { - message = await telegraf.telegram.editMessageText(ctx.chat.id, message.message_id, null, message.text + '\n邀请码无效! 已跳过') - } - } - - const player = new Player({ - uuid: uuid("LSP-yggdrasil:" + ctx.scene.session.data.playerUsername), - username: ctx.scene.session.data.playerUsername, - password: ctx.scene.session.data.password, - email: ctx.scene.session.data.email, - textures: { - skin: skin, - cape: cape - }, - registerDate: Date.now(), - permissions: [{ node: 'login', allowed: true, duration: 0, eternal: true, startDate: Date.now(), highPriority: false }], - telegramBind: { - username: ctx.from.username, - verified: verified - } - }) - - player.save() - - message = await telegraf.telegram.editMessageText(ctx.chat.id, message.message_id, null, message.text + '\n注册成功!') - - ctx.scene.leave() - } - } -) - -export const allScenes = [ - registerWizard, -] - -const register = async () => { - registerWizard.command('cancel', (ctx) => { - ctx.reply("注册已取消") - return ctx.scene.leave() + const [invitation, v] = makeInvitation(args[0], args[1], args[2] || 'telegram') + ctx.replyWithMarkdownV2('邀请码:\n```' + invitation + '```\n\n验证码:\n```' + v + "```") }) +} - telegraf.command('register', async (ctx) => { - const username = ctx.from.username - if(!username) { - return ctx.reply("请设置 Telegram 用户名!") +const adminBan = () => { + telegraf.command('banplr', async (ctx) => { + const player = await Player.findOne({ 'binding.platform': 'telegram', 'binding.username': ctx.message.from.username }) + + if(!player || player.permissions.indexOf('admin') === -1) { + return ctx.reply('配钥匙吗?什么?你配?哦不你不配!') } - const player = await Player.findOne({ "telegramBind.username": username }) - server.log.info(player) - if(!player) { - return ctx.scene.enter('REGISTRIATION_WIZARD') - } else { - return ctx.reply(`您已绑定用户: ${player.username} (${player.uuid})`) + const args = ctx.update.message.text.split(' ').slice(1) + if(args.length < 1) { + ctx.reply('Usage: /banplr ') + return } + await Player.updateOne({ username: args[0] }, { $pull: { permissions: 'login' } }) + + ctx.reply("已枪毙玩家 " + args[0]) }) +} + +const adminRevokeBan = () => { + telegraf.command('revokeban', async (ctx) => { + const player = await Player.findOne({ 'binding.platform': 'telegram', 'binding.username': ctx.message.from.username }) + + if(!player || player.permissions.indexOf('admin') === -1) { + return ctx.reply('配钥匙吗?什么?你配?哦不你不配!') + } + + const args = ctx.update.message.text.split(' ').slice(1) + if(args.length < 1) { + ctx.reply('Usage: /revokeban ') + return + } + + await Player.updateOne({ username: args[0], permissions: { $nin: [ 'login' ] } }, { $push: { permissions: 'login' } }) + + ctx.reply("已复活玩家 " + args[0]) + }) +} + +const userCreateInvitation = () => { + telegraf.command('inviteme', async (ctx) => { + const args = ctx.update.message.text.split(' ').slice(1) + if(args.length === 0) { + ctx.reply('Usage: /inviteme ') + return + } + + if(ctx.message.chat.id === -1001780498838) { + const [i, v] = makeInvitation(ctx.message.from.username, args[0], 'telegram') + ctx.replyWithMarkdownV2('邀请码:\n```' + i + '```\n\n验证码将发送到私聊,如果您未私聊启动过bot则无法接收到消息,请 /start 后再次执行此指令。') + try { + await telegraf.telegram.sendMessage(ctx.message.from.id, '验证码:\n```' + v + "```", { parse_mode: 'Markdown' }) + } catch (_) { } + } else { + ctx.reply('您不符合自我邀请最低要求,无法获取邀请码') + } + }) +} + +/* + { + p(latform): "name", + n(ame): "name", + t(o): "email", + } +*/ +const makeInvitation = (username, platform, email) => { + const invitation = crypto.publicEncrypt(server.keys.publicKey, Buffer.from(JSON.stringify({ + p: platform, + n: username, + t: email, + }))).toString('hex') + + const v = crypto.createSign('RSA-SHA1').update(invitation).sign(server.keys.privateKey, 'hex') + return [invitation, v] } \ No newline at end of file