// ==UserScript==
// @name 「Feedly」中键标记已读 + 收藏导出为*.url
// @namespace https://www.wdssmq.com/
// @version 0.3.6
// @author 沉冰浮水
// @description 新标签页打开条目时自动标记为已读,收藏计数
// @link https://github.com/wdssmq/userscript/tree/master/feedly
// @link https://greasyfork.org/zh-CN/scripts/381793
// @null ----------------------------
// @contributionURL https://github.com/wdssmq#%E4%BA%8C%E7%BB%B4%E7%A0%81
// @contributionAmount 5.93
// @null ----------------------------
// @link https://github.com/wdssmq/userscript
// @link https://afdian.net/@wdssmq
// @link https://greasyfork.org/zh-CN/users/6865-wdssmq
// @null ----------------------------
// @match https://feedly.com/*
// @noframes
// @run-at document-end
// @grant GM_openInTab
// @grant GM_setClipboard
// ==/UserScript==
/* jshint esversion: 6 */
/* eslint-disable */
(function () {
'use strict';
const gm_name = "feedly";
const curDate = new Date();
// ---------------------------------------------------
const _curUrl = () => { return window.location.href };
const _getDateStr = (date = curDate) => {
const options = { year: "numeric", month: "2-digit", day: "2-digit" };
return date.toLocaleDateString("zh-CN", options).replace(/\//g, "-");
};
// ---------------------------------------------------
const _log = (...args) => console.log(`[${gm_name}]\n`, ...args);
// ---------------------------------------------------
// const $ = window.$ || unsafeWindow.$;
function $n(e) {
return document.querySelector(e);
}
function $na(e) {
return document.querySelectorAll(e);
}
// ---------------------------------------------------
const fnElChange = (el, fn = () => { }) => {
const observer = new MutationObserver((mutationRecord, mutationObserver) => {
// _log('mutationRecord = ', mutationRecord);
// _log('mutationObserver === observer', mutationObserver === observer);
fn(mutationRecord, mutationObserver);
// mutationObserver.disconnect();
});
observer.observe(el, {
// attributes: false,
// attributeFilter: ["class"],
childList: true,
// characterData: false,
subtree: true,
});
};
function fnFindDomUp(el, selector, up = 1) {
// _log("fnFindDomUp", el, selector, up);
const elParent = el.parentNode;
if (selector.indexOf(".") == 0 && elParent.className.indexOf(selector.split(".")[1]) > -1) {
return elParent;
}
const elFind = elParent.parentNode.querySelector(selector);
if (elFind) {
return elFind;
}
if (up > 1) {
return fnFindDomUp(elParent, selector, up - 1);
} else {
return null;
}
}
// localStorage 封装
const lsObj = {
setItem: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
getItem: function (key, def = "") {
const item = localStorage.getItem(key);
if (item) {
return JSON.parse(item);
}
return def;
},
};
// 数据读写封装
const gob = {
// 非 ls 字段
_loaded: false,
_curStars: 0,
_time: {
cycle: 0,
rem: 0,
},
// ls 字段
data: {
bolReset: false,
lstStars: 0,
diffStars: { decr: 0, incr: 0 },
// decrease 减少
// increase 增加
},
// 读取
load: function () {
if (this._loaded) {
return;
}
this._loaded = true;
this.data = lsObj.getItem("feedly_bld_data", this.data);
_log("load", gob);
},
// 保存
save: function () {
lsObj.setItem("feedly_bld_data", this.data);
_log("save", gob);
},
};
// 判断当前地址是否是收藏页
const fnCheckUrl = () => {
if ("https://feedly.com/i/saved" === _curUrl()) {
return true;
}
return false;
};
// 当前星标数获取
function fnLaterGetItems(obj) {
const $listWrap = $n("div.list-entries");
if ($listWrap) {
obj.$list = $listWrap.querySelectorAll("div.content a");
return obj.$list.length;
}
return 0;
}
// 收藏数 View
function fnLaterViewStars() {
gob._curStars = fnLaterGetItems(gob);
const strText = `Read later(${gob._curStars} 丨 -${gob.data.diffStars.decr} 丨 +${gob.data.diffStars.incr})(${gob._time.cycle} - ${gob._time.rem})`;
$n("h1 #header-title").innerHTML = strText;
if ($n("header.header h2")) {
$n("header.header h2").innerHTML = strText;
}
$n("#header-title").innerHTML = strText;
}
// 滚动条滚动时触发
function fnLaterOnScroll() {
if (!fnCheckUrl()) {
return;
}
fnLaterViewStars();
fnColorStars();
// 全部条目加载后执行
if ($n(".list-entries > h2")) {
!gob._loaded && _log("fnLaterOnScroll", "页面加载完成");
fnLaterControl();
} else {
_log("fnLaterOnScroll", "页面加载中");
}
}
// 星标部分入口函数
function fnLaterMain(record, observer) {
if (!fnCheckUrl()) {
return;
}
// 随机直接返回
if (Math.random() > 0.4) {
return;
}
gob._curStars = fnLaterGetItems(gob);
if (gob._curStars === 0) {
return;
}
// observer.disconnect();
// 绑定事件
if ($n("#feedlyFrame") && $n("#feedlyFrame").dataset.addEL !== "done") {
fnLaterOnScroll();
$n("#feedlyFrame").addEventListener("scroll", fnLaterOnScroll);
$n("#feedlyFrame").dataset.addEL = "done";
_log("fnLaterMain", "绑定滚动监听");
}
}
fnElChange($n("#root"), fnLaterMain);
const curTime = Math.floor(curDate.getTime() / 1000);
const curHours = Math.floor(curTime / 3600);
// const cur4Hours = Math.floor(curTime / (60 * 60 * 4));
const cur4Minutes = Math.floor(curTime / 240);
const fnCheckControl = (diff) => {
const iTime = curHours;
gob._time.cycle = iTime;
gob._time.rem = iTime % 4;
// 累计已读少于 17 或者累计新增大于累计已读
if (diff.decr < 17 || diff.incr - diff.decr >= 4) {
return "default";
}
// 每 4 小时且累计已读大于累计新增
if (iTime % 4 === 0 && diff.decr - diff.incr >= 4) {
return "reset";
}
return "lock";
};
// 星标变动控制
function fnLaterControl() {
if (gob._loaded) {
return;
}
gob.load();
// 解锁重置判断
if (gob.data.bolReset) {
gob.data.diffStars.decr = 0;
gob.data.diffStars.incr = 0;
}
// 星标变化计数
const diff = gob._curStars - gob.data.lstStars;
// 写入新的星标数
gob.data.lstStars = gob._curStars;
// 初始化时直接返回
if (diff === gob._curStars) {
gob.save();
return;
}
// 大于上一次记录
if (diff > 0) {
// 新增星标计数
gob.data.diffStars.incr += Math.abs(diff);
} else {
// 已读星标计数
gob.data.diffStars.decr += Math.abs(diff);
}
// 记录变量原始值
const strReset = gob.data.bolReset.toString();
// _log("fnLaterControl", strReset);
gob.data.bolReset = ((diff) => {
if (fnCheckControl(diff) === "reset") {
return true;
}
return false;
})(gob.data.diffStars);
// 更新 localStorage 存储
if (diff !== 0 || strReset !== gob.data.bolReset.toString()) {
gob.save();
}
}
// 按规则给星标条目着色
function fnColorStars(offset = 0) {
// _log("fnColorStars", curTime, cur4Minutes);
const $stars = gob.$list;
const isLock = "lock" === fnCheckControl(gob.data.diffStars) ? true : false;
if (isLock) {
$n(".list-entries").style.backgroundColor = "#666";
}
let pickCount = 0;
[].forEach.call($stars, function ($e, i) {
// _log("fnColorStars", $e, i);
// _log("fnColorStars", "==============================");
const $ago = fnFindDomUp($e, ".ago", 2);
const href = $e.href + $ago.innerHTML;
const hash = parseInt(href.replace(/\D/g, ""));
// _log("fnColorStars", href, hash);
// const intNum = parseInt(hash + cur4Minutes + i);
const intNum = parseInt(hash + cur4Minutes + offset);
if (intNum % 7 === 0 && (!isLock || pickCount <= 7)) {
// _log("fnColorStars", intNum, intNum % 4);
pickCount++;
$e.parentNode.parentNode.style.backgroundColor = "#ddd";
} else {
$e.parentNode.parentNode.style.backgroundColor = "transparent";
$e.parentNode.parentNode.style.display = "";
if (isLock || pickCount > 7) {
if (intNum % 4 !== 0) {
$e.parentNode.parentNode.style.display = "none";
return;
} else {
$e.parentNode.parentNode.style.backgroundColor = "#666";
// $e.parentNode.parentNode.style = $e.style.color = "#666";
}
}
}
});
if (pickCount <= 4) {
fnColorStars(offset + 1);
}
}
/* global GM_setClipboard:true */
// nodeList 转换为 Array
function fnNodeListToArray(nodeList) {
return Array.prototype.slice.call(nodeList);
}
// 构造 Bash Shell 脚本
function fnMKShell(arrList, prefix = "") {
const curDateStr = _getDateStr();
let strRlt =
"if [ ! -d \"prefix-date\" ]; then\n" +
"mkdir prefix-date\n" +
"fi\n" +
"cd prefix-date\n\n";
strRlt = strRlt.replace(/prefix/g, prefix);
strRlt = strRlt.replace(/date/g, curDateStr);
/**
* e {title:"", href:""}
*/
arrList.forEach(function (e, i) {
const serial = i + 1;
// _log(e);
// 移除不能用于文件名的字符
let title = e.title || e.innerText;
title = title.replace(/\\|\/|:|\*|!|\?]|<|>/g, "");
title = title.replace(/["'\s]/g, "");
// _log(title);
const lenTitle = title.length;
if (lenTitle >= 155) {
title = `标题过长丨${lenTitle}`;
}
// 获取文章链接
const href = e.href || e.url;
// url 文件名
const urlFileName = `${serial}丨${title}.url`;
strRlt += `echo [InternetShortcut] > "${urlFileName}"\n`;
strRlt += `echo "URL=${href}" >> "${urlFileName}"\n`;
strRlt += "\n";
});
{
strRlt += "exit\n\n";
}
return strRlt;
}
// 星标文章导出为 *.url 文件
$n("#root").addEventListener("mouseup", function (event) {
if (event.target.innerHTML.indexOf("Read later") > -1 && $n(".list-entries > h2")) {
const $el = event.target;
console.log($el);
const listItems = fnNodeListToArray($na("div.content a"));
GM_setClipboard(fnMKShell(listItems, "feedly"));
$n(".list-entries > h2").innerHTML = "已复制到剪贴板";
}
}, false);
// 拿回订阅源地址
// 绑定监听事件到 div#box 上
$n("#root").addEventListener("mouseup", function (event) {
// 输出触发事件的元素
// 根据内容判断是否执行相应操作
const elText = event.target.innerHTML;
if (
// elText.indexOf("Feed not found") > -1 ||
elText.indexOf("Wrong feed URL") > -1
) {
// 内部再输出一次确定判断条件正确
console.log(event.target);
// 拿到解码后的订阅源地址
const curUrl = ((url) => {
return url.replace("https://feedly.com/i/subscription/feed/", "");
})(decodeURIComponent(curUrl));
// 输出到页面中
$n("#feedlyPageFX h2").insertAdjacentHTML(
"beforeend",
`<div class="sub">${curUrl}</div>`,
);
}
}, false);
// 自动标记已读
(() => {
if (!$n("#root") || $n("#root").dataset.MarkRead === "bind") {
return;
}
// _log("fnAutoMark", "自动标记已读");
$n("#root").dataset.MarkRead = "bind";
// 根据事件返回需要的 dom 元素
const fnEventFilter = (eType, eTgt) => {
// _log("fnEventFilter", eType, eTgt);
let pick = false;
let objRlt = null, objDef = {
$entry: null,
$btn: null,
};
if (eType === "mouseup") {
if (
eTgt.classList.contains("entry__title") && eTgt.nodeName === "A"
) {
objRlt = {
// 当前条目元素
$entry: eTgt.parentNode.parentNode,
// 标记已读的按钮
$btn: eTgt.parentNode.querySelector("button.EntryMarkAsReadButton"),
};
pick = true;
}
} else if (eType === "mouseover") {
if (eTgt.nodeName === "ARTICLE" && eTgt.className.indexOf("entry") > -1) {
objRlt = {
// 当前内容条目元素
$entry: eTgt,
// 标记已读的按钮
$btn: eTgt.querySelector("button.EntryMarkAsReadButton"),
};
// _log("fnEventFilter", "移入");
// _log("fnEventFilter", eTgt.dataset.leaveCount, typeof eTgt.dataset.leaveCount);
const intLeaveCount = parseInt(eTgt.dataset.leaveCount);
// 已经触发过 leave 事件时才通过
pick = intLeaveCount >= 1 ? true : false;
if (pick) {
return objRlt;
}
// _log("fnEventFilter", intLeaveCount);
if (!isNaN(intLeaveCount)) {
// _log("fnEventFilter", "已绑定移出事件");
return objDef;
}
// 绑定移出事件
eTgt.addEventListener("mouseleave", () => {
// _log("fnEventFilter", "移出");
const intLeaveCount = parseInt(eTgt.dataset.leaveCount);
if (intLeaveCount === 0) {
// await _sleep(1000);
eTgt.dataset.leaveCount = "1";
}
}, false);
// 设置初始值
eTgt.dataset.leaveCount = 0;
}
}
if (pick) {
return objRlt;
}
return objDef;
};
// 事件处理函数
const fnEventHandler = (event) => {
// 限制鼠标在元素右侧移入才会触发
if (event.type === "mouseover") {
const intDiff = Math.abs(event.offsetX - event.target.offsetWidth);
if (intDiff > 17) {
return;
}
}
const { $entry, $btn } = fnEventFilter(event.type, event.target);
if (!$entry || !$btn) {
return;
}
// 判断是否含有指定类名
if ($entry.className.indexOf("entry--read") === -1) {
_log("fnMarRead", event.button, "自动标记已读");
$btn.click();
}
};
// 绑定监听事件
$n("#root").addEventListener("mouseup", fnEventHandler, false);
$n("#root").addEventListener("mouseover", fnEventHandler, false);
})();
})();