enwnbot2

Converts MediaWiki [[links]] and {{templates}} to links, informs important events from wiki, handles announces review queue, and under review, and handles when they last saw a given user.
git clone http://git.hanabi.in/repos/enwnbot2.git
Log | Files | Refs | README | LICENSE

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 }