koishi-plugin-starfx-bot 0.18.2 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +2 -1
- package/lib/index.js +339 -14
- package/lib/utils.d.ts +22 -2
- package/package.json +8 -3
- package/readme.md +10 -0
package/lib/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export declare const name = "starfx-bot";
|
|
|
3
3
|
export declare let baseDir: string;
|
|
4
4
|
export declare let assetsDir: string;
|
|
5
5
|
export declare const starfxLogger: Logger;
|
|
6
|
-
export declare const inject: string[];
|
|
7
6
|
export declare const repeatContextMap: Map<string, [string, number]>;
|
|
8
7
|
interface sendLocalImageConfigItem {
|
|
9
8
|
hiddenInHelp: boolean;
|
|
@@ -35,6 +34,8 @@ export interface Config {
|
|
|
35
34
|
roomNumber: boolean;
|
|
36
35
|
saveRoomAsFile: string;
|
|
37
36
|
forward: boolean;
|
|
37
|
+
searchExchangeRate: boolean;
|
|
38
|
+
intervalGetExchangeRate: boolean;
|
|
38
39
|
atNotSay: boolean;
|
|
39
40
|
atNotSayProperty: number;
|
|
40
41
|
atNotSayOther: boolean;
|
package/lib/index.js
CHANGED
|
@@ -44,7 +44,6 @@ __export(src_exports, {
|
|
|
44
44
|
apply: () => apply,
|
|
45
45
|
assetsDir: () => assetsDir,
|
|
46
46
|
baseDir: () => baseDir,
|
|
47
|
-
inject: () => inject,
|
|
48
47
|
name: () => name,
|
|
49
48
|
repeatContextMap: () => repeatContextMap,
|
|
50
49
|
starfxLogger: () => starfxLogger,
|
|
@@ -61,10 +60,16 @@ var import_node_path = __toESM(require("node:path"));
|
|
|
61
60
|
var import_sharp = __toESM(require("sharp"));
|
|
62
61
|
var import_jimp = require("jimp");
|
|
63
62
|
var import_rss_parser = __toESM(require("rss-parser"));
|
|
63
|
+
var import_chartjs_adapter_dayjs_3 = require("chartjs-adapter-dayjs-3");
|
|
64
64
|
var cheerio = __toESM(require("cheerio"));
|
|
65
65
|
var import_http_proxy_agent = require("http-proxy-agent");
|
|
66
66
|
var import_https_proxy_agent = require("https-proxy-agent");
|
|
67
67
|
var import_axios = __toESM(require("axios"));
|
|
68
|
+
var import_chart = require("chart.js");
|
|
69
|
+
var import_skia_canvas = require("skia-canvas");
|
|
70
|
+
import_chart.Chart.register(
|
|
71
|
+
...import_chart.registerables
|
|
72
|
+
);
|
|
68
73
|
async function addRecord(ctx, gid, avatarUrl) {
|
|
69
74
|
const recordDir = `${assetsDir}/record/${gid}`;
|
|
70
75
|
const avatarBuffer = await ctx.http.get(avatarUrl, { responseType: "arraybuffer" });
|
|
@@ -397,6 +402,100 @@ async function drawBanGDream(avatar, inputOptions) {
|
|
|
397
402
|
return `data:image/png;base64,${(await image.getBuffer("image/jpeg")).toString("base64")}`;
|
|
398
403
|
}
|
|
399
404
|
__name(drawBanGDream, "drawBanGDream");
|
|
405
|
+
async function intervalGetExchangeRate(ctx, cfg, session, searchString, exchangeRatePath) {
|
|
406
|
+
}
|
|
407
|
+
__name(intervalGetExchangeRate, "intervalGetExchangeRate");
|
|
408
|
+
async function parseNaturalCurrency(searchString) {
|
|
409
|
+
const index = await getCurrencyCodesMap();
|
|
410
|
+
const s = searchString.replace(/\s+/g, "");
|
|
411
|
+
const match = s.match(/(.+?)(兑|对)(.+)/);
|
|
412
|
+
if (match) {
|
|
413
|
+
const from = index[match[1]];
|
|
414
|
+
const to = index[match[3]];
|
|
415
|
+
if (from && to) {
|
|
416
|
+
return from + to;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (index[s]) {
|
|
420
|
+
return index[s];
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
__name(parseNaturalCurrency, "parseNaturalCurrency");
|
|
425
|
+
async function getExchangeRate(ctx, cfg, session, searchString, raw, retryTimes = 1) {
|
|
426
|
+
const apiKey = retryTimes ? await getMvpAPIKey() : await updateMvpAPIKey();
|
|
427
|
+
try {
|
|
428
|
+
let guids = [];
|
|
429
|
+
if (searchString) {
|
|
430
|
+
const parsed = await parseNaturalCurrency(searchString);
|
|
431
|
+
if (parsed) {
|
|
432
|
+
searchString = parsed;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (raw && /^([a-z0-9]{6})([,\s]([a-z0-9]{6}))*$/.test(raw)) {
|
|
436
|
+
guids = raw.split(",");
|
|
437
|
+
} else if (/^([a-zA-Z]{3}|[a-zA-Z]{6})([,\s]([a-zA-Z]{3}|[a-zA-Z]{6}))*$/.test(searchString)) {
|
|
438
|
+
const currencies = await getCurrencyCodes();
|
|
439
|
+
const codes = searchString.split(/[,\s]+/);
|
|
440
|
+
const invalidCodes = [];
|
|
441
|
+
let search6Strings = [];
|
|
442
|
+
codes.forEach((code) => {
|
|
443
|
+
const first = code.slice(0, 3).toUpperCase();
|
|
444
|
+
const second = code.length === 3 ? "CNY" : code.slice(3, 6).toUpperCase();
|
|
445
|
+
search6Strings.push(code.length === 3 ? first + second : code);
|
|
446
|
+
if (!currencies.some((c) => c.code === first)) invalidCodes.push(first);
|
|
447
|
+
if (code.length === 6 && !currencies.some((c) => c.code === second)) invalidCodes.push(second);
|
|
448
|
+
});
|
|
449
|
+
if (invalidCodes.length > 0) {
|
|
450
|
+
throw new Error(`输入的货币代码不合法: ${invalidCodes.join(", ")}`);
|
|
451
|
+
}
|
|
452
|
+
guids = await getExchangeGuids(search6Strings.join(","), apiKey);
|
|
453
|
+
}
|
|
454
|
+
if (!guids.length || guids.every((g) => !g)) {
|
|
455
|
+
throw new Error(`输入无效: ${searchString}`);
|
|
456
|
+
}
|
|
457
|
+
const result = [];
|
|
458
|
+
for (const guid of guids) {
|
|
459
|
+
if (!guid) throw new Error("GUID not found");
|
|
460
|
+
const nowPriceArr = await getQuotes(guid, apiKey);
|
|
461
|
+
const monthPriceArr = await getMonthMSNClosePrices(guid, apiKey);
|
|
462
|
+
const nowPrice = nowPriceArr?.[0];
|
|
463
|
+
const oneMonthPrice = monthPriceArr?.[0];
|
|
464
|
+
if (!nowPrice || !oneMonthPrice) {
|
|
465
|
+
throw new Error("Failed to fetch price");
|
|
466
|
+
}
|
|
467
|
+
const chartBuffer = await drawOneMonthChartSkia(oneMonthPrice.prices, oneMonthPrice.timeStamps, `1 ${nowPrice.fromCurrency} to 1 ${nowPrice.currency}`);
|
|
468
|
+
const imgSrc = "data:image/png;base64," + chartBuffer.toString("base64");
|
|
469
|
+
result.push({
|
|
470
|
+
name: nowPrice.symbolName,
|
|
471
|
+
fromCurrency: nowPrice.fromCurrency,
|
|
472
|
+
currency: nowPrice.currency,
|
|
473
|
+
nowPrice: nowPrice.price,
|
|
474
|
+
oneMonthChart: import_koishi.h.image(imgSrc)
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
for (const item of result) {
|
|
478
|
+
await session.send([
|
|
479
|
+
import_koishi.h.text(`
|
|
480
|
+
${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
481
|
+
${item.name}
|
|
482
|
+
(${item.fromCurrency}/${item.currency})
|
|
483
|
+
当前价格: ${item.nowPrice}
|
|
484
|
+
近30天价格走势:
|
|
485
|
+
`),
|
|
486
|
+
item.oneMonthChart
|
|
487
|
+
]);
|
|
488
|
+
}
|
|
489
|
+
} catch (err) {
|
|
490
|
+
if (retryTimes === 0) {
|
|
491
|
+
const message = err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err);
|
|
492
|
+
await session.send(`查询失败:${message}`);
|
|
493
|
+
} else {
|
|
494
|
+
await getExchangeRate(ctx, cfg, session, searchString, raw, retryTimes - 1);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
__name(getExchangeRate, "getExchangeRate");
|
|
400
499
|
function parseJsonControl(text) {
|
|
401
500
|
try {
|
|
402
501
|
return JSON.parse(text);
|
|
@@ -618,6 +717,212 @@ function ready(session, cfg, param, readyMap) {
|
|
|
618
717
|
return returnMessage;
|
|
619
718
|
}
|
|
620
719
|
__name(ready, "ready");
|
|
720
|
+
async function updateMvpAPIKey() {
|
|
721
|
+
try {
|
|
722
|
+
const htmlResp = await fetch("https://www.msn.com/zh-cn/money/tools/currencyconverter");
|
|
723
|
+
if (!htmlResp.ok) throw new Error(`Failed to fetch page: ${htmlResp.status}`);
|
|
724
|
+
const html = await htmlResp.text();
|
|
725
|
+
const $ = cheerio.load(html);
|
|
726
|
+
const head = $("head");
|
|
727
|
+
const clientSettingsRaw = head.attr("data-client-settings");
|
|
728
|
+
if (!clientSettingsRaw) throw new Error("未找到 data-client-settings 属性");
|
|
729
|
+
const clientSettings = JSON.parse(clientSettingsRaw.replace(/"/g, '"'));
|
|
730
|
+
const version = clientSettings?.bundleInfo?.v;
|
|
731
|
+
if (!version) throw new Error("未找到 bundleInfo.v");
|
|
732
|
+
const targetScope = encodeURIComponent(JSON.stringify({
|
|
733
|
+
audienceMode: "adult",
|
|
734
|
+
browser: { browserType: "edgeChromium", version: "142", ismobile: "false" },
|
|
735
|
+
deviceFormFactor: "desktop",
|
|
736
|
+
domain: "www.msn.com",
|
|
737
|
+
locale: { content: { language: "zh", market: "cn" }, display: { language: "zh", market: "cn" } },
|
|
738
|
+
os: "windows",
|
|
739
|
+
modes: { audienceMode: "adult" },
|
|
740
|
+
platform: "web",
|
|
741
|
+
pageType: "finance::financetools::financecurrencyconverter",
|
|
742
|
+
pageExperiments: ["prg-cmc-river"]
|
|
743
|
+
}));
|
|
744
|
+
const apiUrl = `https://assets.msn.com/resolver/api/resolve/v3/config/?expType=AppConfig&apptype=finance&v=${version}&targetScope=${targetScope}`;
|
|
745
|
+
const apiResp = await fetch(apiUrl);
|
|
746
|
+
if (!apiResp.ok) throw new Error(`Failed to fetch config API: ${apiResp.status}`);
|
|
747
|
+
const json = await apiResp.json();
|
|
748
|
+
const newMvpAPIKey = json?.configs?.["shared/msn-ns/CommonAutoSuggest/default"]?.properties?.["mvpAPIkey"] ?? null;
|
|
749
|
+
mvpAPIKey = newMvpAPIKey;
|
|
750
|
+
return newMvpAPIKey;
|
|
751
|
+
} catch (err) {
|
|
752
|
+
console.error(err);
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
__name(updateMvpAPIKey, "updateMvpAPIKey");
|
|
757
|
+
var mvpAPIKey;
|
|
758
|
+
async function getMvpAPIKey() {
|
|
759
|
+
return mvpAPIKey ? mvpAPIKey : updateMvpAPIKey();
|
|
760
|
+
}
|
|
761
|
+
__name(getMvpAPIKey, "getMvpAPIKey");
|
|
762
|
+
async function getExchangeGuids(symbols, apikey) {
|
|
763
|
+
try {
|
|
764
|
+
const url = `https://assets.msn.cn/service/Finance/IdMap?apikey=${apikey}&MStarIds=${encodeURIComponent(symbols)}`;
|
|
765
|
+
const resp = await fetch(url);
|
|
766
|
+
if (!resp.ok) throw new Error(`请求失败: ${resp.status}`);
|
|
767
|
+
let data = await resp.json();
|
|
768
|
+
data = data.filter((d) => d.status === 200);
|
|
769
|
+
const inputSymbols = symbols.split(",");
|
|
770
|
+
const guidMap = new Map(data.map((item) => [item.mStarId, item.guid]));
|
|
771
|
+
return inputSymbols.map((sym) => guidMap.get(sym) || "");
|
|
772
|
+
} catch (err) {
|
|
773
|
+
console.error(err);
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
__name(getExchangeGuids, "getExchangeGuids");
|
|
778
|
+
async function getQuotes(ids, apiKey) {
|
|
779
|
+
try {
|
|
780
|
+
const url = `https://assets.msn.com/service/Finance/Quotes?apikey=${encodeURIComponent(apiKey)}&cm=zh-cn&it=edgeid&ids=${encodeURIComponent(ids)}&wrapodata=false`;
|
|
781
|
+
const resp = await fetch(url);
|
|
782
|
+
if (!resp.ok) throw new Error(`请求失败: ${resp.status}`);
|
|
783
|
+
const data = await resp.json();
|
|
784
|
+
let quotes = [];
|
|
785
|
+
if (Array.isArray(data)) {
|
|
786
|
+
if (Array.isArray(data[0])) {
|
|
787
|
+
quotes = data.flat();
|
|
788
|
+
} else {
|
|
789
|
+
quotes = data;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const inputIds = ids.split(",");
|
|
793
|
+
const quoteMap = new Map(
|
|
794
|
+
quotes.map((q) => [
|
|
795
|
+
q.instrumentId,
|
|
796
|
+
{
|
|
797
|
+
price: q.price,
|
|
798
|
+
symbolName: q.localizedAttributes?.["zh-cn"]?.symbolName || q.localizedAttributes?.["zh-cn"]?.displayName || q.symbol,
|
|
799
|
+
fromCurrency: q.fromCurrency || q.displayName,
|
|
800
|
+
currency: q.currency
|
|
801
|
+
}
|
|
802
|
+
])
|
|
803
|
+
);
|
|
804
|
+
return inputIds.map((id) => quoteMap.get(id) ?? { price: 0, symbolName: "", fromCurrency: "", currency: "" });
|
|
805
|
+
} catch (err) {
|
|
806
|
+
console.error(err);
|
|
807
|
+
return [];
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
__name(getQuotes, "getQuotes");
|
|
811
|
+
async function getMonthMSNClosePrices(ids, apiKey) {
|
|
812
|
+
const url = `https://assets.msn.com/service/Finance/QuoteSummary?apikey=${apiKey}&ids=${ids}&intents=Charts,Exchanges&type=1M1H&wrapodata=false`;
|
|
813
|
+
const res = await fetch(url);
|
|
814
|
+
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
|
|
815
|
+
let data = await res.json();
|
|
816
|
+
if (Array.isArray(data) && data.length && Array.isArray(data[0])) {
|
|
817
|
+
data = data.flat();
|
|
818
|
+
}
|
|
819
|
+
if (!Array.isArray(data)) {
|
|
820
|
+
throw new Error("Unexpected API response format");
|
|
821
|
+
}
|
|
822
|
+
return data.map((item) => {
|
|
823
|
+
const series = item.chart?.series;
|
|
824
|
+
return {
|
|
825
|
+
prices: series?.prices || [],
|
|
826
|
+
timeStamps: series?.timeStamps || []
|
|
827
|
+
};
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
__name(getMonthMSNClosePrices, "getMonthMSNClosePrices");
|
|
831
|
+
async function drawOneMonthChartSkia(prices, timeStamps, title = "兑换汇率", width = 1200, height = 600) {
|
|
832
|
+
if (!prices.length || !timeStamps.length || prices.length !== timeStamps.length) return;
|
|
833
|
+
const dataPoints = timeStamps.map((t, i) => ({ x: new Date(t), y: prices[i] }));
|
|
834
|
+
const canvas = new import_skia_canvas.Canvas(width, height);
|
|
835
|
+
const whiteBackground = {
|
|
836
|
+
id: "whiteBackground",
|
|
837
|
+
beforeDraw(chart) {
|
|
838
|
+
const { ctx, width: width2, height: height2 } = chart;
|
|
839
|
+
ctx.save();
|
|
840
|
+
ctx.fillStyle = "#ffffff";
|
|
841
|
+
ctx.fillRect(0, 0, width2, height2);
|
|
842
|
+
ctx.restore();
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
new import_chart.Chart(canvas, {
|
|
846
|
+
type: "line",
|
|
847
|
+
data: {
|
|
848
|
+
datasets: [{
|
|
849
|
+
label: title,
|
|
850
|
+
data: dataPoints,
|
|
851
|
+
showLine: true,
|
|
852
|
+
// ⭐ 不连线,只画点
|
|
853
|
+
pointRadius: 0,
|
|
854
|
+
// 小点
|
|
855
|
+
pointBorderWidth: 0,
|
|
856
|
+
// ⭐ 去掉外边框 → 变成实心点
|
|
857
|
+
borderWidth: 2,
|
|
858
|
+
borderColor: "red",
|
|
859
|
+
backgroundColor: "rgba(0,0,255,0.1)",
|
|
860
|
+
tension: 0,
|
|
861
|
+
segment: {
|
|
862
|
+
borderDash: /* @__PURE__ */ __name((ctx) => {
|
|
863
|
+
const p0 = ctx.p0.parsed.x;
|
|
864
|
+
const p1 = ctx.p1.parsed.x;
|
|
865
|
+
const diffHours = (p1 - p0) / (1e3 * 60 * 60);
|
|
866
|
+
return diffHours > 2 ? [6, 4] : [];
|
|
867
|
+
}, "borderDash")
|
|
868
|
+
}
|
|
869
|
+
}]
|
|
870
|
+
},
|
|
871
|
+
options: {
|
|
872
|
+
responsive: false,
|
|
873
|
+
scales: {
|
|
874
|
+
x: {
|
|
875
|
+
type: "time",
|
|
876
|
+
time: {
|
|
877
|
+
parser: "YYYY-MM-DD HH:mm:ss",
|
|
878
|
+
tooltipFormat: "YYYY/MM/DD HH:mm",
|
|
879
|
+
displayFormats: {
|
|
880
|
+
hour: "HH:mm",
|
|
881
|
+
day: "MM-DD"
|
|
882
|
+
},
|
|
883
|
+
unit: "day"
|
|
884
|
+
},
|
|
885
|
+
title: { display: true, text: "日期" }
|
|
886
|
+
},
|
|
887
|
+
y: {
|
|
888
|
+
title: { display: true, text: "价格" }
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
layout: {
|
|
892
|
+
padding: { top: 20, bottom: 20, left: 20, right: 40 }
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
plugins: [whiteBackground]
|
|
896
|
+
});
|
|
897
|
+
return await canvas.toBuffer("png");
|
|
898
|
+
}
|
|
899
|
+
__name(drawOneMonthChartSkia, "drawOneMonthChartSkia");
|
|
900
|
+
var currencyCodes = [];
|
|
901
|
+
async function getCurrencyCodes() {
|
|
902
|
+
if (currencyCodes?.length) return currencyCodes;
|
|
903
|
+
currencyCodes = require(import_node_path.default.resolve(__dirname, "./currency.json"));
|
|
904
|
+
return currencyCodes;
|
|
905
|
+
}
|
|
906
|
+
__name(getCurrencyCodes, "getCurrencyCodes");
|
|
907
|
+
var currencyCodesMap = {};
|
|
908
|
+
async function getCurrencyCodesMap() {
|
|
909
|
+
if (Object.keys(currencyCodesMap)?.length) return currencyCodesMap;
|
|
910
|
+
currencyCodesMap = await buildCurrencyIndex();
|
|
911
|
+
return currencyCodesMap;
|
|
912
|
+
}
|
|
913
|
+
__name(getCurrencyCodesMap, "getCurrencyCodesMap");
|
|
914
|
+
async function buildCurrencyIndex() {
|
|
915
|
+
const map = {};
|
|
916
|
+
(await getCurrencyCodes()).forEach((c) => {
|
|
917
|
+
map[c.country] = c.code;
|
|
918
|
+
map[c.currencyCn] = c.code;
|
|
919
|
+
});
|
|
920
|
+
map["日元"] = "JPY";
|
|
921
|
+
map["人民币"] = "CNY";
|
|
922
|
+
map["港币"] = "HKD";
|
|
923
|
+
return map;
|
|
924
|
+
}
|
|
925
|
+
__name(buildCurrencyIndex, "buildCurrencyIndex");
|
|
621
926
|
|
|
622
927
|
// src/index.ts
|
|
623
928
|
var import_node_path2 = __toESM(require("node:path"));
|
|
@@ -630,7 +935,7 @@ var package_default = {
|
|
|
630
935
|
contributors: [
|
|
631
936
|
"StarFreedomX <starfreedomx@outlook.com>"
|
|
632
937
|
],
|
|
633
|
-
version: "0.
|
|
938
|
+
version: "0.19.1",
|
|
634
939
|
main: "lib/index.js",
|
|
635
940
|
typings: "lib/index.d.ts",
|
|
636
941
|
files: [
|
|
@@ -652,7 +957,7 @@ var package_default = {
|
|
|
652
957
|
],
|
|
653
958
|
koishi: {
|
|
654
959
|
description: {
|
|
655
|
-
zh: "starfx
|
|
960
|
+
zh: "starfx的机器人小插件,目前支持: 明日方舟封印功能 闲鱼“卖掉了”功能 艾特我/他又不说话 我才不是机器人! 我也喜欢你! BanG Dream!边框绘制 群语录 roll 待机人数记录 房间号记录 推特查看原图 复读 echo 撤回 查汇率 黑白名单配置"
|
|
656
961
|
},
|
|
657
962
|
service: {},
|
|
658
963
|
preview: true,
|
|
@@ -663,13 +968,18 @@ var package_default = {
|
|
|
663
968
|
},
|
|
664
969
|
dependencies: {
|
|
665
970
|
axios: "^1.9.0",
|
|
971
|
+
"chart.js": "^4.5.1",
|
|
972
|
+
"chartjs-adapter-dayjs-3": "^1.2.3",
|
|
666
973
|
cheerio: "^1.0.0",
|
|
974
|
+
dayjs: "^1.11.19",
|
|
667
975
|
"http-proxy-agent": "^7.0.2",
|
|
668
976
|
"https-proxy-agent": "^7.0.6",
|
|
669
977
|
jimp: "^1.6.0",
|
|
978
|
+
luxon: "^3.7.2",
|
|
670
979
|
"mime-types": "^3.0.1",
|
|
671
980
|
"rss-parser": "^3.13.0",
|
|
672
|
-
sharp: "^0.34.1"
|
|
981
|
+
sharp: "^0.34.1",
|
|
982
|
+
"skia-canvas": "^3.0.8"
|
|
673
983
|
}
|
|
674
984
|
};
|
|
675
985
|
|
|
@@ -678,7 +988,6 @@ var name = "starfx-bot";
|
|
|
678
988
|
var baseDir;
|
|
679
989
|
var assetsDir;
|
|
680
990
|
var starfxLogger = new import_koishi2.Logger("starfx-bot");
|
|
681
|
-
var inject = ["database"];
|
|
682
991
|
var repeatContextMap = /* @__PURE__ */ new Map();
|
|
683
992
|
var Config2 = import_koishi2.Schema.intersect([
|
|
684
993
|
import_koishi2.Schema.object({
|
|
@@ -704,7 +1013,9 @@ var Config2 = import_koishi2.Schema.intersect([
|
|
|
704
1013
|
saveReadyAsFile: import_koishi2.Schema.string().description("写入待机人数的本地地址,留空则不写入"),
|
|
705
1014
|
roomNumber: import_koishi2.Schema.boolean().default(false).description("主跑房间号记录功能"),
|
|
706
1015
|
saveRoomAsFile: import_koishi2.Schema.string().description("写入房间号的本地地址,留空则不写入"),
|
|
707
|
-
forward: import_koishi2.Schema.boolean().default(true).description("消息转发功能")
|
|
1016
|
+
forward: import_koishi2.Schema.boolean().default(true).description("消息转发功能"),
|
|
1017
|
+
searchExchangeRate: import_koishi2.Schema.boolean().default(false).description("查汇率功能"),
|
|
1018
|
+
intervalGetExchangeRate: import_koishi2.Schema.boolean().default(false).description("汇率定时推送功能").hidden()
|
|
708
1019
|
}).description("指令小功能"),
|
|
709
1020
|
import_koishi2.Schema.object({
|
|
710
1021
|
atNotSay: import_koishi2.Schema.boolean().default(true).description("开启‘艾特我又不说话’功能"),
|
|
@@ -726,8 +1037,8 @@ var Config2 = import_koishi2.Schema.intersect([
|
|
|
726
1037
|
repeatPossibility: import_koishi2.Schema.number().role("slider").min(0).max(1).step(0.01).default(0.3).description("复读发生概率")
|
|
727
1038
|
}).description("复读功能"),
|
|
728
1039
|
import_koishi2.Schema.object({
|
|
729
|
-
|
|
730
|
-
|
|
1040
|
+
filePathToBase64: import_koishi2.Schema.boolean().default(false).description("在消息发送前检查是否有file://,如果有那么转换为base64再发送"),
|
|
1041
|
+
originImg: import_koishi2.Schema.boolean().default(false).description("根据链接获取原图开关")
|
|
731
1042
|
}).description("自用功能"),
|
|
732
1043
|
import_koishi2.Schema.union([
|
|
733
1044
|
import_koishi2.Schema.object({
|
|
@@ -850,7 +1161,7 @@ function apply(ctx, cfg) {
|
|
|
850
1161
|
}
|
|
851
1162
|
if (cfg.roomNumber) {
|
|
852
1163
|
const roomNumMap = /* @__PURE__ */ new Map();
|
|
853
|
-
ctx.command("room-number [param: string]").action(async ({ session }, param) => {
|
|
1164
|
+
ctx.command("room-number [param: string]").usage("记录房间号").action(async ({ session }, param) => {
|
|
854
1165
|
const nowRoomNumMap = cfg.saveRoomAsFile ? readMap(cfg.saveRoomAsFile) : roomNumMap;
|
|
855
1166
|
const room = nowRoomNumMap.get(session.gid);
|
|
856
1167
|
if (!param) {
|
|
@@ -874,7 +1185,7 @@ function apply(ctx, cfg) {
|
|
|
874
1185
|
}
|
|
875
1186
|
if (cfg.ready) {
|
|
876
1187
|
const readyMap = /* @__PURE__ */ new Map();
|
|
877
|
-
ctx.command("waiting-play [param:text]", { strictOptions: true }).action(async ({ session }, param) => {
|
|
1188
|
+
ctx.command("waiting-play [param:text]", { strictOptions: true }).usage("待机").action(async ({ session }, param) => {
|
|
878
1189
|
return ready(session, cfg, param, readyMap);
|
|
879
1190
|
});
|
|
880
1191
|
}
|
|
@@ -884,13 +1195,13 @@ function apply(ctx, cfg) {
|
|
|
884
1195
|
});
|
|
885
1196
|
}
|
|
886
1197
|
if (cfg.undo) {
|
|
887
|
-
ctx.command("undo").alias("撤回").action(async ({ session }) => {
|
|
1198
|
+
ctx.command("undo").alias("撤回").usage("撤回消息").action(async ({ session }) => {
|
|
888
1199
|
if (detectControl(controlJson, session.guildId, "undo"))
|
|
889
1200
|
await undo(cfg, session);
|
|
890
1201
|
});
|
|
891
1202
|
}
|
|
892
1203
|
if (cfg.forward) {
|
|
893
|
-
ctx.command("forward").option("group", "-g <group:string>").option("platform", "-p <platform:string>").action(async ({ session, options }) => {
|
|
1204
|
+
ctx.command("forward").option("group", "-g <group:string>").option("platform", "-p <platform:string>").usage("转发消息").action(async ({ session, options }) => {
|
|
894
1205
|
if (detectControl(controlJson, session.guildId, "forward")) {
|
|
895
1206
|
const mapPath = import_node_path2.default.join(assetsDir, "forward.json");
|
|
896
1207
|
const groupMap = readMap(mapPath);
|
|
@@ -916,7 +1227,7 @@ function apply(ctx, cfg) {
|
|
|
916
1227
|
});
|
|
917
1228
|
}
|
|
918
1229
|
if (cfg.originImg) {
|
|
919
|
-
ctx.command("获取X原图 <urls>").alias("推特原图").action(async ({ session }, urls) => {
|
|
1230
|
+
ctx.command("获取X原图 <urls>").alias("推特原图").usage("获取推特原图").action(async ({ session }, urls) => {
|
|
920
1231
|
if (detectControl(controlJson, session.guildId, "originImg")) {
|
|
921
1232
|
let [xUrls, xIndex] = await Promise.all([
|
|
922
1233
|
getXUrl(session?.quote?.content),
|
|
@@ -929,6 +1240,21 @@ function apply(ctx, cfg) {
|
|
|
929
1240
|
}
|
|
930
1241
|
});
|
|
931
1242
|
}
|
|
1243
|
+
if (cfg.searchExchangeRate) {
|
|
1244
|
+
ctx.command("查汇率 <exchangeParam:text>").usage("查询当前汇率").example("查汇率 JPY : 查询日元兑换人民币的汇率(3位字母)").example("查汇率 JPYCNY : 查询日元兑换人民币的汇率(6位字母)").example("查汇率 -r avdzk2 : 查询日元兑换人民币的汇率(msn代码avdzk2)").example("查汇率 -r auvwoc : 查询黄金的价格(msn代码auvwoc, 很怪吧我也不知道为什么是这个)").option("raw", "-r <raw:string>").action(async ({ session, options }, exchangeParam) => {
|
|
1245
|
+
if (detectControl(controlJson, session.guildId, "exchangeRate")) {
|
|
1246
|
+
return await getExchangeRate(ctx, cfg, session, exchangeParam, options?.raw);
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
if (cfg.intervalGetExchangeRate) {
|
|
1251
|
+
ctx.command("开启汇率推送 [exchangeParam:string]").action(async ({ session }, exchangeParam) => {
|
|
1252
|
+
if (detectControl(controlJson, session.guildId, "exchangeRate")) {
|
|
1253
|
+
const exchangeRatePath = import_node_path2.default.join(assetsDir, "exchangeRate.json");
|
|
1254
|
+
return await intervalGetExchangeRate(ctx, cfg, session, exchangeParam, exchangeRatePath);
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
932
1258
|
if (cfg.filePathToBase64) {
|
|
933
1259
|
let isLocalPath = function(src) {
|
|
934
1260
|
return /^([A-Za-z]:\\|\\\\|\/home\/|\/root\/)/.test(src);
|
|
@@ -1029,7 +1355,6 @@ __name(apply, "apply");
|
|
|
1029
1355
|
apply,
|
|
1030
1356
|
assetsDir,
|
|
1031
1357
|
baseDir,
|
|
1032
|
-
inject,
|
|
1033
1358
|
name,
|
|
1034
1359
|
repeatContextMap,
|
|
1035
1360
|
starfxLogger,
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Context, h, Session } from "koishi";
|
|
2
2
|
import sharp from "sharp";
|
|
3
3
|
import { Config } from "./index";
|
|
4
|
+
import 'chartjs-adapter-dayjs-3';
|
|
4
5
|
interface FeatureControl {
|
|
5
6
|
[feature: string]: {
|
|
6
7
|
whitelist: boolean;
|
|
@@ -80,14 +81,14 @@ export declare function handleBanGDreamConfig(options: any): Promise<{
|
|
|
80
81
|
* @param baseImage 被封印的图片url
|
|
81
82
|
* @return 画完的图片 h对象
|
|
82
83
|
*/
|
|
83
|
-
export declare function drawLock(ctx: Context, baseImage: string): Promise<
|
|
84
|
+
export declare function drawLock(ctx: Context, baseImage: string): Promise<"发生错误" | "输入无效" | h>;
|
|
84
85
|
/**
|
|
85
86
|
* "卖掉了"绘图函数
|
|
86
87
|
* @param ctx
|
|
87
88
|
* @param baseImage
|
|
88
89
|
* @return 画完的图片 h对象
|
|
89
90
|
*/
|
|
90
|
-
export declare function drawSold(ctx: Context, baseImage: string): Promise<
|
|
91
|
+
export declare function drawSold(ctx: Context, baseImage: string): Promise<"发生错误" | "输入无效" | h>;
|
|
91
92
|
/**
|
|
92
93
|
* 从url下载图片并返回sharp对象
|
|
93
94
|
* @param ctx Context
|
|
@@ -128,6 +129,8 @@ export declare function drawBanGDream(avatar: string, inputOptions?: {
|
|
|
128
129
|
starNum: number;
|
|
129
130
|
border: string;
|
|
130
131
|
}): Promise<string>;
|
|
132
|
+
export declare function intervalGetExchangeRate(ctx: Context, cfg: Config, session: Session, searchString: string, exchangeRatePath: string): Promise<void>;
|
|
133
|
+
export declare function getExchangeRate(ctx: Context, cfg: Config, session: Session, searchString?: string, raw?: string, retryTimes?: number): Promise<void>;
|
|
131
134
|
export declare function parseJsonControl(text: string): FeatureControl | null;
|
|
132
135
|
export declare function detectControl(controlJson: FeatureControl, guildId: string, funName: string): boolean;
|
|
133
136
|
export declare function handleRoll(session: Session): string;
|
|
@@ -147,4 +150,21 @@ export declare function test(url: string): Promise<void>;
|
|
|
147
150
|
export declare function writeMap(map: Map<any, any>, dest: string): void;
|
|
148
151
|
export declare function readMap(url: string): Map<any, any>;
|
|
149
152
|
export declare function ready(session: Session, cfg: Config, param: string, readyMap: Map<string, string[]>): string;
|
|
153
|
+
/**
|
|
154
|
+
* 绘制近一个月收盘价走势图
|
|
155
|
+
* @param prices 收盘价数组
|
|
156
|
+
* @param timeStamps ISO 时间戳数组
|
|
157
|
+
* @param title 图表标题
|
|
158
|
+
* @param width 图表宽度
|
|
159
|
+
* @param height 图表高度
|
|
160
|
+
* @returns buffer
|
|
161
|
+
*/
|
|
162
|
+
export declare function drawOneMonthChartSkia(prices: number[], timeStamps: string[], title?: string, width?: number, height?: number): Promise<Buffer>;
|
|
163
|
+
interface CurrencyCode {
|
|
164
|
+
"country": string;
|
|
165
|
+
"currencyCn": string;
|
|
166
|
+
"code": string;
|
|
167
|
+
}
|
|
168
|
+
export declare function getCurrencyCodes(): Promise<CurrencyCode[]>;
|
|
169
|
+
export declare function getCurrencyCodesMap(): Promise<Record<string, string>>;
|
|
150
170
|
export {};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"contributors": [
|
|
5
5
|
"StarFreedomX <starfreedomx@outlook.com>"
|
|
6
6
|
],
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.19.1",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"typings": "lib/index.d.ts",
|
|
10
10
|
"files": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"koishi": {
|
|
28
28
|
"description": {
|
|
29
|
-
"zh": "starfx
|
|
29
|
+
"zh": "starfx的机器人小插件,目前支持: 明日方舟封印功能 闲鱼“卖掉了”功能 艾特我/他又不说话 我才不是机器人! 我也喜欢你! BanG Dream!边框绘制 群语录 roll 待机人数记录 房间号记录 推特查看原图 复读 echo 撤回 查汇率 黑白名单配置"
|
|
30
30
|
},
|
|
31
31
|
"service": {},
|
|
32
32
|
"preview": true,
|
|
@@ -37,12 +37,17 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"axios": "^1.9.0",
|
|
40
|
+
"chart.js": "^4.5.1",
|
|
41
|
+
"chartjs-adapter-dayjs-3": "^1.2.3",
|
|
40
42
|
"cheerio": "^1.0.0",
|
|
43
|
+
"dayjs": "^1.11.19",
|
|
41
44
|
"http-proxy-agent": "^7.0.2",
|
|
42
45
|
"https-proxy-agent": "^7.0.6",
|
|
43
46
|
"jimp": "^1.6.0",
|
|
47
|
+
"luxon": "^3.7.2",
|
|
44
48
|
"mime-types": "^3.0.1",
|
|
45
49
|
"rss-parser": "^3.13.0",
|
|
46
|
-
"sharp": "^0.34.1"
|
|
50
|
+
"sharp": "^0.34.1",
|
|
51
|
+
"skia-canvas": "^3.0.8"
|
|
47
52
|
}
|
|
48
53
|
}
|
package/readme.md
CHANGED
|
@@ -13,9 +13,14 @@ StarFreedomX机器人的小功能,自用
|
|
|
13
13
|
* 我也喜欢你!
|
|
14
14
|
* BanG Dream!边框绘制
|
|
15
15
|
* 群语录
|
|
16
|
+
* roll
|
|
17
|
+
* 待机人数记录
|
|
18
|
+
* 房间号记录
|
|
19
|
+
* 推特查看原图
|
|
16
20
|
* 复读
|
|
17
21
|
* echo
|
|
18
22
|
* 撤回
|
|
23
|
+
* 汇率推送
|
|
19
24
|
* 黑白名单配置
|
|
20
25
|
|
|
21
26
|
## List to Do
|
|
@@ -50,6 +55,8 @@ StarFreedomX机器人的小功能,自用
|
|
|
50
55
|
| `sold` | 闲鱼“卖掉了”功能(对应`openSold`) |
|
|
51
56
|
| `repeat` | 群复读功能(对应`openRepeat`) |
|
|
52
57
|
| `record` | 群语录功能(对应`投稿、语录`) |
|
|
58
|
+
| `record-push` | 群语录功能(对应`投稿`) |
|
|
59
|
+
| `record-get` | 群语录功能(对应`语录`) |
|
|
53
60
|
| `atNotSay` | “艾特我/他又不说话”系列功能 |
|
|
54
61
|
| `replyBot` | “我才不是机器人!”系列功能 |
|
|
55
62
|
| `iLoveYou` | “我也喜欢你”系列功能 |
|
|
@@ -60,6 +67,7 @@ StarFreedomX机器人的小功能,自用
|
|
|
60
67
|
| `originImg` | rss获取推特原图 |
|
|
61
68
|
| `sendLocalImage` | 自定义指令发送图片功能(自定义指令也可配置,键为自定义的指令名称) |
|
|
62
69
|
| `forward` | 消息转发功能 |
|
|
70
|
+
| `exchangeRate` | 汇率播报功能 |
|
|
63
71
|
|
|
64
72
|
---
|
|
65
73
|
|
|
@@ -109,3 +117,5 @@ StarFreedomX机器人的小功能,自用
|
|
|
109
117
|
| `0.18.0` | 新增定时echo |
|
|
110
118
|
| `0.18.1` | 修复艾特不说话功能 |
|
|
111
119
|
| `0.18.2` | 不直接操作插件目录,防止更新问题 |
|
|
120
|
+
| `0.19.0` | 新增查汇率功能 |
|
|
121
|
+
| `0.19.0` | 查汇率适配更多语法 |
|