优化项目结构,修改注册流程,添加管理员指令
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
32f7577a07
commit
904b906b63
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
34
src/index.js
34
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}))
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 ?? "")
|
||||
}
|
||||
}
|
|
@ -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: "您的账号未验证,登录请求已禁止"
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
})))
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 <username> <email> [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 <yggdrasil-player-username>')
|
||||
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 <yggdrasil-player-username>')
|
||||
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 <email>')
|
||||
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]
|
||||
}
|
Loading…
Reference in New Issue