enwnbot

Converts MediaWiki [[links]] and {{templates}} to links on IRC
git clone http://git.hanabi.in/repos/enwnbot.git
Log | Files | Refs | README | LICENSE

irc.js (5669B)


      1 const irc = require("irc");
      2 const fetch = require("node-fetch");
      3 const moment = require("moment-timezone");
      4 
      5 const {
      6   admins,
      7   botName,
      8   channels,
      9   maintainers,
     10   report,
     11   RQAPI,
     12   server,
     13   URAPI
     14 } = require("./config");
     15 
     16 const {
     17   fetchData,
     18   fullUrl,
     19   getFullLink,
     20   getFullTemplate,
     21   sayTime,
     22   setthis
     23 } = require("./utils");
     24 
     25 const { fallback, reset, short } = require("./promUrlShortener");
     26 
     27 const client = new irc.Client(server, botName, {
     28   channels
     29 });
     30 
     31 function pm(sender, msg) {
     32   client.say(sender, "I am a bot.");
     33   if (msg.startsWith(report))
     34     admins.forEach(admin =>
     35       client.say(admin, `Message from ${sender}: ${msg}`)
     36     );
     37   if(msg != 'KILL')  return;                                                    
     38   if(['pizero', 'pizero|afk', 'acagastya'].indexOf(from) < 0)  return;
     39   process.abort();                                                              
     40 }
     41 
     42 function err(msg) {
     43   maintainers.forEach(maintainer =>
     44     client.say(maintainer, `Error: ${JSON.stringify(msg)}`)
     45   );
     46 }
     47 
     48 async function announceRQ(sender, channel) {
     49   const data = await fetchData(RQAPI);
     50   if (data.error)
     51     client.say(
     52       channel,
     53       `Error occurred, ${sender}.  Try this instead: "[[CAT:REV]]"`
     54     );
     55   else {
     56     const { list } = data;
     57     if (!list.length) client.say(channel, `Review queue is empty, ${sender}.`);
     58     else {
     59       client.say(
     60         channel,
     61         `${list.length} articles to review, ${sender}.  They are:`
     62       );
     63       const titles = list.map(({ title }) => title);
     64       const times = list.map(({ timestamp }) => moment().to(moment(timestamp)));
     65       const urls = titles.map(fullUrl);
     66       client.say(channel, "(Hold on a sec...  Shortening the URLs.)");
     67       sayShortUrls(true, urls, channel, titles, times);
     68     }
     69   }
     70 }
     71 async function announceUR(sender, channel) {
     72   const data = await fetchData(URAPI);
     73   if (data.error)
     74     client.say(
     75       channel,
     76       `Error occurred, ${sender}.  Try this instead: "[[CAT:Under Review]]"`
     77     );
     78   else {
     79     const { list } = data;
     80     if (!list.length)
     81       client.say(channel, `No articles are under review, ${sender}.`);
     82     else {
     83       client.say(
     84         channel,
     85         `${list.length} articles are under review, ${sender}.  They are:`
     86       );
     87       const titles = list.map(({ title }) => title);
     88       const times = list.map(({ timestamp }) => moment().to(moment(timestamp)));
     89       const urls = titles.map(fullUrl);
     90       client.say(channel, "(Hold on a sec...  Shortening the URLs.)");
     91       sayShortUrls(true, urls, channel, titles, times);
     92     }
     93   }
     94 }
     95 
     96 async function sayShortUrls(
     97   review = false,
     98   urlList,
     99   channel,
    100   titles = [],
    101   times = [],
    102   pending = []
    103 ) {
    104   const shortUrls = await Promise.all(urlList.map(short));
    105 
    106   shortUrls.forEach(({ url, err }, idx) => {
    107     if (!err) {
    108       let msg = url;
    109       if (review) msg += " submitted for review";
    110       if (times.length) msg += ` *${times[idx]}*`;
    111       if (titles.length) msg += ` -- ${titles[idx]}`;
    112       if (pending[idx]) msg += " *under review*";
    113       client.say(channel, msg);
    114     } else console.log(err);
    115   });
    116 }
    117 
    118 function groupChat(sender, channel, msg) {
    119   const thanksRegex = new RegExp(`thanks,? ${botName}`, "i");
    120   if (thanksRegex.test(msg)) client.say(channel, `You are welcome, ${sender}.`);
    121   if (msg.includes(`${botName} !RQ`)) announceRQ(sender, channel);
    122   if (msg.includes(`${botName} !UR`)) announceUR(sender, channel);
    123   if (msg.includes(`${botName} !FB`)) fallback();
    124   if (msg.includes(`${botName} !TRY`)) reset();
    125   if (msg.includes(`${botName} !SET`)) setthis(sender, channel, msg, client);
    126   if (msg.includes(`${botName} !time`)) sayTime(msg, client, channel);
    127   const regex1 = /\[{2}(.*?)\]{2}/g;
    128   const regex2 = /\{{2}(.*?)\}{2}/g;
    129   const links = msg.match(regex1);
    130   const templates = msg.match(regex2);
    131   if (links) {
    132     const nonEmptyLink = links.filter(el => el.length > 4);
    133     const fullLinks = nonEmptyLink.map(getFullLink);
    134     if (fullLinks.length) sayShortUrls(false, fullLinks, channel);
    135   }
    136   if (!msg.endsWith("--nl") && templates) {
    137     const nonEmptyTl = templates.filter(el => el.length > 4);
    138     const fullLinks = nonEmptyTl.map(getFullTemplate);
    139     if (fullLinks.length) sayShortUrls(false, fullLinks, channel);
    140   }
    141 }
    142 
    143 client.addListener("error", err);
    144 client.addListener("pm", pm);
    145 client.addListener("message", groupChat);
    146 
    147 const submittedState = {
    148   announced: []
    149 };
    150 
    151 async function announceSubmitted() {
    152   const res = await fetch(RQAPI);
    153   const parsed = await res.json();
    154 
    155   const underReview = await fetch(URAPI);
    156   const urParsed = await underReview.json();
    157   const urTitles = urParsed.query.categorymembers.map(({ title }) => title);
    158 
    159   const titleTime = parsed.query.categorymembers.map(({ title, timestamp }) => {
    160     return { timestamp, title };
    161   });
    162   const allTitles = parsed.query.categorymembers.map(({ title }) => title);
    163   const pending = titleTime.filter(
    164     ({ title }) => !submittedState.announced.includes(title)
    165   );
    166   const pendingRev = pending.map(el => urTitles.includes(el.title));
    167   const titles = pending.map(({ title }) => title);
    168   submittedState.announced = [...allTitles];
    169   const urls = titles.map(fullUrl);
    170   const times = pending.map(({ timestamp }) => moment().to(moment(timestamp)));
    171 
    172   if (urls.length) {
    173     channels.forEach(channel => client.say(channel, "Review Queue:"));
    174     channels.forEach(channel =>
    175       sayShortUrls(true, urls, channel, titles, times, pendingRev)
    176     );
    177   }
    178 }
    179 
    180 const submittedPeriod = 5 * 60 * 1000;
    181 const cacheClearPeriod = 6 * 60 * 60 * 1000;
    182 
    183 setInterval(announceSubmitted, submittedPeriod);
    184 setInterval(() => {
    185   submittedState.announced = [];
    186 }, cacheClearPeriod);