index.js (5720B)
1 const ES = require("eventsource"); 2 3 const irc = require("irc"); 4 5 const moment = require("moment-timezone"); 6 7 const { 8 channels, 9 ircBotName, 10 ircServer, 11 password, 12 RCAPI, 13 RQAPI, 14 URAPI, 15 } = require("./config.js"); 16 17 const { 18 fetchData, 19 fullUrl, 20 getFullLink, 21 getFullTemplate, 22 linkRegex, 23 streamError, 24 streamMessage, 25 templateRegex, 26 thanksRegex, 27 } = require("./utils.js"); 28 29 const seenLookup = {}; 30 31 const ircClient = new irc.Client(ircServer, ircBotName, { 32 channels, 33 userName: "enwnbot", 34 realName: "enwnbot", 35 sasl: true, 36 password, 37 floodProtection: true, 38 floodProtectionDelay: 1000, 39 }); 40 41 console.log("Connecting to the event stream..."); 42 43 const eventSource = new ES(RCAPI); 44 45 eventSource.onopen = function (event) { 46 console.log("--- Opened connection."); 47 }; 48 49 eventSource.onerror = streamError; 50 51 eventSource.onmessage = function (event) { 52 let msg = streamMessage(event); 53 if (msg) channels.forEach((channel) => ircClient.say(channel, msg)); 54 }; 55 56 ircClient.addListener("error", function (message) { 57 console.log("error: ", message); 58 }); 59 60 ircClient.addListener("pm", function (sender, msg) { 61 if ( 62 msg == "KILL" && 63 ["pizero", "pizero|afk", "acagastya"].indexOf(sender) >= 0 64 ) 65 process.abort(); 66 ircClient.say(sender, "I am a bot."); 67 }); 68 69 ircClient.addListener("message", groupChat); 70 71 ircClient.addListener("names", getNames); 72 73 ircClient.addListener("nick", handleNickChange); 74 75 ircClient.addListener("join", handleJoin); 76 77 ircClient.addListener("part", handlePart); 78 79 ircClient.addListener("quit", handleQuit); 80 81 function handleJoin(channel, nick) { 82 if (seenLookup[channel] === undefined) seenLookup[channel] = {}; 83 seenLookup[channel][nick] = ""; 84 } 85 86 function getNames(channel, nicks) { 87 seenLookup[channel] = { ...nicks }; 88 Object.keys(seenLookup[channel]).forEach( 89 (nick) => (seenLookup[channel][nick] = "") 90 ); 91 } 92 93 function handlePart(channel, nick) { 94 if (seenLookup[channel] === undefined) seenLookup[channel] = {}; 95 seenLookup[channel][nick] = +new Date(); 96 } 97 98 function handleQuit(nick, reason, channels) { 99 channels.forEach((channel) => { 100 if (seenLookup[channel] !== undefined) { 101 seenLookup[channel][nick] = +new Date(); 102 } 103 }); 104 } 105 106 function handleNickChange(oldNick, newNick, channels) { 107 channels.forEach((channel) => { 108 if (seenLookup[channel] !== undefined) { 109 seenLookup[channel][oldNick] = +new Date(); 110 seenLookup[channel][newNick] = ""; 111 } 112 }); 113 } 114 115 function groupChat(sender, channel, msg) { 116 if (thanksRegex.test(msg)) 117 ircClient.say(channel, `You are welcome, ${sender}.`); 118 if (msg.includes(`${ircBotName} !RQ`)) announceRQ(sender, channel); 119 if (msg.includes(`${ircBotName} !UR`)) announceUR(sender, channel); 120 if (msg.startsWith("@seen ")) handleSeen(channel, msg); 121 122 const links = msg.match(linkRegex); 123 const templates = msg.match(templateRegex); 124 125 if (!msg.endsWith("--ignore") && links) { 126 const nonEmptyLinks = links.filter((el) => el.length > 4); 127 const fullLinks = nonEmptyLinks.map(getFullLink); 128 if (fullLinks.length) sayUrls(false, fullLinks, channel); 129 } 130 131 if (!msg.endsWith("--ignore") && templates) { 132 const nonEmptyTemplates = templates.filter((el) => el.length > 4); 133 const fullLinks = nonEmptyTemplates.map(getFullTemplate); 134 if (fullLinks.length) sayUrls(false, fullLinks, channel); 135 } 136 } 137 138 async function announceRQ(sender, channel) { 139 const data = await fetchData(RQAPI); 140 141 if (data.error) 142 ircClient.say( 143 channel, 144 `Error occurred, ${sender}. Try this instead: "[[CAT:REV]]"` 145 ); 146 else { 147 const { list } = data; 148 if (!list.length) 149 ircClient.say(channel, `Review queue is empty, ${sender}.`); 150 else { 151 ircClient.say( 152 channel, 153 `${list.length} articles to review, ${sender}. They are:` 154 ); 155 const titles = list.map(({ title }) => title); 156 const times = list.map(({ timestamp }) => moment().to(moment(timestamp))); 157 const urls = titles.map(fullUrl); 158 sayUrls(true, urls, channel, titles, times); 159 } 160 } 161 } 162 163 async function announceUR(sender, channel) { 164 const data = await fetchData(URAPI); 165 166 if (data.error) 167 ircClient.say( 168 channel, 169 `Error occurred, ${sender}. Try this instead: "[[CAT:Under Review]]"` 170 ); 171 else { 172 const { list } = data; 173 if (!list.length) 174 ircClient.say(channel, `No articles are under review, ${sender}.`); 175 else { 176 ircClient.say( 177 channel, 178 `${list.length} articles are under review, ${sender}. They are:` 179 ); 180 const titles = list.map(({ title }) => title); 181 const times = list.map(({ timestamp }) => moment().to(moment(timestamp))); 182 const urls = titles.map(fullUrl); 183 sayUrls(true, urls, channel, titles, times); 184 } 185 } 186 } 187 188 function handleSeen(channel, msg) { 189 const username = msg.split("@seen ")[1]; 190 if (seenLookup[channel] === undefined) return; 191 if (seenLookup[channel][username] === undefined) { 192 ircClient.say(channel, `I have never seen ${username} on ${channel}.`); 193 return; 194 } 195 if (seenLookup[channel][username] === "") { 196 ircClient.say(channel, `${username} is online.`); 197 return; 198 } 199 const time = new Date(seenLookup[channel][username]).toUTCString(); 200 ircClient.say( 201 channel, 202 `${username} was last onilne on ${channel} at ${time}.` 203 ); 204 } 205 206 function sayUrls( 207 review = false, 208 urlList, 209 channel, 210 titles = [], 211 times = [], 212 pending = [] 213 ) { 214 urlList.forEach((url, idx) => { 215 let msg = url; 216 if (review) msg += " submitted for review"; 217 if (times.length) msg += ` *${times[idx]}*`; 218 if (titles.length) msg += ` -- ${titles[idx]}`; 219 if (pending[idx]) msg += " *under review*"; 220 ircClient.say(channel, msg); 221 }); 222 }