onkol 0.5.0 → 0.5.2
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/dist/cli/index.js +35 -16
- package/dist/plugin/discord-client.js +37 -10
- package/package.json +1 -1
- package/src/plugin/discord-client.ts +40 -12
package/dist/cli/index.js
CHANGED
|
@@ -570,32 +570,45 @@ program
|
|
|
570
570
|
console.log(chalk.cyan('[1/3] Updating files from npm package...'));
|
|
571
571
|
try {
|
|
572
572
|
// Find where this CLI is running from — that's the latest package
|
|
573
|
+
// __dirname is dist/cli/, so pkgRoot is the npm package root
|
|
573
574
|
const pkgRoot = resolve(__dirname, '..');
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
575
|
+
const { readdirSync, chmodSync } = await import('fs');
|
|
576
|
+
// Try src/plugin first (has .ts files), then dist/plugin (.js files)
|
|
577
|
+
let pluginUpdated = false;
|
|
578
|
+
for (const candidate of ['src/plugin', 'dist/plugin']) {
|
|
579
|
+
const pluginSrc = resolve(pkgRoot, candidate);
|
|
580
|
+
if (existsSync(pluginSrc)) {
|
|
581
|
+
const pluginDest = resolve(dir, 'plugins/discord-filtered');
|
|
582
|
+
mkdirSync(pluginDest, { recursive: true });
|
|
583
|
+
for (const f of readdirSync(pluginSrc)) {
|
|
584
|
+
if (f.endsWith('.ts') || f.endsWith('.js')) {
|
|
585
|
+
copyFileSync(resolve(pluginSrc, f), resolve(pluginDest, f));
|
|
586
|
+
}
|
|
585
587
|
}
|
|
588
|
+
console.log(chalk.green(` ✓ Plugin files updated (from ${candidate})`));
|
|
589
|
+
pluginUpdated = true;
|
|
590
|
+
break;
|
|
586
591
|
}
|
|
587
|
-
|
|
592
|
+
}
|
|
593
|
+
if (!pluginUpdated) {
|
|
594
|
+
console.log(chalk.yellow(` ⚠ No plugin source found in package (looked in ${pkgRoot})`));
|
|
588
595
|
}
|
|
589
596
|
// Copy scripts
|
|
597
|
+
const scriptsSrc = resolve(pkgRoot, 'scripts');
|
|
590
598
|
if (existsSync(scriptsSrc)) {
|
|
591
|
-
|
|
599
|
+
mkdirSync(resolve(dir, 'scripts'), { recursive: true });
|
|
600
|
+
let count = 0;
|
|
592
601
|
for (const f of readdirSync(scriptsSrc)) {
|
|
593
602
|
if (f.endsWith('.sh')) {
|
|
594
603
|
copyFileSync(resolve(scriptsSrc, f), resolve(dir, 'scripts', f));
|
|
595
604
|
chmodSync(resolve(dir, 'scripts', f), 0o755);
|
|
605
|
+
count++;
|
|
596
606
|
}
|
|
597
607
|
}
|
|
598
|
-
console.log(chalk.green(
|
|
608
|
+
console.log(chalk.green(` ✓ ${count} scripts updated`));
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
console.log(chalk.yellow(` ⚠ No scripts dir found at ${scriptsSrc}`));
|
|
599
612
|
}
|
|
600
613
|
}
|
|
601
614
|
catch (err) {
|
|
@@ -665,11 +678,17 @@ program
|
|
|
665
678
|
--intent "${w.intent}" \
|
|
666
679
|
${resumeArg}`;
|
|
667
680
|
try {
|
|
668
|
-
execSync(cmd, { stdio: 'pipe' });
|
|
681
|
+
const output = execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
669
682
|
console.log(chalk.green(` ✓ ${w.name} respawned${w.sessionId ? ' (resumed)' : ''}`));
|
|
683
|
+
if (output.trim())
|
|
684
|
+
console.log(chalk.gray(` ${output.trim()}`));
|
|
670
685
|
}
|
|
671
686
|
catch (err) {
|
|
672
|
-
console.log(chalk.red(` ✗ Failed to spawn ${w.name}
|
|
687
|
+
console.log(chalk.red(` ✗ Failed to spawn ${w.name}`));
|
|
688
|
+
if (err.stderr)
|
|
689
|
+
console.log(chalk.red(` stderr: ${err.stderr.toString().trim()}`));
|
|
690
|
+
if (err.stdout)
|
|
691
|
+
console.log(chalk.gray(` stdout: ${err.stdout.toString().trim()}`));
|
|
673
692
|
}
|
|
674
693
|
// Small delay to avoid Discord rate limits
|
|
675
694
|
await new Promise(r => setTimeout(r, 2000));
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Client, GatewayIntentBits } from 'discord.js';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg']);
|
|
6
|
+
const ATTACHMENT_DIR = join(tmpdir(), 'onkol-attachments');
|
|
7
|
+
mkdirSync(ATTACHMENT_DIR, { recursive: true });
|
|
2
8
|
export function shouldForwardMessage(messageChannelId, authorId, isBot, targetChannelId, allowedUsers) {
|
|
3
9
|
if (isBot)
|
|
4
10
|
return false;
|
|
@@ -8,17 +14,38 @@ export function shouldForwardMessage(messageChannelId, authorId, isBot, targetCh
|
|
|
8
14
|
return false;
|
|
9
15
|
return true;
|
|
10
16
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
function isTextAttachment(a) {
|
|
18
|
+
return a.contentType?.startsWith('text/') === true || a.name?.endsWith('.txt') === true;
|
|
19
|
+
}
|
|
20
|
+
function isImageAttachment(a) {
|
|
21
|
+
if (a.contentType?.startsWith('image/'))
|
|
22
|
+
return true;
|
|
23
|
+
const name = (a.name || '').toLowerCase();
|
|
24
|
+
return IMAGE_EXTENSIONS.has(name.slice(name.lastIndexOf('.')));
|
|
25
|
+
}
|
|
26
|
+
// Resolve all attachments: text gets inlined, images get downloaded to temp files
|
|
27
|
+
async function resolveAttachments(message) {
|
|
14
28
|
let content = message.content;
|
|
15
|
-
const
|
|
16
|
-
for (const attachment of textAttachments.values()) {
|
|
29
|
+
for (const attachment of message.attachments.values()) {
|
|
17
30
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
if (isTextAttachment(attachment)) {
|
|
32
|
+
const res = await fetch(attachment.url);
|
|
33
|
+
if (res.ok) {
|
|
34
|
+
const text = await res.text();
|
|
35
|
+
content = content ? `${content}\n\n${text}` : text;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (isImageAttachment(attachment)) {
|
|
39
|
+
const res = await fetch(attachment.url);
|
|
40
|
+
if (res.ok) {
|
|
41
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
42
|
+
const filename = `${message.id}-${attachment.name || 'image.png'}`;
|
|
43
|
+
const filepath = join(ATTACHMENT_DIR, filename);
|
|
44
|
+
writeFileSync(filepath, buffer);
|
|
45
|
+
const note = `[User sent an image: ${attachment.name || 'image'}. Saved to ${filepath} — use the Read tool to view it.]`;
|
|
46
|
+
content = content ? `${content}\n\n${note}` : note;
|
|
47
|
+
console.error(`[discord-filtered] Downloaded image: ${filepath} (${buffer.length} bytes)`);
|
|
48
|
+
}
|
|
22
49
|
}
|
|
23
50
|
}
|
|
24
51
|
catch (err) {
|
|
@@ -37,7 +64,7 @@ export function createDiscordClient(config, onMessage) {
|
|
|
37
64
|
});
|
|
38
65
|
client.on('messageCreate', async (message) => {
|
|
39
66
|
if (shouldForwardMessage(message.channel.id, message.author.id, message.author.bot, config.channelId, config.allowedUsers)) {
|
|
40
|
-
const content = await
|
|
67
|
+
const content = await resolveAttachments(message);
|
|
41
68
|
if (content) {
|
|
42
69
|
onMessage(content, message);
|
|
43
70
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { Client, GatewayIntentBits, type Message, type Attachment } from 'discord.js'
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
|
|
6
|
+
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'])
|
|
7
|
+
const ATTACHMENT_DIR = join(tmpdir(), 'onkol-attachments')
|
|
8
|
+
mkdirSync(ATTACHMENT_DIR, { recursive: true })
|
|
2
9
|
|
|
3
10
|
export interface DiscordClientConfig {
|
|
4
11
|
botToken: string
|
|
@@ -19,24 +26,45 @@ export function shouldForwardMessage(
|
|
|
19
26
|
return true
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
function isTextAttachment(a: Attachment): boolean {
|
|
30
|
+
return a.contentType?.startsWith('text/') === true || a.name?.endsWith('.txt') === true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isImageAttachment(a: Attachment): boolean {
|
|
34
|
+
if (a.contentType?.startsWith('image/')) return true
|
|
35
|
+
const name = (a.name || '').toLowerCase()
|
|
36
|
+
return IMAGE_EXTENSIONS.has(name.slice(name.lastIndexOf('.')))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Resolve all attachments: text gets inlined, images get downloaded to temp files
|
|
40
|
+
async function resolveAttachments(message: Message): Promise<string> {
|
|
25
41
|
let content = message.content
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
for (const attachment of textAttachments.values()) {
|
|
42
|
+
|
|
43
|
+
for (const attachment of message.attachments.values()) {
|
|
30
44
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
if (isTextAttachment(attachment)) {
|
|
46
|
+
const res = await fetch(attachment.url)
|
|
47
|
+
if (res.ok) {
|
|
48
|
+
const text = await res.text()
|
|
49
|
+
content = content ? `${content}\n\n${text}` : text
|
|
50
|
+
}
|
|
51
|
+
} else if (isImageAttachment(attachment)) {
|
|
52
|
+
const res = await fetch(attachment.url)
|
|
53
|
+
if (res.ok) {
|
|
54
|
+
const buffer = Buffer.from(await res.arrayBuffer())
|
|
55
|
+
const filename = `${message.id}-${attachment.name || 'image.png'}`
|
|
56
|
+
const filepath = join(ATTACHMENT_DIR, filename)
|
|
57
|
+
writeFileSync(filepath, buffer)
|
|
58
|
+
const note = `[User sent an image: ${attachment.name || 'image'}. Saved to ${filepath} — use the Read tool to view it.]`
|
|
59
|
+
content = content ? `${content}\n\n${note}` : note
|
|
60
|
+
console.error(`[discord-filtered] Downloaded image: ${filepath} (${buffer.length} bytes)`)
|
|
61
|
+
}
|
|
35
62
|
}
|
|
36
63
|
} catch (err) {
|
|
37
64
|
console.error(`[discord-filtered] Failed to fetch attachment ${attachment.name}: ${err}`)
|
|
38
65
|
}
|
|
39
66
|
}
|
|
67
|
+
|
|
40
68
|
return content
|
|
41
69
|
}
|
|
42
70
|
|
|
@@ -62,7 +90,7 @@ export function createDiscordClient(
|
|
|
62
90
|
config.allowedUsers
|
|
63
91
|
)
|
|
64
92
|
) {
|
|
65
|
-
const content = await
|
|
93
|
+
const content = await resolveAttachments(message)
|
|
66
94
|
if (content) {
|
|
67
95
|
onMessage(content, message)
|
|
68
96
|
}
|