@@ -2,146 +2,124 @@ import express from "express";
22import fetch from "node-fetch" ;
33import tmi from "tmi.js" ;
44import { WebSocketServer } from "ws" ;
5- import fs from "fs " ;
5+ import path from "path " ;
66
77const app = express ( ) ;
88const PORT = 3000 ;
9- const DATA_FILE = "./data.json" ;
9+ const __dirname = process . cwd ( ) ;
1010
11- let allTimeData = { } ;
12- if ( fs . existsSync ( DATA_FILE ) ) {
13- try {
14- allTimeData = JSON . parse ( fs . readFileSync ( DATA_FILE , "utf8" ) ) ;
15- } catch {
16- allTimeData = { } ;
17- }
18- } else {
19- fs . writeFileSync ( DATA_FILE , "{}" ) ;
20- }
21-
22- const activeChannels = { } ; // { channel: { emotes, users, client } }
11+ const activeChannels = { } ; // { channel: { emotes:[], users:{}, emoteUsage:{} } }
2312
2413app . use ( express . static ( "public" ) ) ;
2514
26- // Save leaderboard data every minute
27- setInterval ( ( ) => {
28- fs . writeFileSync ( DATA_FILE , JSON . stringify ( allTimeData , null , 2 ) ) ;
29- } , 60000 ) ;
30-
31- // Fetch 7TV emotes for a channel
15+ // 🔹 Get 7TV emotes for a channel
3216async function get7TVEmotes ( channel ) {
3317 try {
3418 const res = await fetch ( `https://7tv.io/v3/users/twitch/${ channel } ` ) ;
35- if ( ! res . ok ) return [ ] ;
3619 const data = await res . json ( ) ;
37- return (
38- data . emote_set ?. emotes ?. map ( ( e ) => ( {
39- name : e . name ,
40- id : e . id ,
41- } ) ) || [ ]
42- ) ;
43- } catch {
20+ return data . emote_set ?. emotes ?. map ( e => ( {
21+ name : e . name ,
22+ id : e . id ,
23+ url : `https://cdn.7tv.app/emote/ ${ e . id } /4x.webp`
24+ } ) ) || [ ] ;
25+ } catch ( e ) {
26+ console . log ( "7TV fetch failed:" , e ) ;
4427 return [ ] ;
4528 }
4629}
4730
48- // Get Twitch/7TV user info
31+ // 🔹 Get Twitch user info
4932async function getTwitchUser ( username ) {
5033 try {
5134 const res = await fetch ( `https://7tv.io/v3/users/twitch/${ username } ` ) ;
5235 if ( ! res . ok ) return null ;
5336 const data = await res . json ( ) ;
5437 return {
55- id : data . id || username ,
56- avatar : data . avatar_url || null ,
57- paint : data . style ?. paint || null ,
38+ id : data . id ,
39+ avatar : data . avatar_url || "https://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_70x70.png" ,
40+ paint : data . style ?. paint_id || null
5841 } ;
5942 } catch {
6043 return null ;
6144 }
6245}
6346
64- // Start tracking a Twitch channel
47+ // 🔹 Connect and track emotes in a channel
6548async function startTracking ( channel ) {
6649 if ( activeChannels [ channel ] ) return activeChannels [ channel ] ;
67- console . log ( `🟣 Tracking started for # ${ channel } ` ) ;
50+ console . log ( `Starting tracking for ${ channel } ` ) ;
6851
6952 const emotes = await get7TVEmotes ( channel ) ;
70- const users = allTimeData [ channel ] || { } ;
53+ const users = { } ;
54+ const emoteUsage = { } ;
7155
7256 const client = new tmi . Client ( {
7357 connection : { reconnect : true } ,
74- channels : [ channel ] ,
58+ channels : [ channel ]
7559 } ) ;
7660
7761 client . connect ( ) ;
7862
7963 client . on ( "message" , async ( ch , tags , msg , self ) => {
8064 if ( self ) return ;
81-
8265 const username = tags [ "display-name" ] || tags . username ;
66+
8367 if ( ! users [ username ] ) {
8468 const info = await getTwitchUser ( username ) ;
8569 users [ username ] = {
8670 username,
8771 id : info ?. id || username ,
88- avatar :
89- info ?. avatar ||
90- "https://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_70x70.png" ,
91- paint : info ?. paint || null ,
92- count : 0 ,
93- emoteUsage : { } ,
72+ avatar : info ?. avatar ,
73+ paint : info ?. paint ,
74+ count : 0
9475 } ;
9576 }
9677
9778 const words = msg . split ( / \s + / ) ;
98- const used = emotes . filter ( ( e ) => words . includes ( e . name ) ) ;
79+ const used = emotes . filter ( e => words . includes ( e . name ) ) ;
80+
9981 if ( used . length > 0 ) {
10082 for ( const e of used ) {
10183 users [ username ] . count ++ ;
102- users [ username ] . emoteUsage [ e . name ] =
103- ( users [ username ] . emoteUsage [ e . name ] || 0 ) + 1 ;
84+ emoteUsage [ e . name ] = ( emoteUsage [ e . name ] || 0 ) + 1 ;
10485 }
105- allTimeData [ channel ] = users ;
106- broadcast ( channel , { type : "update" , user : users [ username ] } ) ;
86+ broadcast ( channel , { type : "update" , user : users [ username ] , emoteUsage } ) ;
10787 }
10888 } ) ;
10989
110- activeChannels [ channel ] = { emotes, users, client } ;
111- allTimeData [ channel ] = users ;
90+ activeChannels [ channel ] = { emotes, users, emoteUsage, client } ;
11291 return activeChannels [ channel ] ;
11392}
11493
115- // WebSocket setup
94+ // 🔹 WebSocket setup
11695const wss = new WebSocketServer ( { noServer : true } ) ;
11796const sockets = new Set ( ) ;
11897
11998function broadcast ( channel , data ) {
12099 const msg = JSON . stringify ( { channel, ...data } ) ;
121- for ( const ws of sockets ) {
122- if ( ws . readyState === ws . OPEN ) ws . send ( msg ) ;
123- }
100+ for ( const ws of sockets ) ws . send ( msg ) ;
124101}
125102
126- const server = app . listen ( PORT , ( ) =>
127- console . log ( `✅ Running at http://localhost:${ PORT } ` )
128- ) ;
103+ const server = app . listen ( PORT , ( ) => {
104+ console . log ( `✅ Running at http://localhost:${ PORT } ` ) ;
105+ } ) ;
129106
130107server . on ( "upgrade" , ( req , socket , head ) => {
131- wss . handleUpgrade ( req , socket , head , ( ws ) => {
108+ wss . handleUpgrade ( req , socket , head , ws => {
132109 sockets . add ( ws ) ;
133110 ws . on ( "close" , ( ) => sockets . delete ( ws ) ) ;
134111 } ) ;
135112} ) ;
136113
137- // API routes
138- app . get ( "/api/start /:channel" , async ( req , res ) => {
114+ // 🔹 API Routes
115+ app . get ( "/api/channel /:channel" , async ( req , res ) => {
139116 const channel = req . params . channel . toLowerCase ( ) ;
140- await startTracking ( channel ) ;
141- res . json ( { success : true } ) ;
117+ const tracker = await startTracking ( channel ) ;
118+ res . json ( {
119+ emotes : tracker . emotes ,
120+ users : Object . values ( tracker . users ) ,
121+ emoteUsage : tracker . emoteUsage
122+ } ) ;
142123} ) ;
143124
144- app . get ( "/api/leaderboard/:channel" , async ( req , res ) => {
145- const channel = req . params . channel . toLowerCase ( ) ;
146- res . json ( Object . values ( allTimeData [ channel ] || { } ) ) ;
147- } ) ;
125+ app . use ( express . static ( path . join ( __dirname , "public" ) ) ) ;
0 commit comments