您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
新标签页打开条目时自动标记为已读,收藏计数
当前为
// ==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 http://greasyfork.icu/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 http://greasyfork.icu/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); })(); })();