Commit f93f991b authored by nanahira's avatar nanahira

thirdeye

parent 7965d428
This diff is collapsed.
......@@ -29,10 +29,10 @@
"dependencies": {
"better-sqlite3": "^7.4.3",
"class-transformer": "^0.4.0",
"koishi-thirdeye": "^8.3.3",
"leven": "3.1",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"schemastery-gen": "^2.4.2",
"source-map-support": "^0.5.20"
},
"devDependencies": {
......
import { DefineSchema, RegisterSchema } from 'schemastery-gen';
import { Schema } from 'koishi';
import { DefineSchema, RegisterSchema } from 'koishi-thirdeye';
import { YGOProCardLike } from './YGOProCard';
import _ from 'lodash';
import { renderTemplate } from './utility';
......
import 'source-map-support/register';
import { Context } from 'koishi';
import { MyPlugin } from './plugin';
import { YGOCardConfig } from './config';
export * from './plugin';
import sqlite, { Database } from 'better-sqlite3';
import { promises as fs } from 'fs';
import _ from 'lodash';
import { YGOProCard } from './YGOProCard';
import { plainToClass } from 'class-transformer';
import path from 'path';
import { BasePlugin, DefinePlugin, LifecycleEvents } from 'koishi-thirdeye';
export * from './config';
const plugin = new MyPlugin();
export default class ygocard {
static Config: any = plugin.schema;
constructor(ctx: Context, config: Partial<YGOCardConfig>) {
ctx.plugin(plugin, config as YGOCardConfig);
@DefinePlugin({ name: 'ygocard', schema: YGOCardConfig })
export default class YGOCardPlugin
extends BasePlugin<YGOCardConfig>
implements LifecycleEvents {
private dbs: Database[] = [];
private queryInDB(value: string, db: Database) {
const possibleValueNumber = parseInt(value) || 0;
const statement = db.prepare<[number, string, string]>(
"select datas.*,texts.name,texts.desc from datas,texts where datas.id = texts.id and datas.type & 0x4000 = 0 and (datas.alias = 0 or datas.id - datas.alias > 10) and (datas.id = ? or texts.name like ('%' || ? || '%') or texts.desc like ('%' || ? || '%'))",
);
return statement
.all(possibleValueNumber, value, value)
.map((obj) => plainToClass(YGOProCard, obj));
}
private queryInAllDBs(value: string, matchCount?: number) {
return _.sortBy(
_.uniqBy(
_.flatten(this.dbs.map((db) => this.queryInDB(value, db))),
(c) => c.id,
),
(c) => c.getDistanceFrom(value),
).slice(0, matchCount || this.config.matchCount);
}
private async loadDBs() {
const allDatabaseFiles = _.flatten(
await Promise.all(
this.config.databasePaths.map(async (p) =>
(await fs.readdir(p))
.filter((f) => f.endsWith('.cdb'))
.map((f) => path.join(p, f)),
),
),
);
this.ctx
.logger('ygocard')
.info(`Will load cards from ${allDatabaseFiles.length} databases.`);
this.dbs = allDatabaseFiles.map((dbFile) =>
sqlite(dbFile, {
readonly: true,
fileMustExist: true,
}),
);
}
onConnect() {
this.loadDBs();
}
onDisconnect() {
this.dbs.forEach((db) => db.close());
}
onApply() {
const ctx = this.ctx;
ctx
.command(
`${this.config.commandName} <cardQuery:string>`,
'查询 YGOPro 卡片',
)
.option('count', '-c <count:posint>', {
fallback: this.config.matchCount,
})
.usage(`使用 ${this.config.commandName} 卡号或关键词 搜索卡片。`)
.example(
`${this.config.commandName} 10000 搜索卡号为 10000 的卡片。 ${this.config.commandName} 青眼白龙 搜索卡名或描述中包含『青眼白龙』的卡片。`,
)
.action(async ({ session, options }, value) => {
const cards = this.queryInAllDBs(value, options.count);
if (!cards.length) {
return `没有找到卡片 ${value} 。`;
}
if (cards.length === 1) {
return cards[0].getDisplayString(this.config);
}
await session.send(
`卡片 ${value} 匹配下列结果:\n--------------------\n${cards
.map((c, i) => `${i + 1}\t${c.getIdAndName()}`)
.join(
'\n',
)}\n--------------------\n输入编号或卡片密码查询对应的卡片。`,
);
const reply = await session.prompt();
if (!reply) {
return;
}
const replyNumber = parseInt(reply);
const card =
cards[replyNumber - 1] ||
cards.find((c) => c.id === replyNumber) ||
cards.find((c) => c.name === value) ||
cards.find((c) => c.name.includes(value)) ||
cards.find((c) => c.desc.includes(value));
if (!card) {
return '没有找到卡片。';
}
return card.getDisplayString(this.config);
});
}
}
import 'source-map-support/register';
import { Context } from 'koishi';
import { YGOCardConfig } from './config';
import sqlite, { Database } from 'better-sqlite3';
import { promises as fs } from 'fs';
import _ from 'lodash';
import { YGOProCard } from './YGOProCard';
import { plainToClass } from 'class-transformer';
import path from 'path';
export type PluginConfig = Partial<YGOCardConfig>;
export class MyPlugin {
private config: YGOCardConfig;
private ctx: Context;
private dbs: Database[] = [];
name = 'ygocard-main';
schema = YGOCardConfig;
private queryInDB(value: string, db: Database) {
const possibleValueNumber = parseInt(value) || 0;
const statement = db.prepare<[number, string, string]>(
"select datas.*,texts.name,texts.desc from datas,texts where datas.id = texts.id and datas.type & 0x4000 = 0 and (datas.alias = 0 or datas.id - datas.alias > 10) and (datas.id = ? or texts.name like ('%' || ? || '%') or texts.desc like ('%' || ? || '%'))",
);
return statement
.all(possibleValueNumber, value, value)
.map((obj) => plainToClass(YGOProCard, obj));
}
private queryInAllDBs(value: string, matchCount?: number) {
return _.sortBy(
_.uniqBy(
_.flatten(this.dbs.map((db) => this.queryInDB(value, db))),
(c) => c.id,
),
(c) => c.getDistanceFrom(value),
).slice(0, matchCount || this.config.matchCount);
}
private async loadDBs() {
const allDatabaseFiles = _.flatten(
await Promise.all(
this.config.databasePaths.map(async (p) =>
(await fs.readdir(p))
.filter((f) => f.endsWith('.cdb'))
.map((f) => path.join(p, f)),
),
),
);
this.ctx
.logger('ygocard')
.info(`Will load cards from ${allDatabaseFiles.length} databases.`);
this.dbs = allDatabaseFiles.map((dbFile) =>
sqlite(dbFile, {
readonly: true,
fileMustExist: true,
}),
);
}
async apply(ctx: Context, config: YGOCardConfig) {
this.ctx = ctx;
this.config = config;
ctx.on('ready', () => this.loadDBs());
ctx.on('dispose', () => {
this.dbs.forEach((db) => db.close());
});
ctx
.command(
`${this.config.commandName} <cardQuery:string>`,
'查询 YGOPro 卡片',
)
.option('count', '-c <count:posint>', {
fallback: this.config.matchCount,
})
.usage(`使用 ${this.config.commandName} 卡号或关键词 搜索卡片。`)
.example(
`${this.config.commandName} 10000 搜索卡号为 10000 的卡片。 ${this.config.commandName} 青眼白龙 搜索卡名或描述中包含『青眼白龙』的卡片。`,
)
.action(async ({ session, options }, value) => {
const cards = this.queryInAllDBs(value, options.count);
if (!cards.length) {
return `没有找到卡片 ${value} 。`;
}
if (cards.length === 1) {
return cards[0].getDisplayString(this.config);
}
await session.send(
`卡片 ${value} 匹配下列结果:\n--------------------\n${cards
.map((c, i) => `${i + 1}\t${c.getIdAndName()}`)
.join(
'\n',
)}\n--------------------\n输入编号或卡片密码查询对应的卡片。`,
);
const reply = await session.prompt();
if (!reply) {
return;
}
const replyNumber = parseInt(reply);
const card =
cards[replyNumber - 1] ||
cards.find((c) => c.id === replyNumber) ||
cards.find((c) => c.name === value) ||
cards.find((c) => c.name.includes(value)) ||
cards.find((c) => c.desc.includes(value));
if (!card) {
return '没有找到卡片。';
}
return card.getDisplayString(this.config);
});
}
}
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