slashvibe-mcp 0.2.0 โ 0.2.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/discord.js +19 -0
- package/index.js +53 -2
- package/package.json +2 -1
- package/tools/patterns.js +79 -0
- package/tools/social-inbox.js +180 -0
- package/tools/social-post.js +141 -0
package/discord.js
CHANGED
|
@@ -116,6 +116,24 @@ async function postAnnouncement(message) {
|
|
|
116
116
|
return post(null, { embed });
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Post a conversation highlight
|
|
121
|
+
*/
|
|
122
|
+
async function postHighlight(handle, title, summary, threads = []) {
|
|
123
|
+
const threadList = threads.length > 0
|
|
124
|
+
? '\n\n**Open threads:**\n' + threads.map(t => `โข ${t}`).join('\n')
|
|
125
|
+
: '';
|
|
126
|
+
|
|
127
|
+
const embed = {
|
|
128
|
+
color: 0xF39C12, // Gold/amber for highlights
|
|
129
|
+
title: `๐ฌ ${title}`,
|
|
130
|
+
description: summary + threadList,
|
|
131
|
+
footer: { text: `shared by @${handle} ยท slashvibe.dev` },
|
|
132
|
+
timestamp: new Date().toISOString()
|
|
133
|
+
};
|
|
134
|
+
return post(null, { embed });
|
|
135
|
+
}
|
|
136
|
+
|
|
119
137
|
/**
|
|
120
138
|
* Post who's currently online
|
|
121
139
|
*/
|
|
@@ -147,5 +165,6 @@ module.exports = {
|
|
|
147
165
|
postActivity,
|
|
148
166
|
postStatus,
|
|
149
167
|
postAnnouncement,
|
|
168
|
+
postHighlight,
|
|
150
169
|
postOnlineList
|
|
151
170
|
};
|
package/index.js
CHANGED
|
@@ -9,10 +9,44 @@
|
|
|
9
9
|
const presence = require('./presence');
|
|
10
10
|
const config = require('./config');
|
|
11
11
|
const store = require('./store');
|
|
12
|
+
const prompts = require('./prompts');
|
|
12
13
|
|
|
13
14
|
// Tools that shouldn't show presence footer (would be redundant/noisy)
|
|
14
15
|
const SKIP_FOOTER_TOOLS = ['vibe_init', 'vibe_doctor', 'vibe_test', 'vibe_update'];
|
|
15
16
|
|
|
17
|
+
// Infer user prompt from tool arguments (for pattern logging)
|
|
18
|
+
function inferPromptFromArgs(toolName, args) {
|
|
19
|
+
const action = toolName.replace('vibe_', '');
|
|
20
|
+
const handle = args.handle ? `@${args.handle.replace('@', '')}` : '';
|
|
21
|
+
const message = args.message ? `"${args.message.slice(0, 50)}..."` : '';
|
|
22
|
+
const note = args.note || '';
|
|
23
|
+
const mood = args.mood || '';
|
|
24
|
+
const reaction = args.reaction || '';
|
|
25
|
+
|
|
26
|
+
switch (action) {
|
|
27
|
+
case 'start': return 'start vibing';
|
|
28
|
+
case 'who': return 'who is online';
|
|
29
|
+
case 'ping': return `ping ${handle} ${note}`.trim();
|
|
30
|
+
case 'react': return `react ${reaction} to ${handle}`.trim();
|
|
31
|
+
case 'dm': return `message ${handle} ${message}`.trim();
|
|
32
|
+
case 'inbox': return 'check inbox';
|
|
33
|
+
case 'open': return `open thread with ${handle}`;
|
|
34
|
+
case 'status': return `set status to ${mood}`;
|
|
35
|
+
case 'context': return 'share context';
|
|
36
|
+
case 'summarize': return 'summarize session';
|
|
37
|
+
case 'bye': return 'end session';
|
|
38
|
+
case 'remember': return `remember about ${handle}`;
|
|
39
|
+
case 'recall': return `recall ${handle}`;
|
|
40
|
+
case 'forget': return `forget ${handle}`;
|
|
41
|
+
case 'board': return args.content ? 'post to board' : 'view board';
|
|
42
|
+
case 'invite': return 'generate invite';
|
|
43
|
+
case 'echo': return 'send feedback';
|
|
44
|
+
case 'x_mentions': return 'check x mentions';
|
|
45
|
+
case 'x_reply': return 'reply on x';
|
|
46
|
+
default: return `${action} ${handle}`.trim() || null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
16
50
|
// Generate terminal title escape sequence (OSC 0)
|
|
17
51
|
function getTerminalTitle(onlineCount, unreadCount, lastActivity) {
|
|
18
52
|
const parts = [];
|
|
@@ -145,7 +179,12 @@ const tools = {
|
|
|
145
179
|
vibe_echo: require('./tools/echo'),
|
|
146
180
|
// X/Twitter bridge
|
|
147
181
|
vibe_x_mentions: require('./tools/x-mentions'),
|
|
148
|
-
vibe_x_reply: require('./tools/x-reply')
|
|
182
|
+
vibe_x_reply: require('./tools/x-reply'),
|
|
183
|
+
// Unified social inbox (Phase 1a)
|
|
184
|
+
vibe_social_inbox: require('./tools/social-inbox'),
|
|
185
|
+
vibe_social_post: require('./tools/social-post'),
|
|
186
|
+
// Language evolution
|
|
187
|
+
vibe_patterns: require('./tools/patterns')
|
|
149
188
|
};
|
|
150
189
|
|
|
151
190
|
/**
|
|
@@ -196,7 +235,19 @@ class VibeMCPServer {
|
|
|
196
235
|
}
|
|
197
236
|
|
|
198
237
|
try {
|
|
199
|
-
|
|
238
|
+
// Log prompt pattern (if _prompt passed) or infer from args
|
|
239
|
+
const args = params.arguments || {};
|
|
240
|
+
const inferredPrompt = args._prompt || inferPromptFromArgs(params.name, args);
|
|
241
|
+
if (inferredPrompt) {
|
|
242
|
+
prompts.log(inferredPrompt, {
|
|
243
|
+
tool: params.name,
|
|
244
|
+
action: params.name.replace('vibe_', ''),
|
|
245
|
+
target: args.handle || args.to || null,
|
|
246
|
+
transform: args.format || args.category || null
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const result = await tool.handler(args);
|
|
200
251
|
|
|
201
252
|
// Add ambient presence footer (unless tool is in skip list)
|
|
202
253
|
let footer = '';
|
package/package.json
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe patterns โ View emerging language constructs
|
|
3
|
+
*
|
|
4
|
+
* Shows frequent prompt patterns and suggests new commands.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const prompts = require('../prompts');
|
|
8
|
+
|
|
9
|
+
const definition = {
|
|
10
|
+
name: 'vibe_patterns',
|
|
11
|
+
description: 'View emerging language patterns from how people use /vibe. Shows frequent prompts and suggests new commands.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
limit: {
|
|
16
|
+
type: 'number',
|
|
17
|
+
description: 'Number of recent prompts to analyze (default: 50)'
|
|
18
|
+
},
|
|
19
|
+
raw: {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
description: 'Show raw prompts instead of patterns'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
async function handler({ limit = 50, raw = false }) {
|
|
28
|
+
if (raw) {
|
|
29
|
+
// Show raw recent prompts
|
|
30
|
+
const recent = prompts.getRecent(limit);
|
|
31
|
+
|
|
32
|
+
if (recent.length === 0) {
|
|
33
|
+
return {
|
|
34
|
+
display: `## Prompt Log\n\n_No prompts logged yet. Use /vibe and patterns will emerge._\n\nFile: \`${prompts.PROMPTS_FILE}\``
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let display = `## Recent Prompts (${recent.length})\n\n`;
|
|
39
|
+
display += '| Time | Prompt | Tool |\n';
|
|
40
|
+
display += '|------|--------|------|\n';
|
|
41
|
+
|
|
42
|
+
for (const p of recent.slice(0, 20)) {
|
|
43
|
+
const time = new Date(p.ts).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
44
|
+
const prompt = p.prompt.slice(0, 40) + (p.prompt.length > 40 ? '...' : '');
|
|
45
|
+
display += `| ${time} | ${prompt} | ${p.tool || '-'} |\n`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
display += `\n---\n_${recent.length} total logged ยท ${prompts.PROMPTS_FILE}_`;
|
|
49
|
+
return { display };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Show patterns and suggestions
|
|
53
|
+
const patterns = prompts.extractPatterns();
|
|
54
|
+
const suggestions = prompts.suggestConstructs();
|
|
55
|
+
|
|
56
|
+
let display = '## Emerging Patterns\n\n';
|
|
57
|
+
|
|
58
|
+
if (patterns.length === 0) {
|
|
59
|
+
display += '_Not enough data yet. Keep using /vibe and patterns will emerge._\n';
|
|
60
|
+
} else {
|
|
61
|
+
display += '**Frequent patterns:**\n';
|
|
62
|
+
for (const { pattern, count } of patterns.slice(0, 10)) {
|
|
63
|
+
display += `- \`${pattern}\` (${count}x)\n`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (suggestions.length > 0) {
|
|
68
|
+
display += '\n**Suggested constructs:**\n';
|
|
69
|
+
for (const { pattern, construct, count } of suggestions) {
|
|
70
|
+
display += `- \`${construct}\` โ from "${pattern}" (${count}x)\n`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
display += `\n---\n_Run \`vibe patterns --raw\` to see individual prompts_`;
|
|
75
|
+
|
|
76
|
+
return { display };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe social-inbox โ Unified social inbox across all channels
|
|
3
|
+
*
|
|
4
|
+
* Reads from the local cache (sync-then-read pattern) for instant access.
|
|
5
|
+
* Use --refresh to trigger a sync.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { requireInit, header, divider, formatTimeAgo } = require('./_shared');
|
|
9
|
+
|
|
10
|
+
const API_URL = process.env.VIBE_API_URL || 'https://slashvibe.dev';
|
|
11
|
+
|
|
12
|
+
const definition = {
|
|
13
|
+
name: 'vibe_social_inbox',
|
|
14
|
+
description: 'See messages across all connected social channels (X, Farcaster, etc.)',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
channel: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
enum: ['all', 'x', 'farcaster', 'discord', 'telegram', 'whatsapp', 'email'],
|
|
21
|
+
description: 'Filter by channel (default: all)'
|
|
22
|
+
},
|
|
23
|
+
high_signal: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
description: 'Show only high-signal messages like mentions/DMs (default: true)'
|
|
26
|
+
},
|
|
27
|
+
limit: {
|
|
28
|
+
type: 'number',
|
|
29
|
+
description: 'Number of messages to show (default: 20, max: 100)'
|
|
30
|
+
},
|
|
31
|
+
refresh: {
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
description: 'Force sync from external APIs (default: false)'
|
|
34
|
+
},
|
|
35
|
+
status: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
description: 'Show channel connection status (default: false)'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
async function handler(args) {
|
|
44
|
+
const initCheck = requireInit();
|
|
45
|
+
if (initCheck) return initCheck;
|
|
46
|
+
|
|
47
|
+
const {
|
|
48
|
+
channel = 'all',
|
|
49
|
+
high_signal = true,
|
|
50
|
+
limit = 20,
|
|
51
|
+
refresh = false,
|
|
52
|
+
status = false
|
|
53
|
+
} = args;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
// Build query params
|
|
57
|
+
const params = new URLSearchParams();
|
|
58
|
+
if (channel !== 'all') params.set('channel', channel);
|
|
59
|
+
if (!high_signal) params.set('high_signal', 'false');
|
|
60
|
+
if (limit) params.set('limit', limit.toString());
|
|
61
|
+
if (refresh) params.set('refresh', 'true');
|
|
62
|
+
if (status) params.set('status', 'true');
|
|
63
|
+
|
|
64
|
+
const url = `${API_URL}/api/social?${params}`;
|
|
65
|
+
|
|
66
|
+
const response = await fetch(url);
|
|
67
|
+
const data = await response.json();
|
|
68
|
+
|
|
69
|
+
if (!data.success) {
|
|
70
|
+
return { display: `${header('Social Inbox')}\n\n_Error:_ ${data.error}` };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Status view
|
|
74
|
+
if (status) {
|
|
75
|
+
let display = header('Channel Status');
|
|
76
|
+
display += '\n\n';
|
|
77
|
+
|
|
78
|
+
for (const [ch, info] of Object.entries(data.channels || {})) {
|
|
79
|
+
const icon = info.status?.status === 'connected' ? 'โ
' : 'โ';
|
|
80
|
+
const configured = info.configured ? 'configured' : 'not configured';
|
|
81
|
+
display += `${icon} **${ch}** โ ${configured}\n`;
|
|
82
|
+
|
|
83
|
+
if (info.status?.error) {
|
|
84
|
+
display += ` _${info.status.error}_\n`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (info.capabilities) {
|
|
88
|
+
const caps = [];
|
|
89
|
+
if (info.capabilities.read) caps.push('read');
|
|
90
|
+
if (info.capabilities.write) caps.push('write');
|
|
91
|
+
if (info.capabilities.dm) caps.push('dm');
|
|
92
|
+
display += ` Capabilities: ${caps.join(', ')}\n`;
|
|
93
|
+
}
|
|
94
|
+
display += '\n';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { display };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Inbox view
|
|
101
|
+
const messages = data.messages || [];
|
|
102
|
+
|
|
103
|
+
if (messages.length === 0) {
|
|
104
|
+
let display = header('Social Inbox');
|
|
105
|
+
display += '\n\n';
|
|
106
|
+
|
|
107
|
+
if (data.summary?.total === 0) {
|
|
108
|
+
display += '_No messages synced yet._\n\n';
|
|
109
|
+
display += 'Run `vibe social-inbox --status` to check channel connections.\n';
|
|
110
|
+
display += 'Run `vibe social-inbox --refresh` to trigger a sync.';
|
|
111
|
+
} else {
|
|
112
|
+
display += `_No ${channel === 'all' ? '' : channel + ' '}messages found._`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { display };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Format messages
|
|
119
|
+
let display = header(`Social Inbox (${messages.length})`);
|
|
120
|
+
display += '\n\n';
|
|
121
|
+
|
|
122
|
+
// Group by channel for summary
|
|
123
|
+
const byChannel = {};
|
|
124
|
+
for (const msg of messages) {
|
|
125
|
+
byChannel[msg.channel] = (byChannel[msg.channel] || 0) + 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const channelSummary = Object.entries(byChannel)
|
|
129
|
+
.map(([ch, count]) => `${ch}: ${count}`)
|
|
130
|
+
.join(' | ');
|
|
131
|
+
display += `๐ฌ ${channelSummary}\n`;
|
|
132
|
+
display += divider();
|
|
133
|
+
display += '\n';
|
|
134
|
+
|
|
135
|
+
// Show messages
|
|
136
|
+
for (const msg of messages) {
|
|
137
|
+
const channelIcon = getChannelIcon(msg.channel);
|
|
138
|
+
const typeIcon = getTypeIcon(msg.type);
|
|
139
|
+
|
|
140
|
+
display += `${channelIcon} **@${msg.from}** ${typeIcon} โ _${msg.timeAgo}_\n`;
|
|
141
|
+
display += `${msg.content}\n`;
|
|
142
|
+
display += `_[${msg.channel}:${msg.id.split(':')[1]?.slice(0, 8)}]_\n\n`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
display += divider();
|
|
146
|
+
display += 'Reply: `vibe post "message" --x --farcaster`';
|
|
147
|
+
|
|
148
|
+
return { display };
|
|
149
|
+
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return {
|
|
152
|
+
display: `${header('Social Inbox')}\n\n_Error:_ ${e.message}`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getChannelIcon(channel) {
|
|
158
|
+
const icons = {
|
|
159
|
+
x: '๐',
|
|
160
|
+
farcaster: '๐ฃ',
|
|
161
|
+
discord: '๐ฌ',
|
|
162
|
+
telegram: 'โ๏ธ',
|
|
163
|
+
whatsapp: '๐',
|
|
164
|
+
email: '๐ง'
|
|
165
|
+
};
|
|
166
|
+
return icons[channel] || '๐ฑ';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getTypeIcon(type) {
|
|
170
|
+
const icons = {
|
|
171
|
+
mention: '@',
|
|
172
|
+
reply: 'โฉ๏ธ',
|
|
173
|
+
dm: 'โ๏ธ',
|
|
174
|
+
like: 'โค๏ธ',
|
|
175
|
+
repost: '๐'
|
|
176
|
+
};
|
|
177
|
+
return icons[type] || '';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = { definition, handler };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe social-post โ Post to multiple social channels at once
|
|
3
|
+
*
|
|
4
|
+
* Multi-cast posting with dry-run preview support.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { requireInit, header, divider, warning } = require('./_shared');
|
|
8
|
+
|
|
9
|
+
const API_URL = process.env.VIBE_API_URL || 'https://slashvibe.dev';
|
|
10
|
+
|
|
11
|
+
const definition = {
|
|
12
|
+
name: 'vibe_social_post',
|
|
13
|
+
description: 'Post content to one or more social channels (X, Farcaster, etc.)',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
content: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'The content to post'
|
|
20
|
+
},
|
|
21
|
+
channels: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
items: { type: 'string' },
|
|
24
|
+
description: 'Channels to post to (e.g., ["x", "farcaster"])'
|
|
25
|
+
},
|
|
26
|
+
dry_run: {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
description: 'Preview post without sending (default: false)'
|
|
29
|
+
},
|
|
30
|
+
reply_to: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'Message ID to reply to (e.g., "x:1234567890")'
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
required: ['content', 'channels']
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
async function handler(args) {
|
|
40
|
+
const initCheck = requireInit();
|
|
41
|
+
if (initCheck) return initCheck;
|
|
42
|
+
|
|
43
|
+
const { content, channels, dry_run, reply_to } = args;
|
|
44
|
+
|
|
45
|
+
// Validation
|
|
46
|
+
if (!content || typeof content !== 'string' || content.trim().length === 0) {
|
|
47
|
+
return { display: 'Need content to post.' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!channels || !Array.isArray(channels) || channels.length === 0) {
|
|
51
|
+
return { display: 'Need at least one channel. Options: x, farcaster' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const trimmed = content.trim();
|
|
55
|
+
|
|
56
|
+
// Character limit warnings
|
|
57
|
+
const warnings = [];
|
|
58
|
+
if (channels.includes('x') && trimmed.length > 280) {
|
|
59
|
+
warnings.push(`X: Content is ${trimmed.length} chars (max 280). Will be truncated.`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch(`${API_URL}/api/social`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
body: JSON.stringify({
|
|
67
|
+
content: trimmed,
|
|
68
|
+
channels,
|
|
69
|
+
dry_run: dry_run || false,
|
|
70
|
+
reply_to
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
|
|
76
|
+
if (!data.success && !data.dry_run) {
|
|
77
|
+
return { display: `${header('Post Failed')}\n\n_Error:_ ${data.error}` };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Dry run preview
|
|
81
|
+
if (data.dry_run) {
|
|
82
|
+
let display = header('Post Preview (Dry Run)');
|
|
83
|
+
display += '\n\n';
|
|
84
|
+
|
|
85
|
+
for (const [ch, preview] of Object.entries(data.previews || {})) {
|
|
86
|
+
const icon = preview.configured ? 'โ
' : 'โ';
|
|
87
|
+
const canPost = preview.canWrite ? 'can post' : 'read-only';
|
|
88
|
+
|
|
89
|
+
display += `${icon} **${ch}** โ ${canPost}\n`;
|
|
90
|
+
|
|
91
|
+
if (!preview.configured) {
|
|
92
|
+
display += ` _Not configured_\n`;
|
|
93
|
+
} else if (preview.wouldTruncate) {
|
|
94
|
+
display += ` โ ๏ธ Content will be truncated to 280 chars\n`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
display += ` "${preview.content.slice(0, 100)}${preview.content.length > 100 ? '...' : ''}"\n\n`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
display += divider();
|
|
101
|
+
display += 'Remove `--dry_run` to post for real.';
|
|
102
|
+
|
|
103
|
+
return { display };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Actual post results
|
|
107
|
+
let display = header('Posted');
|
|
108
|
+
display += '\n\n';
|
|
109
|
+
|
|
110
|
+
if (warnings.length > 0) {
|
|
111
|
+
display += warning(warnings.join('\n')) + '\n\n';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let anySuccess = false;
|
|
115
|
+
for (const [ch, result] of Object.entries(data.results || {})) {
|
|
116
|
+
if (result.success) {
|
|
117
|
+
anySuccess = true;
|
|
118
|
+
display += `โ
**${ch}** โ Posted!\n`;
|
|
119
|
+
if (result.url) {
|
|
120
|
+
display += ` ๐ ${result.url}\n`;
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
display += `โ **${ch}** โ Failed: ${result.error}\n`;
|
|
124
|
+
}
|
|
125
|
+
display += '\n';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!anySuccess) {
|
|
129
|
+
display += '\n_No posts succeeded. Check channel configuration._';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { display };
|
|
133
|
+
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return {
|
|
136
|
+
display: `${header('Post Error')}\n\n_Error:_ ${e.message}`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = { definition, handler };
|