Commit bea6b1bc authored by nanahira's avatar nanahira

Merge branch 'master' of ../srvpro

parents a76fe295 0129e335
...@@ -80,12 +80,12 @@ class Challonge { ...@@ -80,12 +80,12 @@ class Challonge {
return false; return false;
} }
} }
// POST /v1/tournaments/${tournament_id}/participants/bulk_add.json { api_key: string, participants: { name: string }[] } returns ANY // POST /v1/tournaments/${tournament_id}/participants/bulk_add.json { api_key: string, participants: { name: string, deckbuf?: string }[] } returns ANY
async uploadParticipants(participantNames) { async uploadParticipants(participants) {
try { try {
await axios_1.default.post(`${this.config.challonge_url}/v1/tournaments/${this.config.tournament_id}/participants/bulk_add.json`, { await axios_1.default.post(`${this.config.challonge_url}/v1/tournaments/${this.config.tournament_id}/participants/bulk_add.json`, {
api_key: this.config.api_key, api_key: this.config.api_key,
participants: participantNames.map(name => ({ name })), participants,
}); });
return true; return true;
} }
......
...@@ -134,12 +134,12 @@ export class Challonge { ...@@ -134,12 +134,12 @@ export class Challonge {
} }
} }
// POST /v1/tournaments/${tournament_id}/participants/bulk_add.json { api_key: string, participants: { name: string }[] } returns ANY // POST /v1/tournaments/${tournament_id}/participants/bulk_add.json { api_key: string, participants: { name: string, deckbuf?: string }[] } returns ANY
async uploadParticipants(participantNames: string[]) { async uploadParticipants(participants: { name: string, deckbuf?: string }[]) {
try { try {
await axios.post(`${this.config.challonge_url}/v1/tournaments/${this.config.tournament_id}/participants/bulk_add.json`, { await axios.post(`${this.config.challonge_url}/v1/tournaments/${this.config.tournament_id}/participants/bulk_add.json`, {
api_key: this.config.api_key, api_key: this.config.api_key,
participants: participantNames.map(name => ({ name })), participants,
}); });
return true; return true;
} catch (e) { } catch (e) {
......
...@@ -204,9 +204,9 @@ ...@@ -204,9 +204,9 @@
"post_score": false, "post_score": false,
"get_score": false, "get_score": false,
"punish_quit_before_match": false, "punish_quit_before_match": false,
"init_post": { "match_api": {
"enabled": false, "enabled": false,
"url": "https://sapi.moecube.com:444/ygopro/match/clear", "url": "https://sapi.moecube.com:444/ygopro/match",
"accesskey": "momobako" "accesskey": "momobako"
} }
}, },
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
"invalid_password_existed": "Password invalid (Already Existed)", "invalid_password_existed": "Password invalid (Already Existed)",
"invalid_password_not_found": "Password invalid (Not Found)", "invalid_password_not_found": "Password invalid (Not Found)",
"invalid_password_action": "Password invalid (Invalid Action)", "invalid_password_action": "Password invalid (Invalid Action)",
"invalid_password_checksum": "Password incorrect (Checksum Failed) (Please re-login your account.)", "invalid_password_checksum": "Password incorrect (Checksum Failed) (Please re-login and wait for 5 minutes for fix)",
"bad_user_name": "Please enter the correct ID", "bad_user_name": "Please enter the correct ID",
"server_full": "Server is full, please try again later.", "server_full": "Server is full, please try again later.",
"too_much_connection": "Too many clients running at the moment! ", "too_much_connection": "Too many clients running at the moment! ",
...@@ -405,7 +405,7 @@ ...@@ -405,7 +405,7 @@
"invalid_password_existed": "主机密码不正确 (Already Existed)", "invalid_password_existed": "主机密码不正确 (Already Existed)",
"invalid_password_not_found": "主机密码不正确 (Not Found)", "invalid_password_not_found": "主机密码不正确 (Not Found)",
"invalid_password_action": "主机密码不正确 (Invalid Action)", "invalid_password_action": "主机密码不正确 (Invalid Action)",
"invalid_password_checksum": "主机密码不正确 (Checksum Failed) (请退出并重新登录你的账号)", "invalid_password_checksum": "主机密码不正确 (Checksum Failed) (请重新登录你的账号,等待5分钟后重进)",
"bad_user_name": "请输入正确的用户名", "bad_user_name": "请输入正确的用户名",
"server_full": "服务器已经爆满,请稍候再试", "server_full": "服务器已经爆满,请稍候再试",
"too_much_connection": "同时开启的客户端数量过多 ", "too_much_connection": "同时开启的客户端数量过多 ",
......
...@@ -39,11 +39,13 @@ ...@@ -39,11 +39,13 @@
"ygopro-deck-encode": "^1.0.14" "ygopro-deck-encode": "^1.0.14"
}, },
"devDependencies": { "devDependencies": {
"@types/async": "^3.2.25",
"@types/bunyan": "^1.8.8", "@types/bunyan": "^1.8.8",
"@types/formidable": "^3.4.6",
"@types/ip6addr": "^0.2.3", "@types/ip6addr": "^0.2.3",
"@types/lzma": "^2.3.0", "@types/lzma": "^2.3.0",
"@types/node": "^16.18.126", "@types/node": "^16.18.126",
"@types/underscore": "^1.11.4", "@types/underscore": "^1.13.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"coffeescript": "^2.7.0", "coffeescript": "^2.7.0",
"typescript": "^5.8.3" "typescript": "^5.8.3"
...@@ -95,6 +97,13 @@ ...@@ -95,6 +97,13 @@
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz",
"integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q==" "integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q=="
}, },
"node_modules/@types/async": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.25.tgz",
"integrity": "sha512-O6Th/DI18XjrL9TX8LO9F/g26qAz5vynmQqlXt/qLGrskvzCKXKc5/tATz3G2N6lM8eOf3M8/StB14FncAmocg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/bunyan": { "node_modules/@types/bunyan": {
"version": "1.8.8", "version": "1.8.8",
"resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.8.tgz", "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.8.tgz",
...@@ -104,6 +113,16 @@ ...@@ -104,6 +113,16 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/formidable": {
"version": "3.4.6",
"resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.6.tgz",
"integrity": "sha512-LI4Hk+KNsM5q7br4oMVoaWeb+gUqJpz1N8+Y2Q6Cz9cVH33ybahRKUWaRmMboVlkwSbOUGgwc/pEkS7yMSzoWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/ip6addr": { "node_modules/@types/ip6addr": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/@types/ip6addr/-/ip6addr-0.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/ip6addr/-/ip6addr-0.2.3.tgz",
...@@ -128,10 +147,11 @@ ...@@ -128,10 +147,11 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/underscore": { "node_modules/@types/underscore": {
"version": "1.11.4", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.13.0.tgz",
"integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==", "integrity": "sha512-L6LBgy1f0EFQZ+7uSA57+n2g/s4Qs5r06Vwrwn0/nuK1de+adz00NWaztRQ30aEqw5qOaWbPI8u2cGQ52lj6VA==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.3", "version": "8.5.3",
...@@ -1565,6 +1585,7 @@ ...@@ -1565,6 +1585,7 @@
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
"integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@ioredis/commands": "^1.1.1", "@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0", "cluster-key-slot": "^1.1.0",
...@@ -3750,6 +3771,12 @@ ...@@ -3750,6 +3771,12 @@
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz",
"integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q==" "integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q=="
}, },
"@types/async": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.25.tgz",
"integrity": "sha512-O6Th/DI18XjrL9TX8LO9F/g26qAz5vynmQqlXt/qLGrskvzCKXKc5/tATz3G2N6lM8eOf3M8/StB14FncAmocg==",
"dev": true
},
"@types/bunyan": { "@types/bunyan": {
"version": "1.8.8", "version": "1.8.8",
"resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.8.tgz", "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.8.tgz",
...@@ -3759,6 +3786,15 @@ ...@@ -3759,6 +3786,15 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/formidable": {
"version": "3.4.6",
"resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.6.tgz",
"integrity": "sha512-LI4Hk+KNsM5q7br4oMVoaWeb+gUqJpz1N8+Y2Q6Cz9cVH33ybahRKUWaRmMboVlkwSbOUGgwc/pEkS7yMSzoWg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/ip6addr": { "@types/ip6addr": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/@types/ip6addr/-/ip6addr-0.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/ip6addr/-/ip6addr-0.2.3.tgz",
...@@ -3781,9 +3817,9 @@ ...@@ -3781,9 +3817,9 @@
"dev": true "dev": true
}, },
"@types/underscore": { "@types/underscore": {
"version": "1.11.4", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.13.0.tgz",
"integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==", "integrity": "sha512-L6LBgy1f0EFQZ+7uSA57+n2g/s4Qs5r06Vwrwn0/nuK1de+adz00NWaztRQ30aEqw5qOaWbPI8u2cGQ52lj6VA==",
"dev": true "dev": true
}, },
"@types/ws": { "@types/ws": {
...@@ -4831,6 +4867,7 @@ ...@@ -4831,6 +4867,7 @@
"version": "5.6.1", "version": "5.6.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
"integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
"peer": true,
"requires": { "requires": {
"@ioredis/commands": "^1.1.1", "@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0", "cluster-key-slot": "^1.1.0",
......
...@@ -50,11 +50,13 @@ ...@@ -50,11 +50,13 @@
"webhook": "node ygopro-webhook.js" "webhook": "node ygopro-webhook.js"
}, },
"devDependencies": { "devDependencies": {
"@types/async": "^3.2.25",
"@types/bunyan": "^1.8.8", "@types/bunyan": "^1.8.8",
"@types/formidable": "^3.4.6",
"@types/ip6addr": "^0.2.3", "@types/ip6addr": "^0.2.3",
"@types/lzma": "^2.3.0", "@types/lzma": "^2.3.0",
"@types/node": "^16.18.126", "@types/node": "^16.18.126",
"@types/underscore": "^1.11.4", "@types/underscore": "^1.13.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"coffeescript": "^2.7.0", "coffeescript": "^2.7.0",
"typescript": "^5.8.3" "typescript": "^5.8.3"
......
###
Main script of new dashboard account system.
The account list file is stored at `./config/admin_user.json`. The users are stored at `users`.
The key is the username. The `permissions` field could be a string, using a permission set from the example, or an object, to define a specific set of permissions.
eg. An account for a judge could be as follows, to use the default permission of judges,
"username": {
"password": "123456",
"enabled": true,
"permissions": "judge"
},
or as follows, to use a specific set of permissions.
"username": {
"password": "123456",
"enabled": true,
"permissions": {
"get_rooms": true,
"duel_log": true,
"download_replay": true,
"deck_dashboard_read": true,
"deck_dashboard_write": true,
"shout": true,
"kick_user": true,
"start_death": true
}
},
###
fs = require 'fs'
loadJSON = require('load-json-file').sync
loadJSONPromise = require('load-json-file')
moment = require 'moment'
moment.updateLocale('zh-cn', {
relativeTime: {
future: '%s内',
past: '%s前',
s: '%d秒',
m: '1分钟',
mm: '%d分钟',
h: '1小时',
hh: '%d小时',
d: '1天',
dd: '%d天',
M: '1个月',
MM: '%d个月',
y: '1年',
yy: '%d年'
}
})
bunyan = require 'bunyan'
log = bunyan.createLogger name: "auth"
util = require 'util'
if not fs.existsSync('./logs')
fs.mkdirSync('./logs')
add_log = (message) ->
mt = moment()
log.info(message)
text = mt.format('YYYY-MM-DD HH:mm:ss') + " --> " + message + "\n"
res = false
try
await fs.promises.appendFile("./logs/"+mt.format('YYYY-MM-DD')+".log", text)
res = true
catch
res = false
return res
default_data = loadJSON('./data/default_data.json')
setting_save = (settings) ->
try
await fs.promises.writeFile(settings.file, JSON.stringify(settings, null, 2))
catch e
add_log("save fail");
return
default_data = loadJSON('./data/default_data.json')
try
users = loadJSON('./config/admin_user.json')
catch
users = default_data.users
setting_save(users)
save = () ->
return await setting_save(users)
reload = () ->
user_backup = users
try
users = await loadJSONPromise('./config/admin_user.json')
catch
users = user_backup
await add_log("Invalid user data JSON")
return
check_permission = (user, permission_required) ->
_permission = user.permissions
permission = _permission
if typeof(permission) != 'object'
permission = users.permission_examples[_permission]
if !permission
await add_log("Permision not set:"+_permission)
return false
return permission[permission_required]
@auth = (name, pass, permission_required, action = 'unknown', no_log) ->
await reload()
user = users.users[name]
if !user
await add_log("Unknown user login. User: "+ name+", Permission needed: "+ permission_required+", Action: " +action)
return false
if user.password != pass
await add_log("Unauthorized user login. User: "+ name+", Permission needed: "+ permission_required+", Action: " +action)
return false
if !user.enabled
await add_log("Disabled user login. User: "+ name+", Permission needed: "+ permission_required+", Action: " +action)
return false
if !await check_permission(user, permission_required)
await add_log("Permission denied. User: "+ name+", Permission needed: "+ permission_required+", Action: " +action)
return false
if !no_log
await add_log("Operation success. User: "+ name+", Permission needed: "+ permission_required+", Action: " +action)
return true
@add_user = (name, pass, enabled, permissions) ->
await reload()
if users.users[name]
return false
users.users[name] = {
"password": pass,
"enabled": enabled,
"permissions": permissions
}
await save()
return true
@delete_user = (name) ->
await reload()
if !users.users[name]
return false
delete users.users[name]
await save()
return
@update_user = (name, key, value) ->
await reload()
if !users.users[name]
return false
users.users[name][key] = value
await save()
return
// Generated by CoffeeScript 2.7.0 "use strict";
(function() { var __importDefault = (this && this.__importDefault) || function (mod) {
/* return (mod && mod.__esModule) ? mod : { "default": mod };
Main script of new dashboard account system. };
The account list file is stored at `./config/admin_user.json`. The users are stored at `users`. Object.defineProperty(exports, "__esModule", { value: true });
The key is the username. The `permissions` field could be a string, using a permission set from the example, or an object, to define a specific set of permissions. exports.update_user = exports.delete_user = exports.add_user = exports.auth = void 0;
eg. An account for a judge could be as follows, to use the default permission of judges, /*
"username": { Main script of new dashboard account system.
"password": "123456", The account list file is stored at `./config/admin_user.json`. The users are stored at `users`.
"enabled": true, The key is the username. The `permissions` field could be a string, using a permission set from the example, or an object, to define a specific set of permissions.
"permissions": "judge" eg. An account for a judge could be as follows, to use the default permission of judges,
}, "username": {
or as follows, to use a specific set of permissions. "password": "123456",
"username": { "enabled": true,
"password": "123456", "permissions": "judge"
"enabled": true, },
"permissions": { or as follows, to use a specific set of permissions.
"get_rooms": true, "username": {
"duel_log": true, "password": "123456",
"download_replay": true, "enabled": true,
"deck_dashboard_read": true, "permissions": {
"deck_dashboard_write": true, "get_rooms": true,
"shout": true, "duel_log": true,
"kick_user": true, "download_replay": true,
"start_death": true "deck_dashboard_read": true,
} "deck_dashboard_write": true,
}, "shout": true,
*/ "kick_user": true,
var add_log, bunyan, check_permission, default_data, fs, loadJSON, loadJSONPromise, log, moment, reload, save, setting_save, users, util; "start_death": true
}
fs = require('fs'); },
*/
loadJSON = require('load-json-file').sync; const fs_1 = __importDefault(require("fs"));
const load_json_file_1 = require("load-json-file");
loadJSONPromise = require('load-json-file'); const load_json_file_2 = __importDefault(require("load-json-file"));
const moment_1 = __importDefault(require("moment"));
moment = require('moment'); const bunyan_1 = __importDefault(require("bunyan"));
moment_1.default.updateLocale("zh-cn", {
moment.updateLocale('zh-cn', {
relativeTime: { relativeTime: {
future: '%s内', future: "%s内",
past: '%s前', past: "%s前",
s: '%d秒', s: "%d秒",
m: '1分钟', m: "1分钟",
mm: '%d分钟', mm: "%d分钟",
h: '1小时', h: "1小时",
hh: '%d小时', hh: "%d小时",
d: '1天', d: "1天",
dd: '%d天', dd: "%d天",
M: '1个月', M: "1个月",
MM: '%d个月', MM: "%d个月",
y: '1年', y: "1年",
yy: '%d年' yy: "%d年",
} },
}); });
const log = bunyan_1.default.createLogger({ name: "auth" });
bunyan = require('bunyan'); if (!fs_1.default.existsSync("./logs")) {
fs_1.default.mkdirSync("./logs");
log = bunyan.createLogger({ }
name: "auth" const add_log = async function (message) {
}); const mt = (0, moment_1.default)();
util = require('util');
if (!fs.existsSync('./logs')) {
fs.mkdirSync('./logs');
}
add_log = async function(message) {
var mt, res, text;
mt = moment();
log.info(message); log.info(message);
text = mt.format('YYYY-MM-DD HH:mm:ss') + " --> " + message + "\n"; const text = mt.format("YYYY-MM-DD HH:mm:ss") + " --> " + message + "\n";
res = false; let res = false;
try { try {
await fs.promises.appendFile("./logs/" + mt.format('YYYY-MM-DD') + ".log", text); await fs_1.default.promises.appendFile(`./logs/${mt.format("YYYY-MM-DD")}.log`, text);
res = true; res = true;
} catch (error) { }
res = false; catch {
res = false;
} }
return res; return res;
}; };
const default_data = (0, load_json_file_1.sync)("./data/default_data.json");
default_data = loadJSON('./data/default_data.json'); const setting_save = async function (settings) {
setting_save = async function(settings) {
var e;
try { try {
await fs.promises.writeFile(settings.file, JSON.stringify(settings, null, 2)); await fs_1.default.promises.writeFile(settings.file, JSON.stringify(settings, null, 2));
} catch (error) { }
e = error; catch (e) {
add_log("save fail"); add_log("save fail");
} }
}; };
let users;
default_data = loadJSON('./data/default_data.json'); try {
users = (0, load_json_file_1.sync)("./config/admin_user.json");
try { }
users = loadJSON('./config/admin_user.json'); catch {
} catch (error) {
users = default_data.users; users = default_data.users;
setting_save(users); setting_save(users);
} }
const save = async function () {
save = async function() { await setting_save(users);
return (await setting_save(users)); };
}; const reload = async function () {
const user_backup = users;
reload = async function() {
var user_backup;
user_backup = users;
try { try {
users = (await loadJSONPromise('./config/admin_user.json')); users = (await (0, load_json_file_2.default)("./config/admin_user.json"));
} catch (error) { }
users = user_backup; catch {
await add_log("Invalid user data JSON"); users = user_backup;
} await add_log("Invalid user data JSON");
}; }
};
check_permission = async function(user, permission_required) { const check_permission = async function (user, permission_required) {
var _permission, permission; const _permission = user.permissions;
_permission = user.permissions; let permission;
permission = _permission; if (typeof _permission !== "object") {
if (typeof permission !== 'object') { permission = users.permission_examples[_permission];
permission = users.permission_examples[_permission]; }
else {
permission = _permission;
} }
if (!permission) { if (!permission) {
await add_log("Permision not set:" + _permission); await add_log("Permision not set:" + String(_permission));
return false; return false;
} }
return permission[permission_required]; return Boolean(permission[permission_required]);
}; };
const auth = async function (name, pass, permission_required, action = "unknown", no_log) {
this.auth = async function(name, pass, permission_required, action = 'unknown', no_log) {
var user;
await reload(); await reload();
user = users.users[name]; const user = users.users[name];
if (!user) { if (!user) {
await add_log("Unknown user login. User: " + name + ", Permission needed: " + permission_required + ", Action: " + action); await add_log("Unknown user login. User: " +
return false; name +
", Permission needed: " +
permission_required +
", Action: " +
action);
return false;
} }
if (user.password !== pass) { if (user.password !== pass) {
await add_log("Unauthorized user login. User: " + name + ", Permission needed: " + permission_required + ", Action: " + action); await add_log("Unauthorized user login. User: " +
return false; name +
", Permission needed: " +
permission_required +
", Action: " +
action);
return false;
} }
if (!user.enabled) { if (!user.enabled) {
await add_log("Disabled user login. User: " + name + ", Permission needed: " + permission_required + ", Action: " + action); await add_log("Disabled user login. User: " +
return false; name +
", Permission needed: " +
permission_required +
", Action: " +
action);
return false;
} }
if (!(await check_permission(user, permission_required))) { if (!(await check_permission(user, permission_required))) {
await add_log("Permission denied. User: " + name + ", Permission needed: " + permission_required + ", Action: " + action); await add_log("Permission denied. User: " +
return false; name +
", Permission needed: " +
permission_required +
", Action: " +
action);
return false;
} }
if (!no_log) { if (!no_log) {
await add_log("Operation success. User: " + name + ", Permission needed: " + permission_required + ", Action: " + action); await add_log("Operation success. User: " +
name +
", Permission needed: " +
permission_required +
", Action: " +
action);
} }
return true; return true;
}; };
exports.auth = auth;
this.add_user = async function(name, pass, enabled, permissions) { const add_user = async function (name, pass, enabled, permissions) {
await reload(); await reload();
if (users.users[name]) { if (users.users[name]) {
return false; return false;
} }
users.users[name] = { users.users[name] = {
"password": pass, password: pass,
"enabled": enabled, enabled: enabled,
"permissions": permissions permissions: permissions,
}; };
await save(); await save();
return true; return true;
}; };
exports.add_user = add_user;
this.delete_user = async function(name) { const delete_user = async function (name) {
await reload(); await reload();
if (!users.users[name]) { if (!users.users[name]) {
return false; return;
} }
delete users.users[name]; delete users.users[name];
await save(); await save();
}; };
exports.delete_user = delete_user;
this.update_user = async function(name, key, value) { const update_user = async function (name, key, value) {
await reload(); await reload();
if (!users.users[name]) { if (!users.users[name]) {
return false; return;
} }
users.users[name][key] = value; users.users[name][key] = value;
await save(); await save();
}; };
exports.update_user = update_user;
}).call(this);
/*
Main script of new dashboard account system.
The account list file is stored at `./config/admin_user.json`. The users are stored at `users`.
The key is the username. The `permissions` field could be a string, using a permission set from the example, or an object, to define a specific set of permissions.
eg. An account for a judge could be as follows, to use the default permission of judges,
"username": {
"password": "123456",
"enabled": true,
"permissions": "judge"
},
or as follows, to use a specific set of permissions.
"username": {
"password": "123456",
"enabled": true,
"permissions": {
"get_rooms": true,
"duel_log": true,
"download_replay": true,
"deck_dashboard_read": true,
"deck_dashboard_write": true,
"shout": true,
"kick_user": true,
"start_death": true
}
},
*/
import fs from "fs";
import { sync as loadJSON } from "load-json-file";
import loadJSONPromise from "load-json-file";
import moment from "moment";
import bunyan from "bunyan";
import util from "util";
moment.updateLocale("zh-cn", {
relativeTime: {
future: "%s内",
past: "%s前",
s: "%d秒",
m: "1分钟",
mm: "%d分钟",
h: "1小时",
hh: "%d小时",
d: "1天",
dd: "%d天",
M: "1个月",
MM: "%d个月",
y: "1年",
yy: "%d年",
},
});
const log = bunyan.createLogger({ name: "auth" });
if (!fs.existsSync("./logs")) {
fs.mkdirSync("./logs");
}
type PermissionSet = Record<string, boolean>;
type UserPermissions = string | PermissionSet;
interface UserEntry {
password: string;
enabled: boolean;
permissions: UserPermissions;
[key: string]: any;
}
interface UsersFile {
file?: string;
permission_examples: Record<string, PermissionSet>;
users: Record<string, UserEntry>;
}
const add_log = async function (message: string): Promise<boolean> {
const mt = moment();
log.info(message);
const text = mt.format("YYYY-MM-DD HH:mm:ss") + " --> " + message + "\n";
let res = false;
try {
await fs.promises.appendFile(`./logs/${mt.format("YYYY-MM-DD")}.log`, text);
res = true;
} catch {
res = false;
}
return res;
};
const default_data = loadJSON("./data/default_data.json") as {
users: UsersFile;
};
const setting_save = async function (settings: UsersFile): Promise<void> {
try {
await fs.promises.writeFile(settings.file as string, JSON.stringify(settings, null, 2));
} catch (e) {
add_log("save fail");
}
};
let users: UsersFile;
try {
users = loadJSON("./config/admin_user.json") as UsersFile;
} catch {
users = default_data.users;
setting_save(users);
}
const save = async function (): Promise<void> {
await setting_save(users);
};
const reload = async function (): Promise<void> {
const user_backup = users;
try {
users = (await loadJSONPromise("./config/admin_user.json")) as UsersFile;
} catch {
users = user_backup;
await add_log("Invalid user data JSON");
}
};
const check_permission = async function (
user: UserEntry,
permission_required: string
): Promise<boolean> {
const _permission = user.permissions;
let permission: PermissionSet | undefined;
if (typeof _permission !== "object") {
permission = users.permission_examples[_permission];
} else {
permission = _permission;
}
if (!permission) {
await add_log("Permision not set:" + String(_permission));
return false;
}
return Boolean(permission[permission_required]);
};
export const auth = async function (
name: string,
pass: string,
permission_required: string,
action = "unknown",
no_log?: boolean
): Promise<boolean> {
await reload();
const user = users.users[name];
if (!user) {
await add_log(
"Unknown user login. User: " +
name +
", Permission needed: " +
permission_required +
", Action: " +
action
);
return false;
}
if (user.password !== pass) {
await add_log(
"Unauthorized user login. User: " +
name +
", Permission needed: " +
permission_required +
", Action: " +
action
);
return false;
}
if (!user.enabled) {
await add_log(
"Disabled user login. User: " +
name +
", Permission needed: " +
permission_required +
", Action: " +
action
);
return false;
}
if (!(await check_permission(user, permission_required))) {
await add_log(
"Permission denied. User: " +
name +
", Permission needed: " +
permission_required +
", Action: " +
action
);
return false;
}
if (!no_log) {
await add_log(
"Operation success. User: " +
name +
", Permission needed: " +
permission_required +
", Action: " +
action
);
}
return true;
};
export const add_user = async function (
name: string,
pass: string,
enabled: boolean,
permissions: UserPermissions
): Promise<boolean> {
await reload();
if (users.users[name]) {
return false;
}
users.users[name] = {
password: pass,
enabled: enabled,
permissions: permissions,
};
await save();
return true;
};
export const delete_user = async function (name: string): Promise<void> {
await reload();
if (!users.users[name]) {
return;
}
delete users.users[name];
await save();
};
export const update_user = async function (
name: string,
key: string,
value: unknown
): Promise<void> {
await reload();
if (!users.users[name]) {
return;
}
users.users[name][key] = value;
await save();
};
...@@ -500,6 +500,25 @@ var packDatas = function (callback) { ...@@ -500,6 +500,25 @@ var packDatas = function (callback) {
//建立一个http服务器,接收API操作 //建立一个http服务器,接收API操作
async function requestListener(req, res) { async function requestListener(req, res) {
var u = url.parse(req.url, true); var u = url.parse(req.url, true);
// Allow all CORS + PNA (Private Network Access) requests.
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Private-Network", "true");
res.setHeader("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
if ((req.method || "").toLowerCase() === "options") {
const requestHeaders = req.headers["access-control-request-headers"];
res.writeHead(204, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
"Access-Control-Allow-Headers": Array.isArray(requestHeaders)
? requestHeaders.join(", ")
: requestHeaders || "*",
"Access-Control-Allow-Private-Network": "true",
"Access-Control-Max-Age": "86400"
});
res.end();
return;
}
if (!await auth.auth(u.query.username, u.query.password, "pre_dashboard", "pre_dashboard")) { if (!await auth.auth(u.query.username, u.query.password, "pre_dashboard", "pre_dashboard")) {
res.writeHead(403); res.writeHead(403);
......
# 标准库 # 标准库
net = require 'net' net = require 'net'
http = require 'http' http = require 'http'
url = require 'url'
path = require 'path' path = require 'path'
fs = require 'fs' fs = require 'fs'
os = require 'os' os = require 'os'
...@@ -296,7 +295,27 @@ loadLFList = (path) -> ...@@ -296,7 +295,27 @@ loadLFList = (path) ->
lflists.push({date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"), tcg: list.indexOf('TCG') != -1}) lflists.push({date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"), tcg: list.indexOf('TCG') != -1})
catch catch
init = () -> call_match_api = (method, path, params) ->
if not settings.modules.arena_mode.match_api.enabled
return null
match_api_url = new URL(settings.modules.arena_mode.match_api.url + "/" + path)
match_api_url.searchParams.append('ak', settings.modules.arena_mode.match_api.accesskey)
for entry in Object.entries(params)
key = entry[0]
val = entry[1]
match_api_url.searchParams.append(key, val)
try
res = await axios({
method: method
url: match_api_url.toString()
timeout: 30000
})
return res.data
catch e
log.warn 'MATCH API CALL ERROR', method, path, JSON.stringify(params), e.toString()
return null
init = () ->
log.info('Reading config.') log.info('Reading config.')
await createDirectoryIfNotExists("./config") await createDirectoryIfNotExists("./config")
await importOldConfig() await importOldConfig()
...@@ -420,6 +439,13 @@ loadLFList = (path) -> ...@@ -420,6 +439,13 @@ loadLFList = (path) ->
settings.modules.trusted_proxies = settings.modules.neos.trusted_proxies settings.modules.trusted_proxies = settings.modules.neos.trusted_proxies
delete settings.modules.neos.trusted_proxies delete settings.modules.neos.trusted_proxies
imported = true imported = true
# migrate arena_mode.init_post to match_api
if settings.modules.arena_mode.init_post
settings.modules.arena_mode.match_api = settings.modules.arena_mode.init_post
if settings.modules.arena_mode.match_api.url.endsWith('/clear')
settings.modules.arena_mode.match_api.url = settings.modules.arena_mode.match_api.url.slice(0, -6)
delete settings.modules.arena_mode.init_post
imported = true
#finish #finish
keysFromEnv = Object.keys(process.env).filter((key) => key.startsWith('SRVPRO_')) keysFromEnv = Object.keys(process.env).filter((key) => key.startsWith('SRVPRO_'))
if keysFromEnv.length > 0 if keysFromEnv.length > 0
...@@ -591,16 +617,8 @@ loadLFList = (path) -> ...@@ -591,16 +617,8 @@ loadLFList = (path) ->
# pg_client.on 'drain', pg_client.end.bind(pg_client) # pg_client.on 'drain', pg_client.end.bind(pg_client)
# log.info "loading mycard user..." # log.info "loading mycard user..."
# pg_client.connect() # pg_client.connect()
if settings.modules.arena_mode.enabled and settings.modules.arena_mode.init_post.enabled if settings.modules.arena_mode.enabled
postData = qs.stringify({ await call_match_api('POST', 'clear', {arena: settings.modules.arena_mode.mode})
ak: settings.modules.arena_mode.init_post.accesskey,
arena: settings.modules.arena_mode.mode
})
try
log.info("Sending arena init post.")
await axios.post(settings.modules.arena_mode.init_post.url + "?" + postData)
catch e
log.warn 'ARENA INIT POST ERROR', e
if settings.modules.challonge.enabled if settings.modules.challonge.enabled
Challonge = require('./challonge').Challonge Challonge = require('./challonge').Challonge
...@@ -1535,9 +1553,6 @@ class Room ...@@ -1535,9 +1553,6 @@ class Room
lflist = parseInt(param[3]) - 1 lflist = parseInt(param[3]) - 1
@hostinfo.lflist = lflist @hostinfo.lflist = lflist
for extra_mode_func from extra_mode_list
extra_mode_func.call this, rule
if (rule.match /(^|,|,)(NOLFLIST|NF)(,|,|$)/) if (rule.match /(^|,|,)(NOLFLIST|NF)(,|,|$)/)
@hostinfo.lflist = -1 @hostinfo.lflist = -1
...@@ -1586,6 +1601,11 @@ class Room ...@@ -1586,6 +1601,11 @@ class Room
@recover_buffers = [[], [], [], []] @recover_buffers = [[], [], [], []]
@welcome = "${recover_hint}" @welcome = "${recover_hint}"
param = name.match /(.+)#/
rule = if param then param[1].toUpperCase() else ''
for extra_mode_func from extra_mode_list
extra_mode_func.call this, rule, name
@hostinfo.replay_mode = 0 @hostinfo.replay_mode = 0
if settings.modules.tournament_mode.enabled # 0x1: Save the replays in file if settings.modules.tournament_mode.enabled # 0x1: Save the replays in file
...@@ -2628,6 +2648,11 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)-> ...@@ -2628,6 +2648,11 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)->
room.welcome = "${athletic_arena_tip}" room.welcome = "${athletic_arena_tip}"
else else
room.welcome = "${entertain_arena_tip}" room.welcome = "${entertain_arena_tip}"
await call_match_api('POST', 'player-joined', {
username: client.name,
arena: room.arena,
roomname: room.name
})
when 5 when 5
title = info.pass.slice(8).replace(String.fromCharCode(0xFEFF), ' ') title = info.pass.slice(8).replace(String.fromCharCode(0xFEFF), ' ')
room = ROOM_find_by_title(title) room = ROOM_find_by_title(title)
...@@ -2678,8 +2703,8 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)-> ...@@ -2678,8 +2703,8 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)->
# users_cache[client.name] = userData.user.id # users_cache[client.name] = userData.user.id
possible_ids = [ possible_ids = [
userData.user.u16Secret, userData.user.u16Secret,
userData.user.u16SecretPrevious, userData.user.u16SecretPrevious
userData.user.id, # TODO: remove this line after use u16Secret # userData.user.id,
].filter((id) -> id != null) ].filter((id) -> id != null)
try_decrypt_buffer_with_id = (id) -> try_decrypt_buffer_with_id = (id) ->
secret = id % 65535 + 1 secret = id % 65535 + 1
...@@ -3390,7 +3415,7 @@ load_tips = global.load_tips = ()-> ...@@ -3390,7 +3415,7 @@ load_tips = global.load_tips = ()->
load_tips_zh = global.load_tips_zh = ()-> load_tips_zh = global.load_tips_zh = ()->
return await loadRemoteData(tips, "tips_zh", settings.modules.tips.get_zh) return await loadRemoteData(tips, "tips_zh", settings.modules.tips.get_zh)
ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server, datas)-> ygopro.stoc_follow 'DUEL_START', true, (buffer, info, client, server, datas)->
room=ROOM_all[client.rid] room=ROOM_all[client.rid]
return unless room and !client.reconnecting return unless room and !client.reconnecting
if room.duel_stage == ygopro.constants.DUEL_STAGE.BEGIN #first start if room.duel_stage == ygopro.constants.DUEL_STAGE.BEGIN #first start
...@@ -3400,7 +3425,8 @@ ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server, datas)-> ...@@ -3400,7 +3425,8 @@ ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server, datas)->
roomlist.start room if !room.windbot and settings.modules.http.websocket_roomlist roomlist.start room if !room.windbot and settings.modules.http.websocket_roomlist
#room.duels = [] #room.duels = []
room.dueling_players = [] room.dueling_players = []
for player in room.get_playing_player() playing_players = room.get_playing_player()
for player in playing_players
room.dueling_players[player.pos] = player room.dueling_players[player.pos] = player
room.scores[player.name_vpass] = 0 room.scores[player.name_vpass] = 0
room.player_datas.push key: CLIENT_get_authorize_key(player), name: player.name, pos: player.pos room.player_datas.push key: CLIENT_get_authorize_key(player), name: player.name, pos: player.pos
...@@ -3409,6 +3435,14 @@ ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server, datas)-> ...@@ -3409,6 +3435,14 @@ ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server, datas)->
ROOM_players_oppentlist[player.ip] = null ROOM_players_oppentlist[player.ip] = null
if room.hostinfo.auto_death if room.hostinfo.auto_death
ygopro.stoc_send_chat_to_room(room, "${auto_death_part1}#{room.hostinfo.auto_death}${auto_death_part2}", ygopro.constants.COLORS.BABYBLUE) ygopro.stoc_send_chat_to_room(room, "${auto_death_part1}#{room.hostinfo.auto_death}${auto_death_part2}", ygopro.constants.COLORS.BABYBLUE)
if room.arena
await call_match_api('POST', 'room-start', {
usernameA: playing_players[0].name,
usernameB: playing_players[1].name,
roomname: room.name,
starttime: room.start_time,
arena: room.arena
})
else if room.duel_stage == ygopro.constants.DUEL_STAGE.SIDING and client.pos < 4 # side deck verified else if room.duel_stage == ygopro.constants.DUEL_STAGE.SIDING and client.pos < 4 # side deck verified
client.selected_preduel = true client.selected_preduel = true
if client.side_tcount if client.side_tcount
...@@ -4218,9 +4252,31 @@ if true ...@@ -4218,9 +4252,31 @@ if true
httpRequestListener = (request, response)-> httpRequestListener = (request, response)->
parseQueryString = true parseQueryString = true
u = url.parse(request.url, parseQueryString) base = "http://#{request.headers.host or 'localhost'}"
urlObj = new URL(request.url, base)
u =
pathname: urlObj.pathname
query: Object.fromEntries(urlObj.searchParams)
#pass_validated = u.query.pass == settings.modules.http.password #pass_validated = u.query.pass == settings.modules.http.password
# Allow all CORS + PNA (Private Network Access) requests.
response.setHeader "Access-Control-Allow-Origin", "*"
response.setHeader "Access-Control-Allow-Private-Network", "true"
response.setHeader "Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method"
if (request.method or "").toLowerCase() == "options"
requestHeaders = request.headers["access-control-request-headers"]
allowHeaders = if Array.isArray(requestHeaders) then requestHeaders.join(", ") else (requestHeaders or "*")
response.writeHead(204, {
"Access-Control-Allow-Origin": "*"
"Access-Control-Allow-Methods": "GET,POST,OPTIONS"
"Access-Control-Allow-Headers": allowHeaders
"Access-Control-Allow-Private-Network": "true"
"Access-Control-Max-Age": "86400"
})
response.end()
return
#console.log(u.query.username, u.query.pass) #console.log(u.query.username, u.query.pass)
if u.pathname == '/api/getrooms' if u.pathname == '/api/getrooms'
pass_validated = await auth.auth(u.query.username, u.query.pass, "get_rooms", "get_rooms", true) pass_validated = await auth.auth(u.query.username, u.query.pass, "get_rooms", "get_rooms", true)
......
// Generated by CoffeeScript 2.7.0 // Generated by CoffeeScript 2.7.0
(function() { (function() {
// 标准库 // 标准库
var Aragami, CLIENT_check_vip, CLIENT_get_absolute_pos, CLIENT_get_authorize_key, CLIENT_get_kick_reconnect_target, CLIENT_get_partner, CLIENT_get_save_data, CLIENT_heartbeat_register, CLIENT_heartbeat_unregister, CLIENT_import_data, CLIENT_is_able_to_kick_reconnect, CLIENT_is_able_to_reconnect, CLIENT_is_banned_by_mc, CLIENT_is_player, CLIENT_kick, CLIENT_kick_reconnect, CLIENT_pre_reconnect, CLIENT_reconnect, CLIENT_reconnect_register, CLIENT_reconnect_unregister, CLIENT_send_pre_reconnect_info, CLIENT_send_reconnect_info, CLIENT_send_replays, CLIENT_send_replays_and_kick, CLIENT_send_vip_status, CLIENT_set_ip, CLIENT_use_cdkey, PQueue, Q, ROOM_all, ROOM_bad_ip, ROOM_ban_player, ROOM_clear_disconnect, ROOM_connected_ip, ROOM_find_by_name, ROOM_find_by_pid, ROOM_find_by_port, ROOM_find_by_title, ROOM_find_or_create_ai, ROOM_find_or_create_by_name, ROOM_find_or_create_random, ROOM_kick, ROOM_player_flee, ROOM_player_get_score, ROOM_player_lose, ROOM_player_win, ROOM_players_oppentlist, ROOM_unwelcome, ROOM_validate, ReplayParser, ResolveData, Room, SERVER_clear_disconnect, SERVER_kick, SOCKET_flush_data, VIP_generate_cdkeys, YGOProDeck, _, _async, addCallback, aragami, aragami_classes, athleticChecker, auth, axios, badwordR, badwords, ban_user, bunyan, challonge, checkFileExists, concat_name, createDirectoryIfNotExists, crypto, dataManager, deck_name_match, dialogues, disconnect_list, exec, execFile, extra_mode_list, fs, geoip, getDuelLogQueryFromQs, getRealIp, get_memory_usage, gpt_tokenizer, http, httpRequestListener, importOldConfig, import_datas, init, ip6addr, isTrustedProxy, lflists, loadJSON, loadJSONAsync, loadLFList, loadRemoteData, load_dialogues, load_dialogues_custom, load_tips, load_tips_zh, load_words, log, long_resolve_cards, memory_usage, merge, moment, moment_long_ago_string, moment_now, moment_now_string, msg_polyfill, mustache, neosRequestListener, net, netRequestHandler, os, osu, path, qs, real_windbot_server_ip, release_disconnect, report_to_big_brother, request, roomlist, rooms_count, setting_change, setting_get, setting_save, settings, spawn, spawnSync, spawn_windbot, tips, toIpv4, toIpv6, url, util, utility, wait_room_start, wait_room_start_arena, windbot_looplimit, windbot_process, windbots, words, ygopro, zlib; var Aragami, CLIENT_check_vip, CLIENT_get_absolute_pos, CLIENT_get_authorize_key, CLIENT_get_kick_reconnect_target, CLIENT_get_partner, CLIENT_get_save_data, CLIENT_heartbeat_register, CLIENT_heartbeat_unregister, CLIENT_import_data, CLIENT_is_able_to_kick_reconnect, CLIENT_is_able_to_reconnect, CLIENT_is_banned_by_mc, CLIENT_is_player, CLIENT_kick, CLIENT_kick_reconnect, CLIENT_pre_reconnect, CLIENT_reconnect, CLIENT_reconnect_register, CLIENT_reconnect_unregister, CLIENT_send_pre_reconnect_info, CLIENT_send_reconnect_info, CLIENT_send_replays, CLIENT_send_replays_and_kick, CLIENT_send_vip_status, CLIENT_set_ip, CLIENT_use_cdkey, PQueue, Q, ROOM_all, ROOM_bad_ip, ROOM_ban_player, ROOM_clear_disconnect, ROOM_connected_ip, ROOM_find_by_name, ROOM_find_by_pid, ROOM_find_by_port, ROOM_find_by_title, ROOM_find_or_create_ai, ROOM_find_or_create_by_name, ROOM_find_or_create_random, ROOM_kick, ROOM_player_flee, ROOM_player_get_score, ROOM_player_lose, ROOM_player_win, ROOM_players_oppentlist, ROOM_unwelcome, ROOM_validate, ReplayParser, ResolveData, Room, SERVER_clear_disconnect, SERVER_kick, SOCKET_flush_data, VIP_generate_cdkeys, YGOProDeck, _, _async, addCallback, aragami, aragami_classes, athleticChecker, auth, axios, badwordR, badwords, ban_user, bunyan, call_match_api, challonge, checkFileExists, concat_name, createDirectoryIfNotExists, crypto, dataManager, deck_name_match, dialogues, disconnect_list, exec, execFile, extra_mode_list, fs, geoip, getDuelLogQueryFromQs, getRealIp, get_memory_usage, gpt_tokenizer, http, httpRequestListener, importOldConfig, import_datas, init, ip6addr, isTrustedProxy, lflists, loadJSON, loadJSONAsync, loadLFList, loadRemoteData, load_dialogues, load_dialogues_custom, load_tips, load_tips_zh, load_words, log, long_resolve_cards, memory_usage, merge, moment, moment_long_ago_string, moment_now, moment_now_string, msg_polyfill, mustache, neosRequestListener, net, netRequestHandler, os, osu, path, qs, real_windbot_server_ip, release_disconnect, report_to_big_brother, request, roomlist, rooms_count, setting_change, setting_get, setting_save, settings, spawn, spawnSync, spawn_windbot, tips, toIpv4, toIpv6, util, utility, wait_room_start, wait_room_start_arena, windbot_looplimit, windbot_process, windbots, words, ygopro, zlib;
net = require('net'); net = require('net');
http = require('http'); http = require('http');
url = require('url');
path = require('path'); path = require('path');
fs = require('fs'); fs = require('fs');
...@@ -389,8 +387,36 @@ ...@@ -389,8 +387,36 @@
} }
}; };
call_match_api = async function(method, path, params) {
var e, entry, j, key, len, match_api_url, ref, res, val;
if (!settings.modules.arena_mode.match_api.enabled) {
return null;
}
match_api_url = new URL(settings.modules.arena_mode.match_api.url + "/" + path);
match_api_url.searchParams.append('ak', settings.modules.arena_mode.match_api.accesskey);
ref = Object.entries(params);
for (j = 0, len = ref.length; j < len; j++) {
entry = ref[j];
key = entry[0];
val = entry[1];
match_api_url.searchParams.append(key, val);
}
try {
res = (await axios({
method: method,
url: match_api_url.toString(),
timeout: 30000
}));
return res.data;
} catch (error1) {
e = error1;
log.warn('MATCH API CALL ERROR', method, path, JSON.stringify(params), e.toString());
return null;
}
};
init = async function() { init = async function() {
var AthleticChecker, Challonge, DataManager, chat_color, config, cppversion, defaultConfig, default_data, dirPath, dns, e, expansions, get_rooms_count, http_server, https, httpsOptions, https_server, imported, j, key, keysFromEnv, l, len, len1, len2, len3, m, main_http_server, mkdirList, n, neosHttpServer, neosWsServer, plugin_filename, plugin_list, plugin_path, postData, ref, settingKey, val, valFromDefault, vip_info, ws; var AthleticChecker, Challonge, DataManager, chat_color, config, cppversion, defaultConfig, default_data, dirPath, dns, e, expansions, get_rooms_count, http_server, https, httpsOptions, https_server, imported, j, key, keysFromEnv, l, len, len1, len2, len3, m, main_http_server, mkdirList, n, neosHttpServer, neosWsServer, plugin_filename, plugin_list, plugin_path, ref, settingKey, val, valFromDefault, vip_info, ws;
log.info('Reading config.'); log.info('Reading config.');
await createDirectoryIfNotExists("./config"); await createDirectoryIfNotExists("./config");
await importOldConfig(); await importOldConfig();
...@@ -538,6 +564,15 @@ ...@@ -538,6 +564,15 @@
delete settings.modules.neos.trusted_proxies; delete settings.modules.neos.trusted_proxies;
imported = true; imported = true;
} }
// migrate arena_mode.init_post to match_api
if (settings.modules.arena_mode.init_post) {
settings.modules.arena_mode.match_api = settings.modules.arena_mode.init_post;
if (settings.modules.arena_mode.match_api.url.endsWith('/clear')) {
settings.modules.arena_mode.match_api.url = settings.modules.arena_mode.match_api.url.slice(0, -6);
}
delete settings.modules.arena_mode.init_post;
imported = true;
}
//finish //finish
keysFromEnv = Object.keys(process.env).filter((key) => { keysFromEnv = Object.keys(process.env).filter((key) => {
return key.startsWith('SRVPRO_'); return key.startsWith('SRVPRO_');
...@@ -742,18 +777,10 @@ ...@@ -742,18 +777,10 @@
// pg_client.on 'drain', pg_client.end.bind(pg_client) // pg_client.on 'drain', pg_client.end.bind(pg_client)
// log.info "loading mycard user..." // log.info "loading mycard user..."
// pg_client.connect() // pg_client.connect()
if (settings.modules.arena_mode.enabled && settings.modules.arena_mode.init_post.enabled) { if (settings.modules.arena_mode.enabled) {
postData = qs.stringify({ await call_match_api('POST', 'clear', {
ak: settings.modules.arena_mode.init_post.accesskey,
arena: settings.modules.arena_mode.mode arena: settings.modules.arena_mode.mode
}); });
try {
log.info("Sending arena init post.");
await axios.post(settings.modules.arena_mode.init_post.url + "?" + postData);
} catch (error1) {
e = error1;
log.warn('ARENA INIT POST ERROR', e);
}
} }
} }
if (settings.modules.challonge.enabled) { if (settings.modules.challonge.enabled) {
...@@ -1990,9 +2017,6 @@ ...@@ -1990,9 +2017,6 @@
lflist = parseInt(param[3]) - 1; lflist = parseInt(param[3]) - 1;
this.hostinfo.lflist = lflist; this.hostinfo.lflist = lflist;
} }
for (extra_mode_func of extra_mode_list) {
extra_mode_func.call(this, rule);
}
if (rule.match(/(^|,|,)(NOLFLIST|NF)(,|,|$)/)) { if (rule.match(/(^|,|,)(NOLFLIST|NF)(,|,|$)/)) {
this.hostinfo.lflist = -1; this.hostinfo.lflist = -1;
} }
...@@ -2044,6 +2068,11 @@ ...@@ -2044,6 +2068,11 @@
this.welcome = "${recover_hint}"; this.welcome = "${recover_hint}";
} }
} }
param = name.match(/(.+)#/);
rule = param ? param[1].toUpperCase() : '';
for (extra_mode_func of extra_mode_list) {
extra_mode_func.call(this, rule, name);
}
this.hostinfo.replay_mode = 0; this.hostinfo.replay_mode = 0;
if (settings.modules.tournament_mode.enabled) { // 0x1: Save the replays in file if (settings.modules.tournament_mode.enabled) { // 0x1: Save the replays in file
this.hostinfo.replay_mode |= 0x1; this.hostinfo.replay_mode |= 0x1;
...@@ -3435,6 +3464,11 @@ ...@@ -3435,6 +3464,11 @@
} else { } else {
room.welcome = "${entertain_arena_tip}"; room.welcome = "${entertain_arena_tip}";
} }
await call_match_api('POST', 'player-joined', {
username: client.name,
arena: room.arena,
roomname: room.name
});
} }
break; break;
case 5: case 5:
...@@ -3491,11 +3525,8 @@ ...@@ -3491,11 +3525,8 @@
return; return;
} }
// users_cache[client.name] = userData.user.id // users_cache[client.name] = userData.user.id
possible_ids = [ // userData.user.id,
userData.user.u16Secret, possible_ids = [userData.user.u16Secret, userData.user.u16SecretPrevious].filter(function(id) {
userData.user.u16SecretPrevious,
userData.user.id // TODO: remove this line after use u16Secret
].filter(function(id) {
return id !== null; return id !== null;
}); });
try_decrypt_buffer_with_id = function(id) { try_decrypt_buffer_with_id = function(id) {
...@@ -4499,8 +4530,8 @@ ...@@ -4499,8 +4530,8 @@
return (await loadRemoteData(tips, "tips_zh", settings.modules.tips.get_zh)); return (await loadRemoteData(tips, "tips_zh", settings.modules.tips.get_zh));
}; };
ygopro.stoc_follow('DUEL_START', false, async function(buffer, info, client, server, datas) { ygopro.stoc_follow('DUEL_START', true, async function(buffer, info, client, server, datas) {
var deck_arena, deck_name, deck_text, j, l, len, len1, player, ref, ref1, room; var deck_arena, deck_name, deck_text, j, l, len, len1, player, playing_players, ref, room;
room = ROOM_all[client.rid]; room = ROOM_all[client.rid];
if (!(room && !client.reconnecting)) { if (!(room && !client.reconnecting)) {
return; return;
...@@ -4514,9 +4545,9 @@ ...@@ -4514,9 +4545,9 @@
} }
//room.duels = [] //room.duels = []
room.dueling_players = []; room.dueling_players = [];
ref = room.get_playing_player(); playing_players = room.get_playing_player();
for (j = 0, len = ref.length; j < len; j++) { for (j = 0, len = playing_players.length; j < len; j++) {
player = ref[j]; player = playing_players[j];
room.dueling_players[player.pos] = player; room.dueling_players[player.pos] = player;
room.scores[player.name_vpass] = 0; room.scores[player.name_vpass] = 0;
room.player_datas.push({ room.player_datas.push({
...@@ -4532,6 +4563,15 @@ ...@@ -4532,6 +4563,15 @@
if (room.hostinfo.auto_death) { if (room.hostinfo.auto_death) {
ygopro.stoc_send_chat_to_room(room, `\${auto_death_part1}${room.hostinfo.auto_death}\${auto_death_part2}`, ygopro.constants.COLORS.BABYBLUE); ygopro.stoc_send_chat_to_room(room, `\${auto_death_part1}${room.hostinfo.auto_death}\${auto_death_part2}`, ygopro.constants.COLORS.BABYBLUE);
} }
if (room.arena) {
await call_match_api('POST', 'room-start', {
usernameA: playing_players[0].name,
usernameB: playing_players[1].name,
roomname: room.name,
starttime: room.start_time,
arena: room.arena
});
}
} else if (room.duel_stage === ygopro.constants.DUEL_STAGE.SIDING && client.pos < 4) { // side deck verified } else if (room.duel_stage === ygopro.constants.DUEL_STAGE.SIDING && client.pos < 4) { // side deck verified
client.selected_preduel = true; client.selected_preduel = true;
if (client.side_tcount) { if (client.side_tcount) {
...@@ -4541,9 +4581,9 @@ ...@@ -4541,9 +4581,9 @@
} }
} }
if (settings.modules.hide_name === "start" && room.duel_count === 0) { if (settings.modules.hide_name === "start" && room.duel_count === 0) {
ref1 = room.get_playing_player(); ref = room.get_playing_player();
for (l = 0, len1 = ref1.length; l < len1; l++) { for (l = 0, len1 = ref.length; l < len1; l++) {
player = ref1[l]; player = ref[l];
if (player !== client) { if (player !== client) {
ygopro.stoc_send(client, 'HS_PLAYER_ENTER', { ygopro.stoc_send(client, 'HS_PLAYER_ENTER', {
name: player.name, name: player.name,
...@@ -5658,11 +5698,33 @@ ...@@ -5658,11 +5698,33 @@
return callback + "( " + text + " );"; return callback + "( " + text + " );";
}; };
httpRequestListener = async function(request, response) { httpRequestListener = async function(request, response) {
var archiveStream, buffer, death_room_found, duellog, e, err, error, filename, getpath, parseQueryString, pass_validated, ret_keys, roomsjson, success, u; var allowHeaders, archiveStream, base, buffer, death_room_found, duellog, e, err, error, filename, getpath, parseQueryString, pass_validated, requestHeaders, ret_keys, roomsjson, success, u, urlObj;
parseQueryString = true; parseQueryString = true;
u = url.parse(request.url, parseQueryString); base = `http://${request.headers.host || 'localhost'}`;
urlObj = new URL(request.url, base);
u = {
pathname: urlObj.pathname,
query: Object.fromEntries(urlObj.searchParams)
};
//pass_validated = u.query.pass == settings.modules.http.password //pass_validated = u.query.pass == settings.modules.http.password
// Allow all CORS + PNA (Private Network Access) requests.
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Private-Network", "true");
response.setHeader("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
if ((request.method || "").toLowerCase() === "options") {
requestHeaders = request.headers["access-control-request-headers"];
allowHeaders = Array.isArray(requestHeaders) ? requestHeaders.join(", ") : requestHeaders || "*";
response.writeHead(204, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
"Access-Control-Allow-Headers": allowHeaders,
"Access-Control-Allow-Private-Network": "true",
"Access-Control-Max-Age": "86400"
});
response.end();
return;
}
//console.log(u.query.username, u.query.pass) //console.log(u.query.username, u.query.pass)
if (u.pathname === '/api/getrooms') { if (u.pathname === '/api/getrooms') {
pass_validated = (await auth.auth(u.query.username, u.query.pass, "get_rooms", "get_rooms", true)); pass_validated = (await auth.auth(u.query.username, u.query.pass, "get_rooms", "get_rooms", true));
......
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* /*
ygopro-tournament.js ygopro-tournament.ts
ygopro tournament util ygopro tournament util
Author: mercury233 Author: mercury233
License: MIT License: MIT
不带参数运行时,会建立一个服务器,调用API执行对应操作 不带参数运行时,会建立一个服务器,调用API执行对应操作
*/ */
const http = require('http'); const http = __importStar(require("http"));
const https = require('https'); const https = __importStar(require("https"));
const fs = require('fs'); const fs = __importStar(require("fs"));
const url = require('url'); const url = __importStar(require("url"));
const request = require('request'); const axios_1 = __importDefault(require("axios"));
const formidable = require('formidable'); const formidable = __importStar(require("formidable"));
const _ = require('underscore'); const load_json_file_1 = require("load-json-file");
_.str = require('underscore.string'); const challonge_1 = require("./challonge");
_.mixin(_.str.exports()); const asyncLib = __importStar(require("async"));
const loadJSON = require('load-json-file').sync; const ygopro_deck_encode_1 = __importDefault(require("ygopro-deck-encode"));
const axios = require('axios'); const auth = __importStar(require("./ygopro-auth"));
const underscore_1 = __importDefault(require("underscore"));
const auth = require('./ygopro-auth.js'); const settings = (0, load_json_file_1.sync)("./config/config.json");
const config = settings.modules.tournament_mode;
const settings = loadJSON('./config/config.json'); const challonge_config = settings.modules.challonge;
config = settings.modules.tournament_mode; const challonge = new challonge_1.Challonge(challonge_config);
challonge_config = settings.modules.challonge; const ssl_config = settings.modules.http.ssl;
const { Challonge } = require('./challonge');
const challonge = new Challonge(challonge_config);
ssl_config = settings.modules.http.ssl;
const _async = require("async");
const os = require("os");
const PROCESS_COUNT = os.cpus().length;
//http长连接 //http长连接
let responder; let responder;
let wallpapers = [{ url: "", desc: "" }];
config.wallpapers=[""]; axios_1.default
request({ .get("http://www.bing.com/HPImageArchive.aspx", {
url: "http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=zh-CN", params: {
json: true format: "js",
}, function(error, response, body) { idx: 0,
if (_.isString(body)) { n: 8,
mkt: "zh-CN",
},
})
.then((response) => {
const body = response.data;
if (typeof body !== "object" || !body.images) {
console.log("wallpapers bad json", body); console.log("wallpapers bad json", body);
} }
else if (error || !body) { else if (!body) {
console.log('wallpapers error', error, response); console.log("wallpapers error", null, response);
} }
else { else {
config.wallpapers=[]; wallpapers = [];
for (const i in body.images) { for (const i in body.images) {
const wallpaper=body.images[i]; const wallpaper = body.images[i];
const img={ const img = {
"url": "http://s.cn.bing.net"+wallpaper.urlbase+"_768x1366.jpg", url: "http://s.cn.bing.net" + wallpaper.urlbase + "_768x1366.jpg",
"desc": wallpaper.copyright desc: wallpaper.copyright,
} };
config.wallpapers.push(img); wallpapers.push(img);
} }
} }
})
.catch((error) => {
console.log("wallpapers error", error, error?.response);
}); });
//输出反馈信息,如有http长连接则输出到http,否则输出到控制台 //输出反馈信息,如有http长连接则输出到http,否则输出到控制台
const sendResponse = function(text) { const sendResponse = function (text) {
text=""+text; text = "" + text;
if (responder) { if (responder) {
text=text.replace(/\n/g,"<br>"); text = text.replace(/\n/g, "<br>");
responder.write("data: " + text + "\n\n"); responder.write("data: " + text + "\n\n");
} }
else { else {
console.log(text); console.log(text);
} }
} };
//读取指定卡组 //读取指定卡组
const readDeck = async function(deck_name, deck_full_path) { const readDeck = async function (deck_name, deck_full_path) {
const deck={}; const deck_text = await fs.promises.readFile(deck_full_path, { encoding: "utf-8" });
deck.name=deck_name; const deck = ygopro_deck_encode_1.default.fromYdkString(deck_text);
deck_text = await fs.promises.readFile(deck_full_path, { encoding: "ASCII" }); deck.name = deck_name;
deck_array = deck_text.split(/\r?\n/);
deck.main = [];
deck.extra = [];
deck.side = [];
current_deck = deck.main;
for (l in deck_array) {
line = deck_array[l];
if (line.indexOf("#extra") >= 0) {
current_deck = deck.extra;
}
if (line.indexOf("!side") >= 0) {
current_deck = deck.side;
}
card = parseInt(line);
if (!isNaN(card) && !line.endsWith("#")) {
current_deck.push(card);
}
}
return deck; return deck;
} };
//读取指定文件夹中所有卡组 //读取指定文件夹中所有卡组
const getDecks = function(callback) { const getDecks = function (callback) {
const decks=[]; const decks = [];
_async.auto({ asyncLib.auto({
readDir: (done) => { readDir: (done) => {
fs.readdir(config.deck_path, done); fs.readdir(config.deck_path, done);
}, },
handleDecks: ["readDir", (results, done) => { handleDecks: [
const decks_list = results.readDir; "readDir",
_async.each(decks_list, async(deck_name) => { (results, done) => {
if (_.endsWith(deck_name, ".ydk")) { const decks_list = results.readDir;
const deck = await readDeck(deck_name, config.deck_path + deck_name); asyncLib.each(decks_list, async (deck_name) => {
decks.push(deck); if (deck_name.endsWith(".ydk")) {
} const deck = await readDeck(deck_name, config.deck_path + deck_name);
}, done) decks.push(deck);
}] }
}, (err) => { }, done);
callback(err, decks); },
],
}, (err) => {
callback(err, decks);
}); });
};
}
const delDeck = function (deck_name, callback) { const delDeck = function (deck_name, callback) {
if (deck_name.startsWith("../") || deck_name.match(/\/\.\.\//)) { //security issue if (deck_name.startsWith("../") || deck_name.match(/\/\.\.\//)) {
callback("Invalid deck"); //security issue
callback(new Error("Invalid deck"));
} }
fs.unlink(config.deck_path + deck_name, callback); fs.unlink(config.deck_path + deck_name, callback);
} };
const clearDecks = function (callback) { const clearDecks = function (callback) {
_async.auto({ asyncLib.auto({
deckList: (done) => { deckList: (done) => {
fs.readdir(config.deck_path, done); fs.readdir(config.deck_path, done);
}, },
removeAll: ["deckList", (results, done) => { removeAll: [
const decks_list = results.deckList; "deckList",
_async.each(decks_list, delDeck, done); (results, done) => {
}] const decks_list = results.deckList;
asyncLib.each(decks_list, delDeck, done);
},
],
}, callback); }, callback);
} };
const UploadToChallonge = async function () { const UploadToChallonge = async function () {
if (!challonge_config.enabled) { if (!challonge_config.enabled) {
sendResponse("未开启Challonge模式。"); sendResponse("未开启Challonge模式。");
...@@ -147,8 +168,11 @@ const UploadToChallonge = async function () { ...@@ -147,8 +168,11 @@ const UploadToChallonge = async function () {
const player_list = []; const player_list = [];
for (const k in decks_list) { for (const k in decks_list) {
const deck_name = decks_list[k]; const deck_name = decks_list[k];
if (_.endsWith(deck_name, ".ydk")) { if (deck_name.endsWith(".ydk")) {
player_list.push(deck_name.slice(0, deck_name.length - 4)); player_list.push({
name: deck_name.slice(0, deck_name.length - 4),
deckbuf: Buffer.from(ygopro_deck_encode_1.default.fromYdkString(await fs.promises.readFile(config.deck_path + deck_name, { encoding: "utf-8" })).toUpdateDeckPayload()).toString("base64"),
});
} }
} }
if (!player_list.length) { if (!player_list.length) {
...@@ -160,93 +184,100 @@ const UploadToChallonge = async function () { ...@@ -160,93 +184,100 @@ const UploadToChallonge = async function () {
sendResponse("开始清空 Challonge 玩家列表。"); sendResponse("开始清空 Challonge 玩家列表。");
await challonge.clearParticipants(); await challonge.clearParticipants();
sendResponse("开始上传玩家列表至 Challonge。"); sendResponse("开始上传玩家列表至 Challonge。");
for (const chunk of _.chunk(player_list, 10)) { for (const chunk of underscore_1.default.chunk(player_list, 10)) {
sendResponse(`开始上传玩家 ${chunk.join(', ')} 至 Challonge。`); sendResponse(`开始上传玩家 ${chunk.map((c) => c.name).join(", ")} 至 Challonge。`);
await challonge.uploadParticipants(chunk); await challonge.uploadParticipants(chunk);
} }
sendResponse("玩家列表上传完成。"); sendResponse("玩家列表上传完成。");
} catch (e) { }
catch (e) {
sendResponse("Challonge 上传失败:" + e.message); sendResponse("Challonge 上传失败:" + e.message);
} }
return true; return true;
} };
const receiveDecks = function (files, callback) {
const receiveDecks = function(files, callback) {
const result = []; const result = [];
_async.eachSeries(files, async(file) => { asyncLib.eachSeries(files, async (file) => {
if (_.endsWith(file.name, ".ydk")) { if (file.name.endsWith(".ydk")) {
const deck = await readDeck(file.name, file.path); const deck = await readDeck(file.name, file.path);
const minDeck = config.deck_dashboard_min_deck || 40; if (deck.main.length >= 40) {
const maxDeck = config.deck_dashboard_max_deck || 60;
const maxExtra = config.deck_dashboard_max_extra || 15;
const maxSide = config.deck_dashboard_max_side || 15;
if (deck.main.length >= minDeck
&& deck.main.length <= maxDeck
&& deck.extra.length <= maxExtra
&& deck.side.length <= maxSide
) {
fs.createReadStream(file.path).pipe(fs.createWriteStream(config.deck_path + file.name)); fs.createReadStream(file.path).pipe(fs.createWriteStream(config.deck_path + file.name));
result.push({ result.push({
file: file.name, file: file.name,
status: "OK" status: "OK",
}); });
} }
else { else {
result.push({ result.push({
file: file.name, file: file.name,
status: "卡组不合格" status: "卡组不合格",
}); });
} }
} }
else { else {
result.push({ result.push({
file: file.name, file: file.name,
status: "不是卡组文件" status: "不是卡组文件",
}); });
} }
}, (err) => { }, (err) => {
callback(err, result); callback(err, result);
}); });
} };
//建立一个http服务器,接收API操作 //建立一个http服务器,接收API操作
async function requestListener(req, res) { async function requestListener(req, res) {
const u = url.parse(req.url, true); const u = url.parse(req.url || "", true);
// Allow all CORS + PNA (Private Network Access) requests.
/*if (u.query.password !== config.password) { res.setHeader("Access-Control-Allow-Origin", "*");
res.writeHead(403); res.setHeader("Access-Control-Allow-Private-Network", "true");
res.end("Auth Failed."); res.setHeader("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
if ((req.method || "").toLowerCase() === "options") {
const requestHeaders = req.headers["access-control-request-headers"];
res.writeHead(204, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
"Access-Control-Allow-Headers": Array.isArray(requestHeaders)
? requestHeaders.join(", ")
: requestHeaders || "*",
"Access-Control-Allow-Private-Network": "true",
"Access-Control-Max-Age": "86400",
});
res.end();
return; return;
}*/ }
/*if (u.query.password !== config.password) {
if (u.pathname === '/api/upload_decks' && req.method.toLowerCase() == 'post') { res.writeHead(403);
if (!await auth.auth(u.query.username, u.query.password, "deck_dashboard_write", "upload_deck")) { res.end("Auth Failed.");
return;
}*/
if (u.pathname === "/api/upload_decks" && (req.method || "").toLowerCase() == "post") {
if (!(await auth.auth(u.query.username, u.query.password, "deck_dashboard_write", "upload_deck"))) {
res.writeHead(403); res.writeHead(403);
res.end("Auth Failed."); res.end("Auth Failed.");
return; return;
} }
const form = new formidable.IncomingForm(); const form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) { form.parse(req, function (err, fields, files) {
receiveDecks(files, (err, result) => { receiveDecks(files, (err, result) => {
if (err) { if (err) {
console.error(`Upload error: ${err}`); console.error(`Upload error: ${err}`);
res.writeHead(500, { res.writeHead(500, {
"Access-Control-Allow-origin": "*", "Access-Control-Allow-origin": "*",
'content-type': 'text/plain' "content-type": "text/plain",
}); });
res.end(JSON.stringify({error: err.toString()})); res.end(JSON.stringify({ error: err.toString() }));
return; return;
} }
res.writeHead(200, { res.writeHead(200, {
"Access-Control-Allow-origin": "*", "Access-Control-Allow-origin": "*",
'content-type': 'text/plain' "content-type": "text/plain",
}); });
res.end(JSON.stringify(result)); res.end(JSON.stringify(result));
}); });
}); });
} }
else if (u.pathname === '/api/msg') { else if (u.pathname === "/api/msg") {
if (!await auth.auth(u.query.username, u.query.password, "deck_dashboard_read", "login_deck_dashboard")) { if (!(await auth.auth(u.query.username, u.query.password, "deck_dashboard_read", "login_deck_dashboard"))) {
res.writeHead(403); res.writeHead(403);
res.end("Auth Failed."); res.end("Auth Failed.");
return; return;
...@@ -255,102 +286,101 @@ async function requestListener(req, res) { ...@@ -255,102 +286,101 @@ async function requestListener(req, res) {
"Access-Control-Allow-origin": "*", "Access-Control-Allow-origin": "*",
"Content-Type": "text/event-stream", "Content-Type": "text/event-stream",
"Cache-Control": "no-cache", "Cache-Control": "no-cache",
"Connection": "keep-alive" Connection: "keep-alive",
}); });
res.on("close", function () {
res.on("close", function(){
responder = null; responder = null;
}); });
responder = res; responder = res;
sendResponse("已连接。"); sendResponse("已连接。");
} }
else if (u.pathname === '/api/get_bg') { else if (u.pathname === "/api/get_bg") {
if (!await auth.auth(u.query.username, u.query.password, "deck_dashboard_read", "login_deck_dashboard")) { if (!(await auth.auth(u.query.username, u.query.password, "deck_dashboard_read", "login_deck_dashboard"))) {
res.writeHead(403); res.writeHead(403);
res.end("Auth Failed."); res.end("Auth Failed.");
return; return;
} }
res.writeHead(200); res.writeHead(200);
res.end(u.query.callback+'('+JSON.stringify(config.wallpapers[Math.floor(Math.random() * config.wallpapers.length)])+');'); res.end(u.query.callback + "(" + JSON.stringify(wallpapers[Math.floor(Math.random() * wallpapers.length)]) + ");");
} }
else if (u.pathname === '/api/get_decks') { else if (u.pathname === "/api/get_decks") {
if (!await auth.auth(u.query.username, u.query.password, "deck_dashboard_read", "get_decks")) { if (!(await auth.auth(u.query.username, u.query.password, "deck_dashboard_read", "get_decks"))) {
res.writeHead(403); res.writeHead(403);
res.end("Auth Failed."); res.end("Auth Failed.");
return; return;
} }
getDecks((err, decks) => { getDecks((err, decks) => {
if (err) { if (err) {
res.writeHead(500); res.writeHead(500);
res.end(u.query.callback + '(' + err.toString() +');'); res.end(u.query.callback + "(" + err.toString() + ");");
} else { }
else {
res.writeHead(200); res.writeHead(200);
res.end(u.query.callback+'('+JSON.stringify(decks)+');'); res.end(u.query.callback + "(" + JSON.stringify(decks) + ");");
} }
}) });
} }
else if (u.pathname === '/api/del_deck') { else if (u.pathname === "/api/del_deck") {
if (!await auth.auth(u.query.username, u.query.password, "deck_dashboard_write", "delete_deck")) { if (!(await auth.auth(u.query.username, u.query.password, "deck_dashboard_write", "delete_deck"))) {
res.writeHead(403); res.writeHead(403);
res.end("Auth Failed."); res.end("Auth Failed.");
return; return;
} }
res.writeHead(200); res.writeHead(200);
delDeck(u.query.msg, (err) => { delDeck(u.query.msg, (err) => {
let result; let result;
if (err) { if (err) {
result = "删除卡组 " + u.query.msg + "失败: " + err.toString(); result = "删除卡组 " + u.query.msg + "失败: " + err.toString();
} else { }
else {
result = "删除卡组 " + u.query.msg + "成功。"; result = "删除卡组 " + u.query.msg + "成功。";
} }
res.writeHead(200); res.writeHead(200);
res.end(u.query.callback+'("'+result+'");'); res.end(u.query.callback + '("' + result + '");');
}); });
} }
else if (u.pathname === '/api/clear_decks') { else if (u.pathname === "/api/clear_decks") {
if (!await auth.auth(u.query.username, u.query.password, "deck_dashboard_write", "clear_decks")) { if (!(await auth.auth(u.query.username, u.query.password, "deck_dashboard_write", "clear_decks"))) {
res.writeHead(403); res.writeHead(403);
res.end("Auth Failed."); res.end("Auth Failed.");
return; return;
} }
clearDecks((err) => { clearDecks((err) => {
let result; let result;
if (err) { if (err) {
result = "删除全部卡组失败。" + err.toString(); result = "删除全部卡组失败。" + err.toString();
} else { }
else {
result = "删除全部卡组成功。"; result = "删除全部卡组成功。";
} }
res.writeHead(200); res.writeHead(200);
res.end(u.query.callback+'("'+result+'");'); res.end(u.query.callback + '("' + result + '");');
}); });
} }
else if (u.pathname === '/api/upload_to_challonge') { else if (u.pathname === "/api/upload_to_challonge") {
if (!await auth.auth(u.query.username, u.query.password, "deck_dashboard_write", "upload_to_challonge")) { if (!(await auth.auth(u.query.username, u.query.password, "deck_dashboard_write", "upload_to_challonge"))) {
res.writeHead(403); res.writeHead(403);
res.end("Auth Failed."); res.end("Auth Failed.");
return; return;
} }
res.writeHead(200); res.writeHead(200);
const result = await UploadToChallonge(); await UploadToChallonge();
res.end(u.query.callback+'("操作完成。");'); res.end(u.query.callback + '("操作完成。");');
} }
else { else {
res.writeHead(400); res.writeHead(400);
res.end("400"); res.end("400");
} }
} }
if (ssl_config.enabled) { if (ssl_config.enabled) {
const ssl_cert = fs.readFileSync(ssl_config.cert); const ssl_cert = fs.readFileSync(ssl_config.cert);
const ssl_key = fs.readFileSync(ssl_config.key); const ssl_key = fs.readFileSync(ssl_config.key);
const options = { const options = {
cert: ssl_cert, cert: ssl_cert,
key: ssl_key key: ssl_key,
} };
https.createServer(options, requestListener).listen(config.port); https.createServer(options, requestListener).listen(config.port);
} else { }
else {
http.createServer(requestListener).listen(config.port); http.createServer(requestListener).listen(config.port);
} }
/*
ygopro-tournament.ts
ygopro tournament util
Author: mercury233
License: MIT
不带参数运行时,会建立一个服务器,调用API执行对应操作
*/
import * as http from "http";
import * as https from "https";
import * as fs from "fs";
import * as url from "url";
import axios from "axios";
import * as formidable from "formidable";
import { sync as loadJSON } from "load-json-file";
import defaultConfig from "./data/default_config.json";
import { Challonge } from "./challonge";
import * as asyncLib from "async";
import YGOProDeckEncode from "ygopro-deck-encode";
import * as auth from "./ygopro-auth";
import _ from "underscore";
type Settings = typeof defaultConfig;
const settings = loadJSON("./config/config.json") as Settings;
const config = settings.modules.tournament_mode;
const challonge_config = settings.modules.challonge;
const challonge = new Challonge(challonge_config);
const ssl_config = settings.modules.http.ssl;
//http长连接
let responder: http.ServerResponse | null;
let wallpapers: Array<{ url: string; desc: string }> = [{ url: "", desc: "" }];
axios
.get("http://www.bing.com/HPImageArchive.aspx", {
params: {
format: "js",
idx: 0,
n: 8,
mkt: "zh-CN",
},
})
.then((response) => {
const body = response.data;
if (typeof body !== "object" || !body.images) {
console.log("wallpapers bad json", body);
} else if (!body) {
console.log("wallpapers error", null, response);
} else {
wallpapers = [];
for (const i in body.images) {
const wallpaper = body.images[i];
const img = {
url: "http://s.cn.bing.net" + wallpaper.urlbase + "_768x1366.jpg",
desc: wallpaper.copyright,
};
wallpapers.push(img);
}
}
})
.catch((error) => {
console.log("wallpapers error", error, error?.response);
});
//输出反馈信息,如有http长连接则输出到http,否则输出到控制台
const sendResponse = function (text: string) {
text = "" + text;
if (responder) {
text = text.replace(/\n/g, "<br>");
responder.write("data: " + text + "\n\n");
} else {
console.log(text);
}
};
//读取指定卡组
const readDeck = async function (deck_name: string, deck_full_path: string) {
const deck_text = await fs.promises.readFile(deck_full_path, { encoding: "utf-8" });
const deck = YGOProDeckEncode.fromYdkString(deck_text);
deck.name = deck_name;
return deck;
};
//读取指定文件夹中所有卡组
const getDecks = function (callback: (err: Error | null, decks: any[]) => void) {
const decks: any[] = [];
asyncLib.auto(
{
readDir: (done: (err: NodeJS.ErrnoException | null, files?: string[]) => void) => {
fs.readdir(config.deck_path, done);
},
handleDecks: [
"readDir",
(results: any, done: (err?: Error | null) => void) => {
const decks_list = results.readDir as string[];
asyncLib.each(
decks_list,
async (deck_name: string) => {
if (deck_name.endsWith(".ydk")) {
const deck = await readDeck(deck_name, config.deck_path + deck_name);
decks.push(deck);
}
},
done
);
},
],
},
(err: Error | null) => {
callback(err, decks);
}
);
};
const delDeck = function (deck_name: string, callback: (err?: NodeJS.ErrnoException | null) => void) {
if (deck_name.startsWith("../") || deck_name.match(/\/\.\.\//)) {
//security issue
callback(new Error("Invalid deck"));
}
fs.unlink(config.deck_path + deck_name, callback);
};
const clearDecks = function (callback: (err?: Error | null) => void) {
asyncLib.auto(
{
deckList: (done: (err: NodeJS.ErrnoException | null, files?: string[]) => void) => {
fs.readdir(config.deck_path, done);
},
removeAll: [
"deckList",
(results: any, done: (err?: Error | null) => void) => {
const decks_list = results.deckList as string[];
asyncLib.each(decks_list, delDeck as any, done);
},
],
},
callback
);
};
const UploadToChallonge = async function () {
if (!challonge_config.enabled) {
sendResponse("未开启Challonge模式。");
return false;
}
sendResponse("开始读取玩家列表。");
const decks_list = fs.readdirSync(config.deck_path);
const player_list: Array<{ name: string; deckbuf: string }> = [];
for (const k in decks_list) {
const deck_name = decks_list[k];
if (deck_name.endsWith(".ydk")) {
player_list.push({
name: deck_name.slice(0, deck_name.length - 4),
deckbuf: Buffer.from(
YGOProDeckEncode.fromYdkString(
await fs.promises.readFile(config.deck_path + deck_name, { encoding: "utf-8" })
).toUpdateDeckPayload()
).toString("base64"),
});
}
}
if (!player_list.length) {
sendResponse("玩家列表为空。");
return false;
}
sendResponse("读取玩家列表完毕,共有" + player_list.length + "名玩家。");
try {
sendResponse("开始清空 Challonge 玩家列表。");
await challonge.clearParticipants();
sendResponse("开始上传玩家列表至 Challonge。");
for (const chunk of _.chunk(player_list, 10)) {
sendResponse(`开始上传玩家 ${chunk.map((c) => c.name).join(", ")} 至 Challonge。`);
await challonge.uploadParticipants(chunk);
}
sendResponse("玩家列表上传完成。");
} catch (e: any) {
sendResponse("Challonge 上传失败:" + e.message);
}
return true;
};
const receiveDecks = function (
files: any,
callback: (err: Error | null, result: Array<{ file: string; status: string }>) => void
) {
const result: Array<{ file: string; status: string }> = [];
asyncLib.eachSeries(
files,
async (file: any) => {
if (file.name.endsWith(".ydk")) {
const deck = await readDeck(file.name, file.path);
if (deck.main.length >= 40) {
fs.createReadStream(file.path).pipe(fs.createWriteStream(config.deck_path + file.name));
result.push({
file: file.name,
status: "OK",
});
} else {
result.push({
file: file.name,
status: "卡组不合格",
});
}
} else {
result.push({
file: file.name,
status: "不是卡组文件",
});
}
},
(err: Error | null) => {
callback(err, result);
}
);
};
//建立一个http服务器,接收API操作
async function requestListener(req: http.IncomingMessage, res: http.ServerResponse) {
const u = url.parse(req.url || "", true);
// Allow all CORS + PNA (Private Network Access) requests.
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Private-Network", "true");
res.setHeader("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
if ((req.method || "").toLowerCase() === "options") {
const requestHeaders = req.headers["access-control-request-headers"];
res.writeHead(204, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
"Access-Control-Allow-Headers": Array.isArray(requestHeaders)
? requestHeaders.join(", ")
: requestHeaders || "*",
"Access-Control-Allow-Private-Network": "true",
"Access-Control-Max-Age": "86400",
});
res.end();
return;
}
/*if (u.query.password !== config.password) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}*/
if (u.pathname === "/api/upload_decks" && (req.method || "").toLowerCase() == "post") {
if (!(await auth.auth(u.query.username as string, u.query.password as string, "deck_dashboard_write", "upload_deck"))) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}
const form = new (formidable as any).IncomingForm();
form.parse(req, function (err: Error | null, fields: any, files: any) {
receiveDecks(files, (err, result) => {
if (err) {
console.error(`Upload error: ${err}`);
res.writeHead(500, {
"Access-Control-Allow-origin": "*",
"content-type": "text/plain",
});
res.end(JSON.stringify({ error: err.toString() }));
return;
}
res.writeHead(200, {
"Access-Control-Allow-origin": "*",
"content-type": "text/plain",
});
res.end(JSON.stringify(result));
});
});
} else if (u.pathname === "/api/msg") {
if (!(await auth.auth(u.query.username as string, u.query.password as string, "deck_dashboard_read", "login_deck_dashboard"))) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}
res.writeHead(200, {
"Access-Control-Allow-origin": "*",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
res.on("close", function () {
responder = null;
});
responder = res;
sendResponse("已连接。");
} else if (u.pathname === "/api/get_bg") {
if (!(await auth.auth(u.query.username as string, u.query.password as string, "deck_dashboard_read", "login_deck_dashboard"))) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}
res.writeHead(200);
res.end(
u.query.callback + "(" + JSON.stringify(wallpapers[Math.floor(Math.random() * wallpapers.length)]) + ");"
);
} else if (u.pathname === "/api/get_decks") {
if (!(await auth.auth(u.query.username as string, u.query.password as string, "deck_dashboard_read", "get_decks"))) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}
getDecks((err, decks) => {
if (err) {
res.writeHead(500);
res.end(u.query.callback + "(" + err.toString() + ");");
} else {
res.writeHead(200);
res.end(u.query.callback + "(" + JSON.stringify(decks) + ");");
}
});
} else if (u.pathname === "/api/del_deck") {
if (!(await auth.auth(u.query.username as string, u.query.password as string, "deck_dashboard_write", "delete_deck"))) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}
res.writeHead(200);
delDeck(u.query.msg as string, (err) => {
let result;
if (err) {
result = "删除卡组 " + u.query.msg + "失败: " + err.toString();
} else {
result = "删除卡组 " + u.query.msg + "成功。";
}
res.writeHead(200);
res.end(u.query.callback + '("' + result + '");');
});
} else if (u.pathname === "/api/clear_decks") {
if (!(await auth.auth(u.query.username as string, u.query.password as string, "deck_dashboard_write", "clear_decks"))) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}
clearDecks((err) => {
let result;
if (err) {
result = "删除全部卡组失败。" + err.toString();
} else {
result = "删除全部卡组成功。";
}
res.writeHead(200);
res.end(u.query.callback + '("' + result + '");');
});
} else if (u.pathname === "/api/upload_to_challonge") {
if (!(await auth.auth(u.query.username as string, u.query.password as string, "deck_dashboard_write", "upload_to_challonge"))) {
res.writeHead(403);
res.end("Auth Failed.");
return;
}
res.writeHead(200);
await UploadToChallonge();
res.end(u.query.callback + '("操作完成。");');
} else {
res.writeHead(400);
res.end("400");
}
}
if (ssl_config.enabled) {
const ssl_cert = fs.readFileSync(ssl_config.cert);
const ssl_key = fs.readFileSync(ssl_config.key);
const options = {
cert: ssl_cert,
key: ssl_key,
};
https.createServer(options, requestListener).listen(config.port);
} else {
http.createServer(requestListener).listen(config.port);
}
...@@ -216,6 +216,25 @@ var pushHTMLs = function() { ...@@ -216,6 +216,25 @@ var pushHTMLs = function() {
//建立一个http服务器,接收API操作 //建立一个http服务器,接收API操作
async function requestListener(req, res) { async function requestListener(req, res) {
var u = url.parse(req.url, true); var u = url.parse(req.url, true);
// Allow all CORS + PNA (Private Network Access) requests.
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Private-Network", "true");
res.setHeader("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
if ((req.method || "").toLowerCase() === "options") {
const requestHeaders = req.headers["access-control-request-headers"];
res.writeHead(204, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
"Access-Control-Allow-Headers": Array.isArray(requestHeaders)
? requestHeaders.join(", ")
: requestHeaders || "*",
"Access-Control-Allow-Private-Network": "true",
"Access-Control-Max-Age": "86400"
});
res.end();
return;
}
if (!await auth.auth(u.query.username, u.query.password, "update_dashboard", "update_dashboard")) { if (!await auth.auth(u.query.username, u.query.password, "update_dashboard", "update_dashboard")) {
res.writeHead(403); res.writeHead(403);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment