Commit 43274a7f authored by nanahira's avatar nanahira

support yaml deck

parent f2131f12
......@@ -9,6 +9,7 @@
"version": "3.0.3",
"license": "MIT",
"dependencies": {
"js-yaml": "^4.1.0",
"koishi-thirdeye": "^11.0.9",
"leven": "^3.1.0",
"load-json-file": "^6.2.0",
......@@ -20,6 +21,7 @@
"@koishijs/plugin-database-memory": "^1.4.1",
"@koishijs/plugin-sandbox": "^2.0.1",
"@types/jest": "^27.4.1",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.179",
"@types/node": "^17.0.21",
"@typescript-eslint/eslint-plugin": "^4.33.0",
......@@ -706,6 +708,15 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/@eslint/eslintrc/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/@eslint/eslintrc/node_modules/ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
......@@ -715,6 +726,19 @@
"node": ">= 4"
}
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
......@@ -751,6 +775,28 @@
"node": ">=8"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
......@@ -1437,6 +1483,12 @@
"pretty-format": "^27.0.0"
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
"integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
......@@ -2104,13 +2156,9 @@
"dev": true
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/array-union": {
"version": "2.1.0",
......@@ -3584,6 +3632,15 @@
"node": ">=10"
}
},
"node_modules/eslint/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/eslint/node_modules/eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
......@@ -3617,6 +3674,19 @@
"node": ">= 4"
}
},
"node_modules/eslint/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/eslint/node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
......@@ -5351,13 +5421,11 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
......@@ -7096,7 +7164,7 @@
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
"node_modules/stack-utils": {
......@@ -8702,11 +8770,30 @@
"strip-json-comments": "^3.1.1"
},
"dependencies": {
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
}
}
},
......@@ -8740,6 +8827,25 @@
"resolve-from": "^5.0.0"
},
"dependencies": {
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
......@@ -9341,6 +9447,12 @@
"pretty-format": "^27.0.0"
}
},
"@types/js-yaml": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
"integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
......@@ -9864,13 +9976,9 @@
"dev": true
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"array-union": {
"version": "2.1.0",
......@@ -10864,6 +10972,15 @@
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
......@@ -10887,6 +11004,16 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
......@@ -12225,13 +12352,11 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
"argparse": "^2.0.1"
}
},
"jsdom": {
......@@ -13536,7 +13661,7 @@
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
"stack-utils": {
......
......@@ -21,13 +21,15 @@ export class DrawPluginConfig {
})
maxDepth: number;
async loadFileList() {
async loadFileList(...suffixes: string[]) {
return _.flatten(
await Promise.all(
this.deckPaths.map(async (deckPath) => {
const files = await fs.promises.readdir(deckPath);
return files
.filter((file) => file.endsWith('.json'))
.filter((file) =>
suffixes.some((suffix) => file.endsWith('.' + suffix)),
)
.map((file) => path.join(deckPath, file));
}),
),
......
import { Logger, Random } from 'koishi';
import _ from 'lodash';
import { OneDice } from 'onedice';
export type Decks = Record<string, string[]>;
export class Drawer {
constructor(
public decks: Decks,
private logger: Logger,
private maxDepth: number,
) {}
protected parseEntry(name: string, entry: string, depth = 1): string {
let result = entry.replace(/\[([^\]]+)\]/g, (dicePattern) =>
new OneDice().calculate(dicePattern.slice(1, -1)).toString(),
);
if (depth > this.maxDepth) {
this.logger.warn(`Max depth ${this.maxDepth} reached in deck ${name}.`);
return entry;
}
const usedUniqueIndex = new Map<string, Set<number>>();
result = result
.replace(/\{[^%\}]+\}/g, (refPattern) => {
let deckName = refPattern.slice(1, -1);
if (deckName.startsWith('$')) {
deckName = deckName.slice(1);
}
return this.drawFromDeck(deckName, depth + 1, name) || refPattern;
})
.replace(/\{%[^%\}]+\}/g, (uniqRefPattern) => {
const refName = uniqRefPattern.slice(2, -1);
let indexArray = usedUniqueIndex.get(refName);
if (!indexArray) {
indexArray = new Set();
usedUniqueIndex.set(refName, indexArray);
}
const deck = this.decks[refName];
if (!deck) {
this.logger.warn(
`Referenced deck ${refName} not found in deck ${name}.`,
);
return uniqRefPattern;
}
const availableIndexes = _.range(deck.length).filter(
(index) => !indexArray.has(index),
);
if (availableIndexes.length === 0) {
this.logger.warn(
`No more unique entries left for ${refName} in deck ${name}.`,
);
return uniqRefPattern;
}
const index = Random.pick(availableIndexes);
indexArray.add(index);
const entry = deck[index];
return this.parseEntry(refName, entry, depth + 1);
});
return result;
}
drawFromDeck(name: string, depth = 1, referencedDeck?: string): string {
const deck = this.decks[name];
if (!deck) {
this.logger.warn(
`${referencedDeck ? 'Referenced deck' : 'Deck'} ${name} not found${
referencedDeck ? `in deck ${referencedDeck}` : ''
}.`,
);
return null;
}
const entry = Random.pick(deck);
return this.parseEntry(name, entry, depth);
}
}
......@@ -14,17 +14,18 @@ import {
PutRenderer,
StarterPlugin,
} from 'koishi-thirdeye';
import { Logger, Random } from 'koishi';
import { Logger } from 'koishi';
import path from 'path';
import loadJsonFile from 'load-json-file';
import _ from 'lodash';
import * as localeZh from './locales/zh';
import * as localeEn from './locales/en';
import leven from 'leven';
import { OneDice } from 'onedice';
import { Decks, Drawer } from './drawer';
import { YamlDrawer, YamlStruct } from './yaml-plugin';
export * from './config';
type Decks = Record<string, string[]>;
import yaml from 'js-yaml';
import fs from 'fs';
@DefinePlugin()
export default class DrawPlugin
......@@ -34,121 +35,110 @@ export default class DrawPlugin
@InjectLogger()
private logger: Logger;
deckFiles = new Map<string, string[]>();
decks: Decks = {};
jsonDeckFiles = new Map<string, string[]>();
drawer: Drawer;
yamlDecks = new Map<string, YamlDrawer>();
yamlDeckFiles = new Map<string, YamlDrawer>();
onApply() {
this.drawer = new Drawer({}, this.logger, this.config.maxDepth);
}
async loadDeck(file: string) {
async loadJsonDeck(file: string) {
const filename = path.basename(file);
if (this.deckFiles.has(filename)) return;
if (this.jsonDeckFiles.has(filename)) return;
try {
const content: Decks = await loadJsonFile(file);
const deckTitles = Object.keys(content);
this.deckFiles.set(filename, deckTitles);
this.jsonDeckFiles.set(filename, deckTitles);
for (const key of deckTitles) {
if (this.decks[key]) {
if (this.drawer.decks[key]) {
this.logger.warn(`Duplicate deck ${key} in ${file}`);
}
this.decks[key] = content[key];
this.drawer.decks[key] = content[key];
}
} catch (e) {
this.logger.error(`Load deck file ${file} failed: ${e.message}`);
}
}
async loadDecks() {
this.deckFiles.clear();
this.decks = {};
const files = await this.config.loadFileList();
await Promise.all(files.map((file) => this.loadDeck(file)));
async loadYamlDeck(file: string) {
const filename = path.basename(file);
if (this.yamlDeckFiles.has(filename)) return;
try {
const content = yaml.load(
await fs.promises.readFile(file, 'utf8'),
) as YamlStruct;
if (!content.includes || !content.command) {
throw new Error('Invalid yaml deck');
}
const deck = new YamlDrawer(content, this.logger, this.config.maxDepth);
this.yamlDeckFiles.set(filename, deck);
this.yamlDecks.set(deck.meta.command, deck);
} catch (e) {
this.logger.error(`Load deck file ${file} failed: ${e.message}`);
}
}
async loadJsonDecks() {
this.jsonDeckFiles.clear();
this.drawer.decks = {};
const files = await this.config.loadFileList('json');
await Promise.all(files.map((file) => this.loadJsonDeck(file)));
const deckCount = _.sumBy(
Array.from(this.deckFiles.values()),
Array.from(this.jsonDeckFiles.values()),
(v) => v.length,
);
const deckFileCount = this.deckFiles.size;
const deckFileCount = this.jsonDeckFiles.size;
this.logger.info(
`Loaded ${deckCount} decks from ${deckFileCount} deck files.`,
`Loaded ${deckCount} JSON decks from ${deckFileCount} deck files.`,
);
return { deckCount, deckFileCount };
}
parseEntry(name: string, entry: string, depth = 1): string {
let result = entry.replace(/\[([^\]]+)\]/g, (dicePattern) =>
new OneDice().calculate(dicePattern.slice(1, -1)).toString(),
async loadYamlDecks() {
this.yamlDeckFiles.clear();
this.yamlDecks.clear();
const files = await this.config.loadFileList('yaml', 'yml');
await Promise.all(files.map((file) => this.loadYamlDeck(file)));
const deckCount = this.yamlDecks.size;
const deckFileCount = deckCount;
this.logger.info(
`Loaded ${deckCount} YAML decks from ${deckFileCount} deck files.`,
);
if (depth > this.config.maxDepth) {
this.logger.warn(
`Max depth ${this.config.maxDepth} reached in deck ${name}.`,
);
return entry;
}
const usedUniqueIndex = new Map<string, Set<number>>();
result = result
.replace(
/\{[^%\}]+\}/g,
(refPattern) =>
this.drawFromDeck(refPattern.slice(1, -1), depth + 1, name) ||
refPattern,
)
.replace(/\{%[^%\}]+\}/g, (uniqRefPattern) => {
const refName = uniqRefPattern.slice(2, -1);
let indexArray = usedUniqueIndex.get(refName);
if (!indexArray) {
indexArray = new Set();
usedUniqueIndex.set(refName, indexArray);
}
const deck = this.decks[refName];
if (!deck) {
this.logger.warn(
`Referenced deck ${refName} not found in deck ${name}.`,
);
return uniqRefPattern;
}
const availableIndexes = _.range(deck.length).filter(
(index) => !indexArray.has(index),
);
if (availableIndexes.length === 0) {
this.logger.warn(
`No more unique entries left for ${refName} in deck ${name}.`,
);
return uniqRefPattern;
}
const index = Random.pick(availableIndexes);
indexArray.add(index);
const entry = deck[index];
return this.parseEntry(refName, entry, depth + 1);
});
return result;
return { deckCount, deckFileCount };
}
drawFromDeck(name: string, depth = 1, referencedDeck?: string): string {
const deck = this.decks[name];
if (!deck) {
this.logger.warn(
`${referencedDeck ? 'Referenced deck' : 'Deck'} ${name} not found${
referencedDeck ? `in deck ${referencedDeck}` : ''
}.`,
);
return null;
}
const entry = Random.pick(deck);
return this.parseEntry(name, entry, depth);
async loadDecks() {
const results = await Promise.all([
this.loadJsonDecks(),
this.loadYamlDecks(),
]);
return {
deckCount: results[0].deckCount + results[1].deckCount,
deckFileCount: results[0].deckFileCount + results[1].deckFileCount,
};
}
async onConnect() {
await this.loadDecks();
}
@UseCommand('draw <name:text>', { checkArgCount: true })
@UseCommand('draw <name> [param]', { checkArgCount: true })
@CommandLocale('zh', localeZh.commands.draw)
@CommandLocale('en', localeEn.commands.draw)
drawCommand(
@PutArg(0) name: string,
@PutArg(1) param: string,
@PutUserName() user: string,
@PutCommonRenderer() renderer: CRenderer,
) {
const result = this.drawFromDeck(name);
if (this.yamlDecks.has(name)) {
const deck = this.yamlDecks.get(name);
const result = deck.draw(param);
return result ?? renderer('.notFound');
}
const result = this.drawer.drawFromDeck(name);
if (!result) {
return renderer('.notFound');
}
......@@ -164,35 +154,59 @@ export default class DrawPlugin
@UseCommand('draw.list')
@CommandLocale('zh', localeZh.commands.list)
@CommandLocale('en', localeEn.commands.list)
onListCommand(@PutRenderer('.fileInfo') renderer: Renderer) {
const entries = Array.from(this.deckFiles.entries()).map(
onListCommand(
@PutRenderer('.fileInfo') renderer: Renderer,
@PutRenderer('.yamlFileInfo') yamlRenderer: Renderer,
) {
const jsonEntries = Array.from(this.jsonDeckFiles.entries()).map(
([file, titles]) => ({
file,
count: titles.length,
}),
);
return entries.map((entry) => renderer(entry)).join('\n');
const yamlEntries = Array.from(this.yamlDeckFiles.entries()).map(
([file, deck]) => ({ ...deck.meta, file }),
);
return [
...jsonEntries.map((entry) => renderer(entry)),
...yamlEntries.map((entry) => yamlRenderer(entry)),
].join('\n');
}
@UseCommand('draw.help')
@CommandLocale('zh', localeZh.commands.help)
@CommandLocale('en', localeEn.commands.help)
onHelpCommand(@PutRenderer('.fileInfo') renderer: Renderer) {
const entries = Array.from(this.deckFiles.entries()).map(
onHelpCommand(
@PutRenderer('.fileInfo') renderer: Renderer,
@PutRenderer('.yamlFileInfo') yamlRenderer: Renderer,
) {
const jsonEntries = Array.from(this.jsonDeckFiles.entries()).map(
([file, titles]) => ({
file,
count: titles.length,
titles,
}),
);
return entries
.map(
const yamlDecks = Array.from(this.yamlDeckFiles.entries()).map(
([file, deck]) => ({ deck, file }),
);
return [
...jsonEntries.map(
(entry) =>
renderer(entry) +
'\n' +
entry.titles.map((title) => `draw ${title}`).join('\n'),
)
.join('\n');
),
...yamlDecks.map(
(entry) =>
yamlRenderer(entry.deck.meta) +
'\n' +
entry.deck
.getCommands()
.map((command) => `draw ${command}`)
.join('\n'),
),
].join('\n');
}
@UseCommand('draw.reload')
......@@ -211,9 +225,10 @@ export default class DrawPlugin
@PutRenderer('.result') renderer: Renderer,
@PutRenderer('.notFound') notFoundRenderer: Renderer,
) {
const allDecks = _.flatten(Array.from(this.deckFiles.values())).filter(
(d) => d.includes(word),
);
const allDecks = _.flatten([
...Array.from(this.jsonDeckFiles.values()),
...Array.from(this.yamlDeckFiles.values()).map((v) => v.getCommands()),
]).filter((d) => d.includes(word));
if (!allDecks.length) {
return notFoundRenderer({ word });
}
......
......@@ -2,6 +2,8 @@ import { Dict } from 'koishi';
import { CommandLocaleDef } from 'koishi-thirdeye';
export const fileInfo = 'File: {file} Count: {count}';
const yamlFileInfo = '{name} Author: {author} Version: {version}\n{desc}';
const listMessages = { fileInfo, yamlFileInfo };
export const commands: Dict<CommandLocaleDef> = {
draw: {
description: 'Draw from deck.',
......@@ -12,15 +14,11 @@ export const commands: Dict<CommandLocaleDef> = {
},
list: {
description: 'Check deck file list.',
messages: {
fileInfo,
},
messages: listMessages,
},
help: {
description: 'Check deck list.',
messages: {
fileInfo,
},
messages: listMessages,
},
reload: {
description: 'Reload deck files.',
......
import { CommandLocaleDef } from 'koishi-thirdeye';
import { Dict } from 'koishi';
export const fileInfo = '文件名: {file} 数量: {count}';
const fileInfo = '文件名: {file} 数量: {count}';
const yamlFileInfo = '{name} 文件名: {file} 作者: {author} 版本: {version}\n{desc}';
const listMessages = { fileInfo, yamlFileInfo };
export const commands: Dict<CommandLocaleDef> = {
draw: {
description: '进行牌堆抽取',
......@@ -12,15 +14,11 @@ export const commands: Dict<CommandLocaleDef> = {
},
list: {
description: '查看载入了哪些牌堆文件',
messages: {
fileInfo,
},
messages: listMessages,
},
help: {
description: '查看有哪些牌堆名可以使用',
messages: {
fileInfo,
},
messages: listMessages,
},
reload: {
description: '重新载入牌堆数据',
......
import { Logger } from 'koishi';
import { Drawer } from './drawer';
export interface YamlMeta {
name: string;
author: string;
version: number;
command: string;
desc: string;
includes: string[];
}
export type YamlStruct = YamlMeta & Record<string, string[]>;
export class YamlDrawer {
private availableDecks = new Set<string>();
private drawer = new Drawer(this.meta, this.logger, this.maxDepth);
constructor(
public meta: YamlStruct,
private logger: Logger,
private maxDepth: number,
) {
for (const deck of meta.includes ?? Object.keys(meta)) {
this.availableDecks.add(deck);
}
}
draw(deck: string, username?: string) {
deck ??= 'default';
if (!this.availableDecks.has(deck)) {
this.logger.warn(`Deck ${deck} not found.`);
return;
}
let result = this.drawer.drawFromDeck(deck);
if (result && username) {
result = result.replace(/【name】/g, username);
}
return result;
}
getCommands() {
return this.meta.includes.map((deck) =>
deck === 'default' ? this.meta.command : `${this.meta.command} ${deck}`,
);
}
}
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