feat: slack lb

This commit is contained in:
Saahil 2025-01-16 22:23:41 -05:00
parent 011f2da2eb
commit d9e107fe8a
No known key found for this signature in database
GPG key ID: 8A8B64515254CFC6
6 changed files with 197 additions and 2 deletions

2
.gitignore vendored
View file

@ -145,3 +145,5 @@ tiktok.json
assets/list_of_slack_users.json
assets/dice/frames
t5.ts
s.csv
slack-users*

View file

@ -5,3 +5,5 @@ _this is a high seas project..._
- [ ] finish hangman
- [x] finish blind mail
- [ ] finish `docs/` and zeon canvas
- [ ] solve memory issues (maybe just maybe, graph it)
- - OR MIGRATE TO NOCODB HOLY

View file

@ -5,9 +5,10 @@
"author": "Saahil <neon@saahild.com>",
"license": "MIT",
"scripts": {
"start": "ts-node src/index.ts"
"start": "NODE_OPTIONS=\"--max-old-space-size=4096\" ts-node src/index.ts"
},
"dependencies": {
"@js-temporal/polyfill": "^0.4.4",
"@sentry/node": "^8.41.0",
"@sentry/profiling-node": "^8.41.0",
"@slack/bolt": "^3.21.2",

View file

@ -0,0 +1,121 @@
// sourced from skyfall :3
import { Temporal } from "@js-temporal/polyfill";
export enum Mode {
Last30Days,
LastYear,
AdrianMethod,
}
export interface MemberActivity {
user_id: string;
team_id: string;
username: string;
is_primary_owner: boolean;
is_owner: boolean;
is_admin: boolean;
is_restricted: boolean;
is_ultra_restricted: boolean;
is_invited_member: boolean;
is_invited_guest: boolean;
messages_posted_in_channel: number;
reactions_added: number;
days_active: number;
days_active_desktop: number;
days_active_android: number;
days_active_ios: number;
files_added_count: number;
days_active_apps: number;
// days_active_workflows: number;
// days_active_slack_connect: number;
// total_calls_count: number;
// slack_calls_count: number;
slack_huddles_count: number;
// search_count: number;
// is_billable_seat: boolean;
messages_posted: number;
date_claimed: number;
date_last_active: number;
date_last_active_ios: number;
date_last_active_android: number;
date_last_active_desktop: number;
// slack_huddles_count: z.number().nonnegative(), Always 0 on non-Enterprise plans
}
export interface AnalyticsResult {
ok: boolean;
num_found: number;
member_activity: MemberActivity[];
}
export async function fetchAnalyticsData(
// username: string,
xoxc: string,
xoxd: string,
workspace: string,
mode: Mode = Mode.Last30Days
): Promise<AnalyticsResult> {
const formData = new FormData();
formData.append("token", xoxc);
if (mode === Mode.Last30Days) {
// Last30Days handles only the last 30 days
console.log(`[DEBUG] Fetching analytics data for the last 30 days.`);
formData.append("date_range", "30d");
} else {
// For other modes (e.g., LastYear), calculate start and end dates
const currentDate = Temporal.Now.plainDateISO();
const oneWeekAgo = currentDate.subtract({ weeks: 1 });
const startDate = oneWeekAgo.subtract({ years: 1 });
// Subtract 4 days from both startDate and oneWeekAgo
const adjustedEndDate = oneWeekAgo.subtract({ days: 4 });
const adjustedStartDate = startDate.subtract({ days: 4 });
const formattedEndDate = adjustedEndDate.toString();
const formattedStartDate = adjustedStartDate.toString();
console.log(
`[DEBUG] Fetching analytics data from ${formattedStartDate} to ${formattedEndDate}`
);
formData.append("start_date", formattedStartDate);
formData.append("end_date", formattedEndDate);
}
formData.append("count", "1");
formData.append("sort_column", "messages_posted");
formData.append("sort_direction", "desc");
// formData.append("query", username);
formData.append("count", "5000");
const authCookie = `d=${xoxd}`;
const response = await fetch(
`https://${workspace}.slack.com/api/admin.analytics.getMemberAnalytics`,
{
method: "POST",
body: formData,
headers: {
Authority: `${workspace}.slack.com`,
Cookie: authCookie, // We don't really need anything fancy here.
},
}
);
const text = await response.text();
// console.log(text)
const json = JSON.parse(text);
// console.log(json)
const data = json as AnalyticsResult;
// if (!data.ok) {
// throw new Error("Failed to fetch analytics data");
// }
// if (data.num_found > 1) {
// console.warn(`[WARN] Found ${data.num_found} users`);
// }
// const member = data.member_activity.find(
// (member) => member.username === username
// );
// if (!member) {
// throw new Error(`User ${username} not found`);
// }
// return member;
return data;
}

56
src/modules/slackLb.ts Normal file
View file

@ -0,0 +1,56 @@
import { MemberActivity } from "./getSlackAnalytics";
export interface SlackDbEntry {
msgCount: number;
nonChannelMessages: number;
userID: string;
is_admin: boolean;
is_guest: boolean;
is_using_android: boolean;
is_using_ios: boolean;
is_using_desktop: boolean;
daysActive: number;
last_login: string;
timeRecorded: number;
}
export function formatSlackBadge(entry: SlackDbEntry):string {
let str = ""
if(entry.is_admin) str += `:tw_shield: `
if(entry.is_guest) str += `:eyes: `
if(entry.is_using_android) str += `:android: `
if(entry.is_using_ios) str += `:iphone: `
if(entry.is_using_desktop) str += `:laptopparrot: `
return str.trimEnd();
}
export function diffSlackLB(oldLB: SlackDbEntry[], newLB: SlackDbEntry[]) {
const msgs = [];
for (const entry of newLB) {
const oldEntry = oldLB.find((e) => e.userID === entry.userID);
if (!oldEntry) {
msgs.push(
`:yay: <@${entry.userID}> Welcome to the leaderboard joining us in #${
newLB.indexOf(entry) + 1
} place with 💬 \`${entry.msgCount}\` msgs. tags: ${formatSlackBadge(entry)}`,
);
continue;
}
const diff = entry.msgCount - oldEntry.msgCount;
// console.log(diff, entry.msgCount, oldEntry.msgCount)
let newRankMessage =
newLB.indexOf(entry) !== oldLB.findIndex((e) => e.userID == entry.userID)
? newLB.indexOf(entry) - oldLB.findIndex((e) => e.userID == entry.userID) > 0
? `You have moved down to #${newLB.indexOf(entry) + 1} from #${oldLB.findIndex((e) => e.userID == entry.userID) + 1} -- diff of ${newLB.indexOf(entry) - oldLB.findIndex((e) => e.userID == entry.userID)}, o: ${oldLB.findIndex((e) => e.userID == entry.userID)}, n: ${newLB.indexOf(entry)} (debug)`
: `You have moved up to #${newLB.indexOf(entry) + 1} from #${oldLB.findIndex((e) => e.userID == entry.userID) + 1} -- diff of ${newLB.indexOf(entry) - oldLB.findIndex((e) => e.userID == entry.userID)}, o: ${oldLB.findIndex((e) => e.userID == entry.userID)}, n: ${newLB.indexOf(entry)} (debug)`
: ``;
if (diff > 0) {
msgs.push(
`${newRankMessage ? (newRankMessage.includes("up") ? ":upvote:" : ":downvote:") : ""}:yay: ${entry.userID} (${formatSlackBadge(entry)}) You have gained \`${diff}\` 💬. ${newRankMessage ?? "No rank change"}`,
);
} else if (diff < 0) {
msgs.push(
`${newRankMessage ? (newRankMessage.includes("up") ? ":upvote:" : ":downvote:") : ""}:noooovanish: ${entry.userID} (${formatSlackBadge(entry)}) You lost \`${Math.abs(diff)}\` 💬. ${newRankMessage ?? "No rank change"}`,
);
}
}
return msgs;
}

View file

@ -27,6 +27,14 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@js-temporal/polyfill@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@js-temporal/polyfill/-/polyfill-0.4.4.tgz#4c26b4a1a68c19155808363f520204712cfc2558"
integrity sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==
dependencies:
jsbi "^4.3.0"
tslib "^2.4.1"
"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.11":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
@ -2765,6 +2773,11 @@ iterate-value@^1.0.2:
es-get-iterator "^1.0.2"
iterate-iterator "^1.0.1"
jsbi@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.0.tgz#b54ee074fb6fcbc00619559305c8f7e912b04741"
integrity sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==
jsep@^1.3.8:
version "1.4.0"
resolved "https://registry.yarnpkg.com/jsep/-/jsep-1.4.0.tgz#19feccbfa51d8a79f72480b4b8e40ce2e17152f0"
@ -4063,7 +4076,7 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.4.0, tslib@^2.6.2:
tslib@^2.4.0, tslib@^2.4.1, tslib@^2.6.2:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==