koishi-plugin-audiomeme 1.0.0

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 ADDED
@@ -0,0 +1,20 @@
1
+ import { Context, Schema } from 'koishi';
2
+ export declare const name = "audiomeme";
3
+ export declare const inject: {
4
+ optional: string[];
5
+ };
6
+ declare module 'koishi' {
7
+ interface Context {
8
+ puppeteer: import('koishi-plugin-puppeteer').default;
9
+ ffmpeg: any;
10
+ silk: any;
11
+ }
12
+ }
13
+ export interface Config {
14
+ cachePath: string;
15
+ cleanupInterval: number;
16
+ cacheMaxAge: number;
17
+ debug?: boolean;
18
+ }
19
+ export declare const Config: Schema<Config>;
20
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js ADDED
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Config = exports.inject = exports.name = void 0;
7
+ exports.apply = apply;
8
+ const koishi_1 = require("koishi");
9
+ const axios_1 = __importDefault(require("axios"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ exports.name = 'audiomeme';
13
+ exports.inject = {
14
+ optional: ['puppeteer', 'ffmpeg', 'silk'],
15
+ };
16
+ exports.Config = koishi_1.Schema.object({
17
+ cachePath: koishi_1.Schema.string().default('cache/audiomeme').description('Cache directory for audio files.'),
18
+ cleanupInterval: koishi_1.Schema.number().default(10 * 60 * 1000).description('Cleanup interval in milliseconds (default 10 minutes).'),
19
+ cacheMaxAge: koishi_1.Schema.number().default(60 * 60 * 1000).description('Maximum age for cached files in milliseconds (default 1 hour).'),
20
+ debug: koishi_1.Schema.boolean().default(false).description('Enable debug mode to show more logs.'),
21
+ });
22
+ function apply(ctx, config) {
23
+ const logger = ctx.logger('audiomeme');
24
+ const sounds = require('../meme_sounds.json');
25
+ const lastAccess = new Map();
26
+ const cacheDir = path_1.default.resolve(ctx.baseDir, config.cachePath);
27
+ fs_extra_1.default.ensureDirSync(cacheDir);
28
+ async function playSound(name) {
29
+ const sound = sounds.find(s => s.name === name);
30
+ if (!sound)
31
+ return 'Meme sound not found.';
32
+ const fileName = `${encodeURIComponent(sound.name)}.mp3`;
33
+ const filePath = path_1.default.join(cacheDir, fileName);
34
+ if (config.debug)
35
+ logger.info(`Playing sound: ${name}, Path: ${filePath}`);
36
+ try {
37
+ if (!await fs_extra_1.default.pathExists(filePath)) {
38
+ if (config.debug)
39
+ logger.info(`Cache miss. Downloading ${sound.name} from ${sound.url}`);
40
+ const response = await axios_1.default.get(sound.url, {
41
+ responseType: 'arraybuffer',
42
+ timeout: 10000,
43
+ headers: {
44
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
45
+ }
46
+ });
47
+ if (response.data.byteLength === 0) {
48
+ logger.error(`Downloaded file for ${name} is empty.`);
49
+ return 'Downloaded file is empty.';
50
+ }
51
+ await fs_extra_1.default.writeFile(filePath, Buffer.from(response.data));
52
+ if (config.debug)
53
+ logger.info(`Saved to ${filePath}, size: ${response.data.byteLength} bytes`);
54
+ }
55
+ else {
56
+ if (config.debug)
57
+ logger.info(`Cache hit for ${name}.`);
58
+ }
59
+ lastAccess.set(fileName, Date.now());
60
+ const buffer = await fs_extra_1.default.readFile(filePath);
61
+ if (config.debug)
62
+ logger.info(`Sending audio as Buffer, size: ${buffer.byteLength} bytes`);
63
+ return koishi_1.h.audio(buffer, 'audio/mpeg');
64
+ }
65
+ catch (e) {
66
+ const error = e;
67
+ logger.error(`Failed to handle meme audio: ${error.message}`);
68
+ if (axios_1.default.isAxiosError(e)) {
69
+ return `Failed to download audio: ${e.code || e.message}`;
70
+ }
71
+ return 'Failed to download or play meme sound.';
72
+ }
73
+ }
74
+ ctx.command('memeaudio [name:string]', 'Play a meme sound')
75
+ .option('list', '-l List all available meme sounds', { type: 'boolean' })
76
+ .action(async ({ session, options }, name) => {
77
+ if (options?.list) {
78
+ if (!ctx.puppeteer)
79
+ return 'Puppeteer service not found.';
80
+ const listHtml = `
81
+ <html>
82
+ <head>
83
+ <style>
84
+ body {
85
+ background-color: #1a1a1a;
86
+ color: #ffffff;
87
+ font-family: sans-serif;
88
+ padding: 20px;
89
+ margin: 0;
90
+ }
91
+ .container {
92
+ display: grid;
93
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
94
+ gap: 10px;
95
+ padding: 10px;
96
+ }
97
+ .item {
98
+ background-color: #333;
99
+ padding: 10px;
100
+ border-radius: 6px;
101
+ text-align: center;
102
+ word-break: break-all;
103
+ font-size: 14px;
104
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
105
+ }
106
+ h2 {
107
+ text-align: center;
108
+ margin-top: 0;
109
+ color: #4fc3f7;
110
+ }
111
+ </style>
112
+ </head>
113
+ <body>
114
+ <h2>Meme Audio List</h2>
115
+ <div class="container">
116
+ ${sounds.map(s => `<div class="item">${s.name}</div>`).join('')}
117
+ </div>
118
+ </body>
119
+ </html>
120
+ `;
121
+ const page = await ctx.puppeteer.page();
122
+ await page.setContent(listHtml);
123
+ const body = await page.$('body');
124
+ const buffer = await body?.screenshot({ type: 'png' });
125
+ await page.close();
126
+ if (buffer)
127
+ return koishi_1.h.image(buffer, 'image/png');
128
+ return 'Failed to generate list image.';
129
+ }
130
+ if (!name)
131
+ return 'Please provide a meme name or use -l to list all.';
132
+ return playSound(name);
133
+ });
134
+ ctx.command('memeaudio.random', 'Play a random meme sound')
135
+ .action(async () => {
136
+ const randomSound = sounds[Math.floor(Math.random() * sounds.length)];
137
+ return playSound(randomSound.name);
138
+ });
139
+ ctx.setInterval(async () => {
140
+ if (config.debug)
141
+ logger.info('Running cache cleanup check...');
142
+ const now = Date.now();
143
+ const files = await fs_extra_1.default.readdir(cacheDir);
144
+ let deletedCount = 0;
145
+ for (const file of files) {
146
+ const accessTime = lastAccess.get(file);
147
+ let shouldDelete = false;
148
+ if (accessTime) {
149
+ if (now - accessTime > config.cacheMaxAge) {
150
+ shouldDelete = true;
151
+ }
152
+ }
153
+ else {
154
+ try {
155
+ const stats = await fs_extra_1.default.stat(path_1.default.join(cacheDir, file));
156
+ if (now - stats.mtimeMs > config.cacheMaxAge) {
157
+ shouldDelete = true;
158
+ }
159
+ }
160
+ catch (e) { }
161
+ }
162
+ if (shouldDelete) {
163
+ await fs_extra_1.default.unlink(path_1.default.join(cacheDir, file)).catch(() => { });
164
+ lastAccess.delete(file);
165
+ deletedCount++;
166
+ }
167
+ }
168
+ if (config.debug && deletedCount > 0)
169
+ logger.info(`Cleanup finished. Deleted ${deletedCount} files.`);
170
+ }, config.cleanupInterval);
171
+ }