Fetch Slack Channel History
Fetch message history from a Slack channel with user resolution
Source Code
import fs from "fs";
import path from "path";
const [channelId, outputPath, limit = "100", daysBack = "7"] = process.argv.slice(2);
if (!channelId || !outputPath) {
console.error("Usage: slack.core.history <channelId> <outputPath> [limit] [daysBack]");
process.exit(1);
}
const maxMessages = Math.min(parseInt(limit) || 100, 500);
const daysBackNum = Math.min(parseInt(daysBack) || 7, 90);
const oldestTs = Math.floor((Date.now() - daysBackNum * 24 * 60 * 60 * 1000) / 1000);
console.log(`Fetching history from ${channelId} (last ${daysBackNum} days, max ${maxMessages} messages)...`);
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,
isPrivate: c.is_private,
isIm: c.is_im,
isMpim: 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,
realName: 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 with pagination
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(),
oldest: oldestTs.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 with user resolution
const transformed = messages.slice(0, maxMessages).map(msg => {
const user = userMap.get(msg.user) || { name: msg.user || "unknown" };
const ts = parseFloat(msg.ts);
const date = new Date(ts * 1000);
return {
ts: msg.ts,
userId: msg.user,
userName: user.name,
text: msg.text,
threadTs: msg.thread_ts,
replyCount: msg.reply_count,
reactions: msg.reactions?.map(r => ({ name: r.name, count: r.count })),
attachments: msg.attachments?.length || 0,
blocks: msg.blocks?.length || 0,
timestamp: date.toISOString(),
hour: date.getHours(),
dayOfWeek: date.getDay(),
subtype: msg.subtype,
};
});
// Filter out system messages for stats
const userMessages = transformed.filter(m => !m.subtype && m.userId);
// Compute stats
const userCounts = {};
for (const m of userMessages) {
userCounts[m.userName] = (userCounts[m.userName] || 0) + 1;
}
const topPosters = Object.entries(userCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([name, count]) => ({ name, count }));
// Write output
const dir = path.dirname(outputPath);
if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
const output = {
channel: channelInfo,
fetchedAt: new Date().toISOString(),
daysBack: daysBackNum,
count: transformed.length,
userMessageCount: userMessages.length,
topPosters,
messages: transformed,
};
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
console.log(`\n✓ Messages written to: ${outputPath}`);
if (userMessages.length > 0) {
const oldest = transformed[transformed.length - 1];
const newest = transformed[0];
console.log(` Time range: ${oldest.timestamp.split("T")[0]} to ${newest.timestamp.split("T")[0]}`);
console.log(` Top posters:`);
topPosters.forEach(p => console.log(` - ${p.name}: ${p.count} messages`));
}
console.log(JSON.stringify({
success: true,
outputPath,
channel: channelInfo.name || channelId,
count: transformed.length,
userMessageCount: userMessages.length,
}));
} catch (error) {
console.error("Failed:", error.message);
throw error;
}