[雪星实验室] Google Calendar 谷歌日历自动上色

【功能测试中, bug反馈:[email protected]】Google日历自动上色、根据匹配到的关键词显示特定颜色,例如: 休|睡、洗漱|收拾|整理|日记|日志、研究|学习|探索|背词|了解、上学|上班|上课、健身|锻练|热身、路上|通勤、料理|做饭、仪式|典礼|祭祀、紧急|重要|考试|测验、群聊|交流|玩|游戏|知乎、电影|看书|阅书|影评,(20210709)加入英文支持

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name            [雪星实验室] Google Calendar 谷歌日历自动上色
// @name:zh         [雪星实验室] Google Calendar 谷歌日历自动上色
// @name:en         [SNOLAB] Google Calendar Colorize
// @namespace       https://userscript.snomiao.com/
// @version         0.1.4
// @description    【功能测试中, bug反馈:[email protected]】Google日历自动上色、根据匹配到的关键词显示特定颜色,例如: 休|睡、洗漱|收拾|整理|日记|日志、研究|学习|探索|背词|了解、上学|上班|上课、健身|锻练|热身、路上|通勤、料理|做饭、仪式|典礼|祭祀、紧急|重要|考试|测验、群聊|交流|玩|游戏|知乎、电影|看书|阅书|影评,(20210709)加入英文支持
// @description:zh 【功能测试中, bug反馈:[email protected]】Google日历自动上色、根据匹配到的关键词显示特定颜色,例如: 休|睡、洗漱|收拾|整理|日记|日志、研究|学习|探索|背词|了解、上学|上班|上课、健身|锻练|热身、路上|通勤、料理|做饭、仪式|典礼|祭祀、紧急|重要|考试|测验、群聊|交流|玩|游戏|知乎、电影|看书|阅书|影评,(20210709)加入英文支持
// @description:en 【Functional testing, bug feedback: [email protected]】Google Calendar automatically color, according to the keywords matched to show specific colors, such as: rest|sleep, wash|pack|organize|diary|journal, research|study|explore|recite words|understand, school|work|class, fitness|workout|warm-up, on the road|commute, cooking|cooking ritual|ceremony|sacrifice, urgent|important|exam|quiz, group chat|communicate|play|game|know, movie|watch|read|review, (20210709) Add English support
// @author          [email protected]
// @match           *://calendar.google.com/*
// @grant           none
// ==/UserScript==
//
// updates:
// (20220421) set the text color on dark background to white.
//
// [颜色名 — HTML颜色代码](https://htmlcolorcodes.com/zh/yanse-ming/)
// input: h in [0,360] and s,v in [0,1] - output: r,g,b in [0,1]
// [LCH colors in CSS: what, why, and how? – Lea Verou](https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/)
// [javascript - RGB to XYZ and LAB colours conversion - Stack Overflow](https://stackoverflow.com/questions/15408522/rgb-to-xyz-and-lab-colours-conversion)
// [rgb-lab/color.js at master · antimatter15/rgb-lab](https://github.com/antimatter15/rgb-lab/blob/master/color.js)
// [Color math and programming code examples](https://www.easyrgb.com/en/math.php)

globalThis.googleCalendarColorize?.unload?.();

const D65_2deg = [95.047, 100.0, 108.883];
const rad = (deg) => (deg / 180) * Math.PI;
const 向向内积 = (a, b) => a.map((_, i) => a[i] * b[i]).reduce((a, b) => a + b);
const 向向乘 = (a, b) => a.map((_, i) => a[i] * b[i]);
const 向数除 = (a, b) => a.map((_, i) => a[i] / b);
const lch2lab = (l, c, h_deg) => [
  l,
  Math.cos(rad(h_deg)) * c,
  Math.sin(rad(h_deg)) * c,
];
const lab2xyz = (l, a, b) => {
  let vy = (l + 16) / 116;
  let vx = a / 500 + vy;
  let vz = vy - b / 200;
  let xyz = [vx, vy, vz].map((v) =>
    v ** 3 > 0.008856 ? v ** 3 : (v - 16 / 116) / 7.787
  );
  return 向向乘(xyz, D65_2deg);
};
const xyz2srgb = (x, y, z) => {
  let vxyz = 向数除([x, y, z], 100);
  let vr = 向向内积(vxyz, [3.2406, -1.5372, -0.4986]);
  let vg = 向向内积(vxyz, [-0.9689, 1.8758, 0.0415]);
  let vb = 向向内积(vxyz, [0.0557, -0.204, 1.057]);
  return [vr, vg, vb]
    .map((v) => (v > 0.0031308 ? 1.055 * v ** (1 / 2.4) - 0.055 : 12.92 * v))
    .map((v) => v * 255);
};
const srgb2str = (r, g, b) =>
  `rgb(${[r, g, b].map((e) => Math.min(Math.max(0, e | 0), 255)).join(",")})`;
// const lch2str = (l, c, h) =>
//     srgb2str(...xyz2srgb(...lab2xyz(...lch2lab(l, c, h))));
const ss_lch2rgba_str = (l, c, h) =>
  srgb2str(...xyz2srgb(...lab2xyz(...lch2lab(l, c, h ** 1.2 * 300 + 30))));
// document.body.style.background = lch2str(100, 80, 30)
const 深色事件 = {
  // 一般红到青,如果写了就用写的颜色
  "urgent|important|exam|quiz|quarrel|accident|紧急|重要|考试|测验|吵架|事故":
    "red",
  "ritual|ceremony|sacrifice|仪式|典礼|祭祀": "",
  "groupchat|communication|know|blog|little red book|video|jitter|bilibili|B station|群聊|交流|知乎|微博|小红书|视频|抖音|bilibili|B站":
    "",
  "play|games|玩|游戏": "",
  "bill|payment|economic|账单|还款|经济": "",
  "Study|Memorize|Understand|Read|Movies|Watch|Read|Reviews|学习|背词|了解|阅读|电影|看书|阅书|影评|《":
    "",
  "Think|Research|R&D|Develop|Research|Explore|思考|科研|研发|开发|研究|探索":
    "",
};
const 浅色事件 = {
  // 从浅红到天蓝
  "shop|buy|购物|购买": "",
  "rest|sleep|休|睡": "",
  "shower|wash|clean|organizing|clean up|洗澡|洗漱|收拾|整理|大扫除": "",
  "workout|body building|warm up|cook|cook|eat|exercise|健身|锻练|热身|料理|做饭|吃|运动":
    "",
  "diary|schedule|journal|jour|日记|日程|日志": "",
  "maintenance|maintain|Operation|ops|Configuration|conf|维护|运维|配置": "",
  "On the road|commute|school|work|class|course|路上|通勤|上学|上班|上课|课程":
    "",
};
const 任务事件 = {
  // 从浅红到天蓝
  TODO: "",
  PENDING: "",
  DOING: "",
  DONE: "",
};
const lch表值色带转换 = (预设颜色表, s, e) =>
  Object.fromEntries(
    Object.entries(预设颜色表).map(([k, v], i, a) => [
      k,
      v || ss_lch2rgba_str(s, e, i / a.length),
    ])
  );
Object.assign(深色事件, lch表值色带转换(深色事件, 70, 80));
Object.assign(浅色事件, lch表值色带转换(浅色事件, 100, 20));
Object.assign(任务事件, lch表值色带转换(任务事件, 20, 20));

const 更新颜色 = () => {
  const 事件元素列 = [...document.querySelectorAll("div[data-eventid]")];
  const 颜色分析 = 事件元素列.map((e) => ({
    元素: e,
    文本: e.textContent.replace(/Organizer/g, ""),
    颜色: window.getComputedStyle(e).getPropertyValue("background-color"),
  }));
  // [[深色事件, 'black'], [浅色事件, 'white']].
  颜色分析.forEach(({ 元素, 文本 }) =>
    Object.keys(浅色事件)
      .filter((正则) => 文本.match(new RegExp(正则, "i")))
      .sort(
        (正则1, 正则2) =>
          文本.match(new RegExp(正则1, "i")).index -
          文本.match(new RegExp(正则2, "i")).index
      )
      .forEach((正则) => {
        元素.googleCalendarColorizeFlags = { 正则 };
        元素.style.backgroundColor = 浅色事件[正则];
        [...元素.querySelectorAll("span,div")].map(
          (e) => (e.style.color = "black")
        );
      })
  );
  颜色分析.forEach(({ 元素, 文本 }) =>
    Object.keys(深色事件)
      .filter((正则) => 文本.match(new RegExp(正则, "i")))
      .sort(
        (正则1, 正则2) =>
          文本.match(new RegExp(正则1, "i")).index -
          文本.match(new RegExp(正则2, "i")).index
      )
      .forEach((正则) => {
        // console.log(元素);
        元素.googleCalendarColorizeFlags = { 正则 };
        元素.style.backgroundColor = 深色事件[正则];
        [...元素.querySelectorAll("span,div")].map(
          (e) => (e.style.color = "white")
        );
      })
  );
};

更新颜色();

const 节流 =
  (间隔, 函数 = async () => null, 提示函数 = async () => null, 上次执行 = 0) =>
  async (...参数) =>
    +new Date() - 上次执行 > 间隔
      ? ((上次执行 = +new Date()), await 函数(...参数))
      : await 提示函数(...参数);
const 防抖 =
  (
    间隔,
    函数 = async () => null,
    提示函数 = async () => null,
    timerId = null
  ) =>
  (...参数) =>
    new Promise(
      (resolve) => (
        timerId && (clearTimeout(timerId), resolve(提示函数(...参数))),
        (timerId = setTimeout(() => resolve(函数(...参数)), 间隔))
      )
    );
const 节流防抖 = (间隔, 函数 = async () => null, 提示函数 = async () => null) =>
  节流(间隔, 函数, 防抖(间隔, 函数, 提示函数));
const 刷新函数 = 节流防抖(10e3 /* 10s */, () => !document.hidden && 更新颜色());
const 主动刷新函数 = 节流防抖(200, () => !document.hidden && 更新颜色());

// オブザーバインスタンスを作成
const 目标 = document.documentElement || document.body;
const 监视配置 = { attributes: false, childList: true, characterData: false };
const 页面变动监视器 = new MutationObserver((mutations) => {
  if (!mutations.some((record) => record.addedNodes.length)) return;
  页面变动监视器.disconnect();
  刷新函数();
  目标 && 页面变动监视器.observe(目标, 监视配置);
});
页面变动监视器.observe(目标, 监视配置);
const onvisibilitychange = () => setTimeout(刷新函数, 100);
const onmouseup = () => setTimeout(主动刷新函数, 100);
document.addEventListener("visibilitychange", onvisibilitychange, false);
document.addEventListener("mouseup", onmouseup, false);
document.addEventListener("keyup", () => setTimeout(主动刷新函数, 100), false);
window.addEventListener("load", 刷新函数, false);

刷新函数();
const unload = () => {
  页面变动监视器.disconnect();
  document.removeEventListener("visibilitychange", onvisibilitychange, false);
  document.removeEventListener("mouseup", onmouseup, false);
  document.removeEventListener(
    "keyup",
    () => setTimeout(主动刷新函数, 100),
    false
  );
  window.removeEventListener("load", 刷新函数, false);
};
globalThis.googleCalendarColorize = { unload };