Fetch Slack History
Fetch message history from a Slack channel or DM
Source Code
import fs from 'fs';
import path from 'path';
const [channelId, limit = '50', outputPath] = process.argv.slice(2);
const maxMessages = Math.min(parseInt(limit) || 50, 200);
if (!channelId) {
console.error('Error: channelId is required');
process.exit(1);
}
if (!outputPath) {
console.error('Error: outputPath is required');
process.exit(1);
}
console.log(`Fetching up to ${maxMessages} messages from ${channelId}...`);
try {
// Fetch channel info and users in parallel
const [channelInfo, userMap] = await Promise.all([
(async () => {
const params = new URLSearchParams({ channel: channelId });
const res = await fetch('https://slack.com/api/conversations.info?' + params, {
headers: { 'Authorization': 'Bearer PLACEHOLDER_TOKEN' }
});
const data = await res.json();
if (!data.ok) return { id: channelId, name: null };
const c = data.channel;
return {
id: c.id,
name: c.name || null,
is_private: c.is_private,
is_im: c.is_im,
is_mpim: c.is_mpim,
};
})(),
(async () => {
const users = new Map();
let cursor = null;
do {
const params = new URLSearchParams({ limit: '200' });
if (cursor) params.set('cursor', cursor);
const res = await fetch('https://slack.com/api/users.list?' + params, {
headers: { 'Authorization': 'Bearer PLACEHOLDER_TOKEN' }
});
const data = await res.json();
if (!data.ok) break;
for (const u of data.members || []) {
users.set(u.id, {
id: u.id,
name: u.profile?.display_name || u.real_name || u.name,
real_name: u.real_name,
});
}
cursor = data.response_metadata?.next_cursor;
} while (cursor);
return users;
})(),
]);
console.log(`Channel: ${channelInfo.name ? '#' + channelInfo.name : channelId}`);
console.log(` ${userMap.size} users loaded`);
// Fetch messages
const messages = [];
let cursor = null;
while (messages.length < maxMessages) {
const remaining = maxMessages - messages.length;
const params = new URLSearchParams({
channel: channelId,
limit: Math.min(remaining, 100).toString(),
});
if (cursor) params.set('cursor', cursor);
const res = await fetch('https://slack.com/api/conversations.history?' + params, {
headers: { 'Authorization': 'Bearer PLACEHOLDER_TOKEN' }
});
const data = await res.json();
if (!data.ok) throw new Error(`conversations.history failed: ${data.error}`);
messages.push(...(data.messages || []));
cursor = data.response_metadata?.next_cursor;
if (!cursor || data.messages?.length === 0) break;
}
console.log(`Fetched ${messages.length} messages`);
// Transform messages
const transformed = messages.slice(0, maxMessages).map(msg => {
const user = userMap.get(msg.user) || { name: msg.user || 'unknown' };
return {
ts: msg.ts,
user_id: msg.user,
user_name: user.name,
text: msg.text,
thread_ts: msg.thread_ts,
reply_count: msg.reply_count,
reactions: msg.reactions?.map(r => ({ name: r.name, count: r.count })),
attachments: msg.attachments?.length || 0,
blocks: msg.blocks?.length || 0,
time: new Date(parseFloat(msg.ts) * 1000).toISOString(),
};
});
// Write results
const dir = path.dirname(outputPath);
if (dir && dir !== '.') fs.mkdirSync(dir, { recursive: true });
const output = {
channel: channelInfo,
fetchedAt: new Date().toISOString(),
count: transformed.length,
messages: transformed,
};
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
console.log(`\n✓ Messages written to: ${outputPath}`);
if (transformed.length > 0) {
const oldest = transformed[transformed.length - 1];
const newest = transformed[0];
console.log(` Time range: ${oldest.time.split('T')[0]} to ${newest.time.split('T')[0]}`);
console.log(`\n Recent messages:`);
transformed.slice(0, 5).forEach(m => {
const preview = m.text?.slice(0, 60) || '(no text)';
console.log(` ${m.user_name}: ${preview}${m.text?.length > 60 ? '...' : ''}`);
});
}
console.log(JSON.stringify({
success: true,
outputPath,
channel: channelInfo.name || channelId,
count: transformed.length,
}));
} catch (error) {
console.error('Failed to fetch history:', error.message);
throw error;
} import fs from 'fs';
import path from 'path';
const [channelId, limit = '50', outputPath] = process.argv.slice(2);
const maxMessages = Math.min(parseInt(limit) || 50, 200);
if (!channelId) {
console.error('Error: channelId is required');
process.exit(1);
}
if (!outputPath) {
console.error('Error: outputPath is required');
process.exit(1);
}
console.log(`Fetching up to ${maxMessages} messages from ${channelId}...`);
try {
// Fetch channel info and users in parallel
const [channelInfo, userMap] = await Promise.all([
(async () => {
const params = new URLSearchParams({ channel: channelId });
const res = await fetch('https://slack.com/api/conversations.info?' + params, {
headers: { 'Authorization': 'Bearer PLACEHOLDER_TOKEN' }
});
const data = await res.json();
if (!data.ok) return { id: channelId, name: null };
const c = data.channel;
return {
id: c.id,
name: c.name || null,
is_private: c.is_private,
is_im: c.is_im,
is_mpim: c.is_mpim,
};
})(),
(async () => {
const users = new Map();
let cursor = null;
do {
const params = new URLSearchParams({ limit: '200' });
if (cursor) params.set('cursor', cursor);
const res = await fetch('https://slack.com/api/users.list?' + params, {
headers: { 'Authorization': 'Bearer PLACEHOLDER_TOKEN' }
});
const data = await res.json();
if (!data.ok) break;
for (const u of data.members || []) {
users.set(u.id, {
id: u.id,
name: u.profile?.display_name || u.real_name || u.name,
real_name: u.real_name,
});
}
cursor = data.response_metadata?.next_cursor;
} while (cursor);
return users;
})(),
]);
console.log(`Channel: ${channelInfo.name ? '#' + channelInfo.name : channelId}`);
console.log(` ${userMap.size} users loaded`);
// Fetch messages
const messages = [];
let cursor = null;
while (messages.length < maxMessages) {
const remaining = maxMessages - messages.length;
const params = new URLSearchParams({
channel: channelId,
limit: Math.min(remaining, 100).toString(),
});
if (cursor) params.set('cursor', cursor);
const res = await fetch('https://slack.com/api/conversations.history?' + params, {
headers: { 'Authorization': 'Bearer PLACEHOLDER_TOKEN' }
});
const data = await res.json();
if (!data.ok) throw new Error(`conversations.history failed: ${data.error}`);
messages.push(...(data.messages || []));
cursor = data.response_metadata?.next_cursor;
if (!cursor || data.messages?.length === 0) break;
}
console.log(`Fetched ${messages.length} messages`);
// Transform messages
const transformed = messages.slice(0, maxMessages).map(msg => {
const user = userMap.get(msg.user) || { name: msg.user || 'unknown' };
return {
ts: msg.ts,
user_id: msg.user,
user_name: user.name,
text: msg.text,
thread_ts: msg.thread_ts,
reply_count: msg.reply_count,
reactions: msg.reactions?.map(r => ({ name: r.name, count: r.count })),
attachments: msg.attachments?.length || 0,
blocks: msg.blocks?.length || 0,
time: new Date(parseFloat(msg.ts) * 1000).toISOString(),
};
});
// Write results
const dir = path.dirname(outputPath);
if (dir && dir !== '.') fs.mkdirSync(dir, { recursive: true });
const output = {
channel: channelInfo,
fetchedAt: new Date().toISOString(),
count: transformed.length,
messages: transformed,
};
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
console.log(`\n✓ Messages written to: ${outputPath}`);
if (transformed.length > 0) {
const oldest = transformed[transformed.length - 1];
const newest = transformed[0];
console.log(` Time range: ${oldest.time.split('T')[0]} to ${newest.time.split('T')[0]}`);
console.log(`\n Recent messages:`);
transformed.slice(0, 5).forEach(m => {
const preview = m.text?.slice(0, 60) || '(no text)';
console.log(` ${m.user_name}: ${preview}${m.text?.length > 60 ? '...' : ''}`);
});
}
console.log(JSON.stringify({
success: true,
outputPath,
channel: channelInfo.name || channelId,
count: transformed.length,
}));
} catch (error) {
console.error('Failed to fetch history:', error.message);
throw error;
}