Greasy Fork is available in English.

VimGolf - Show recent challenges

Show recent challenges on the VimGolf homepage, pulled from the RSS feed.

// ==UserScript==
// @name         VimGolf - Show recent challenges
// @namespace    vimgolf-recent-challenges
// @version      0.1.0
// @description  Show recent challenges on the VimGolf homepage, pulled from the RSS feed.
// @author       Valacar
// @match        http://www.vimgolf.com/
// @match        https://www.vimgolf.com/
// @grant        GM_xmlhttpRequest
// @connect      vimgolf.com
// ==/UserScript==

(function() {
  "use strict";

  const NUM_CHALLENGES = 5;

  function appendStyle(cssString) {
    const parent = document.head || document.documentElement;
    if (parent) {
      const style = document.createElement("style");
      style.setAttribute("type", "text/css");
      style.textContent = cssString;
      parent.appendChild(style);
    }
  }

  appendStyle(`
  .challengeDate { font-size: 85%; padding-left: 0.5em; }
  .challengeDivider { border-style: dotted; border-color: #000; }
  `);

  function getFeed(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: url,
        onload: response => {
          if (response.status === 200) {
            resolve(response);
          } else {
            reject(Error(response.statusText));
          }
        },
        onerror: () => {
          reject(Error("Network Error"));
        }
      });
    });
  }

  function getXMLinfo(doc) {
    const challenges = [];
    const items = doc.querySelectorAll('item')
    //console.debug("xml items:", items);
    if (items) {
      let o = {};
      for (let item of items) {
        const title = item.querySelector('title');
        if (title) { o.title = title.textContent; }
        const description = item.querySelector('description');
        if (description) {
          let descriptionText = description.textContent;
          if (descriptionText.includes("<img")) {
            // chop off 1x1 pixel tracker and anything after
            descriptionText = descriptionText.split("<img")[0];
          }
          o.description = descriptionText;
        }
        const origLink = item.querySelector('origLink');
        if (origLink) { o.origLink = origLink.textContent; }
        const postTime = item.querySelector('pubDate');
        if (postTime) { o.postTime = postTime.textContent; }
        challenges.push(o);
        o = {};
      }
      //console.debug("challenges", challenges);
      return challenges;
    }
  }

  function addFeedChallenges(challenges, howMany) {
    // TODO: use Document Fragment to append everything in one batch
    const whereToAdd = document.querySelector("#challengeFeed");
    if (whereToAdd && challenges.length >= howMany) {
      for (let i = howMany; i; i--) {
        const date = new Date(challenges[i].postTime);
        const localeDate = date.toLocaleDateString();
        whereToAdd.insertAdjacentHTML("afterend", `
            <h5 class="challenge">
              <a href="${challenges[i].origLink}">${challenges[i].title}</a>
              <span class="challengeDate"> (Posted ${localeDate})</span>
            </h5>
            <p>${challenges[i].description}</p>\n
          `);
      }
    } else {
      console.debug("Didn't add Recent Challenges header?");
    }
  }

  // TODO: cache challenges
  const RSSfeed = "http://feeds.vimgolf.com/latest-challenges";
  getFeed(RSSfeed).then((response) => {
    let challenges;
    //console.debug(`response: ${response}`);
    if (response.responseHeaders.includes("text/xml")) {
      challenges = getXMLinfo(response.responseXML);
      // Add Recent Challenges header
      const insertionPoint = document.querySelector("#content h3:first-of-type");
      if (insertionPoint) {
        insertionPoint.insertAdjacentHTML("beforebegin", "<h3 id='challengeFeed'><b>Recent Challenges</b></h3>");
      }
      // Add a few challenges
      addFeedChallenges(challenges, NUM_CHALLENGES);
      // TODO: add [More] button o show more of the feed
      // Add divider
      insertionPoint.insertAdjacentHTML("beforebegin", '<hr class="challengeDivider">')
    } else {
      console.debug("Error, expected text/xml RSS feed\n", response.responseHeaders);
    }

  })
  .catch(function(error) {
    console.debug("Failed getting vimgolf feed!", error)
  });

})();