Focus Mode for Social Media

block time-wasting social media & other

// ==UserScript==
// @name        Focus Mode for Social Media
// @namespace   Violentmonkey Scripts
// @include     https://twitter.com/home
// @include     https://www.youtube.com*
// @include     https://www.instagram.com/
// @include     https://github.com/
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// @grant       GM_registerMenuCommand
// @grant       GM_unregisterMenuCommand
// @grant       GM_addStyle
// @grant       GM_xmlhttpRequest
// @version     2.0
// @license     MIT
// @author      KraXen72
// @description block time-wasting social media & other
// ==/UserScript==

const getStylesheetUrl = (hostname) => `https://kraxen72.github.io/focus-stylesheets/${hostname}.css`
const site = window.location.hostname
let styleSheetCSS = "";

// const resetSeconds = 30*60
const resetSeconds = 30*10

class Toggle {
  constructor(css, uuid, initialValue = true) {
    if (!css) throw new Error("Please provide CSS to inject!");
    this.css = css
    this.state = GM_getValue(`${site}_state`, initialValue)
    this.disabledTimestamp = GM_getValue(`${site}_disabled_timestamp`, -1)
    this.styleTag = null
    this.menuCommand = false;
    this.menuCommandId = uuid

    this.attemptReenable()
    this.sync()
  }
  enable() {
    this.state = true
    this.disabledTimestamp = -1

    this.sync()
  }
  disable() {
    this.state = false
    this.disabledTimestamp = new Date().getTime()

    this.sync()
  }
  toggle() {
    if (this.state) {
      this.disable()
    } else {
      this.enable()
    }
  }
  attemptReenable() {
    if (this.state) return;
    // if disabled but timestamp is -1, enable it to fix the edge-case.
    if ((new Date().getTime() - this.disabledTimestamp) <= resetSeconds*1000 && this.disabledTimestamp !== -1) {
      console.log("focusMode: resetSeconds has not elapsed yet")
      return;
    }
    console.log("focusMode: resetSeconds has elapsed, enabling.")
    this.state = true;
    this.disabledTimestamp = -1

    this.sync()
  }
  updateCSS() {
    if (this.state) {
      if (!this.styleTag) this.styleTag = GM_addStyle(this.css)
    } else {
      if (this.styleTag) this.styleTag.remove();
      this.styleTag = null
    }
  }
  saveData() {
    GM_setValue(`${site}_state`, this.state)
    if (this.disabledTimestamp === -1) {
      GM_deleteValue(`${site}_disabled_timestamp`)
    } else {
      GM_setValue(`${site}_disabled_timestamp`, this.disabledTimestamp)
    }
  }
  updateMenuCommand() {
    const caption = `focusMode: ${site}: ${this.state ? '✅' : '❌'}`
    if (this.menuCommand) {
      GM_unregisterMenuCommand(this.menuCommandId)
    }
    GM_registerMenuCommand(caption, () => this.toggle.apply(this), { id: this.menuCommandId })
    this.menuCommand = true;
  }
  sync() {
    this.saveData()
    this.updateCSS()
    this.updateMenuCommand()
  }
}

function sameDay(date1, date2) {
  return date1.getFullYear() === date2.getFullYear()
    && date1.getMonth() === date2.getMonth()
    && date1.getDate() === date2.getDate();
}

function zeropad(number, finalWidth, customCharacter) {
    customCharacter = customCharacter || '0';
    number = number + '';
    return number.length >= finalWidth ? number : new Array(finalWidth - number.length + 1).join(customCharacter) + number;
}

new Promise(async (res, rej) => {
  const lastFetchDate = new Date(GM_getValue("lastFetchDate", null))
  const currDate = new Date()
  const fetchedToday = sameDay(lastFetchDate, currDate)

  const storedCSS = GM_getValue("css_"+site, null)
  console.log(lastFetchDate, currDate)

  if (fetchedToday && storedCSS) {
    console.log("focusMode: already fetched today and found stored css. not fetching.")
    res(storedCSS)
  } else {
    const data = await new Promise((resolve, reject) => GM_xmlhttpRequest({
      url: getStylesheetUrl(site),
      method: 'GET',
      onabort: () => resolve(GM_getValue("css_"+site, "")),
      onerror: () => resolve(GM_getValue("css_"+site, "")),
      onload: (response) => resolve(response.responseText)
    }))
    console.log(`focusMode: fetched updated css for ${site}`)
    GM_setValue("lastFetchDate", new Date().toDateString())
    GM_setValue("css_"+site, resText)
    res(data)
  }
}).then(data => {
  let currDate = new Date()

  if (site === "www.youtube.com") {
    console.log("focusMode: yt override kicking in...")
    data = data.replaceAll("%focus%", (currDate.getHours() <= 6) ? `bro go to sleep please it's ${zeropad(currDate.getHours(), 2)}:${zeropad(currDate.getMinutes(), 2)}am` : "focus")
  }

  const toggleInstance = new Toggle(data, btoa(Math.round(Math.random()*100)))
  console.log("focusMode: initialized", toggleInstance, data)
})