您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
煎蛋吐槽记录器,自动记录发送过的主题和评论
当前为
// ==UserScript== // @name 煎蛋吐槽记录器 // @namespace yunyuyuan/jandan-recorder // @version 1.0.7 // @author monkey // @description 煎蛋吐槽记录器,自动记录发送过的主题和评论 // @license MIT // @icon data:image/png;base64,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 // @match *://*.jandan.net/* // @require https://unpkg.com/[email protected]/dist/vue.global.prod.js // @grant GM_addStyle // @grant unsafeWindow // ==/UserScript== (e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const a=document.createElement("style");a.textContent=e,document.head.append(a)})(" .table-container[data-v-4294c7c5]{overflow:auto;flex-grow:1;align-self:stretch}table[data-v-4294c7c5]{width:100%;border-collapse:collapse}table thead[data-v-4294c7c5]{border-radius:12px 12px 0 0}table thead th[data-v-4294c7c5]{padding:10px 0;font-size:16px;position:sticky;top:0;z-index:1;background:#c8c8c8}table tbody td[data-v-4294c7c5]{font-size:14px;padding:8px 0;border-bottom:1px solid rgb(218,218,218)}@media screen and (min-width: 769px){table tbody td[data-v-4294c7c5]{min-width:80px}}.settings-container{width:100%}.settings-container>div{padding:20px 0;border-bottom:1px solid gray}#jandan-recorder-modal{position:fixed;top:0;left:0;right:0;bottom:0;z-index:99999;background:#0009}#jandan-recorder-modal .inner{background:#fff;color:#000;width:70%;height:calc(100% - 100px);margin:50px auto auto;padding:10px;border-radius:12px;box-shadow:0 0 12px #0003;display:flex;align-items:center;flex-direction:column}@media screen and (min-width: 769px){#jandan-recorder-modal .inner{min-width:400px}}@media screen and (max-width: 768px){#jandan-recorder-modal .inner{width:90%}}#jandan-recorder-modal .switcher{margin-bottom:10px;font-size:15px;padding:4px 8px}#header .nav-items .nav-item:last-of-type{display:flex}#header .nav-items .nav-item:last-of-type .jandan-record-link{cursor:pointer}.jandan-record-link{word-break:keep-all} "); (function (vue) { 'use strict'; const InterruptUrls = [ /** * TODO 文章发布: N/A */ /** * 创建 问答/树洞/随手拍/无聊图 : /api/comment/create request { author: "", email: "", comment: "", comment_post_ID: "" } response string(id) */ "/api/comment/create", /** * 楼中回复: /api/tucao/create request { content: "", comment_id?: 5637737, // 树洞id comment_post_ID: 102312 } response { "code": 0, "msg": "success", "data": { "comment_ID": 12039174, "comment_author": "xiaoc", "comment_content": "祝福!", "comment_date": "2024-03-04T15:53:55.267675774+08:00", "comment_date_int": 1709538835, "comment_post_ID": 5637795, "comment_parent": 102312, "comment_reply_ID": 0, "is_jandan_user": 0, "is_tip_user": 0, "vote_negative": 0, "vote_positive": 0 } } */ "/api/tucao/create", /** * BBS发布: /api/forum/posts request { "title": "", "content": "", "page_id": 112928 } response { "code": 0, "msg": "success", "data": "" "post_id": ??? } */ // TODO "/api/forum/posts", 没有返回id,所以暂时不做 /** * BBS吐槽: /api/forum/replies request { "content": "", "post_id": 1282, "page_id": 112928 } */ "/api/forum/replies" ]; const ShowModalEvent = "show-modal"; const PushRecordEvent = "push-record"; const SettingsStorageKey = "jandan-recorder-settings"; const SettingsKeyAutoDeleteDay = "auto-delete-day"; const SettingsKeyAutoDelete404 = "auto-delete-404"; var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); function mitt(n) { return { all: n = n || /* @__PURE__ */ new Map(), on: function(t, e) { var i = n.get(t); i ? i.push(e) : n.set(t, [e]); }, off: function(t, e) { var i = n.get(t); i && (e ? i.splice(i.indexOf(e) >>> 0, 1) : n.set(t, [])); }, emit: function(t, e) { var i = n.get(t); i && i.slice().map(function(n2) { n2(e); }), (i = n.get("*")) && i.slice().map(function(n2) { n2(t, e); }); } }; } const emitter = mitt(); const _window = _unsafeWindow || window; const $ = (_window == null ? void 0 : _window.jQuery) || (_window == null ? void 0 : _window.$); const _hoisted_1$1 = { class: "table-container" }; const _hoisted_2 = ["href"]; const _hoisted_3 = ["onClick"]; const _hoisted_4 = { key: 0 }; const ListStorageKey = "jandan-recorder"; const oneDay = 1e3 * 60 * 60 * 24; const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({ __name: "list", props: { inSetting: { type: Boolean } }, setup(__props) { const props = __props; const list2 = vue.reactive([]); const getListFromStorage = () => { list2.splice(0, list2.length, ...JSON.parse(localStorage.getItem(ListStorageKey) || "[]")); }; const saveList = () => { localStorage.setItem(ListStorageKey, JSON.stringify(vue.toRaw(list2))); getListFromStorage(); }; emitter.on(PushRecordEvent, (newItem) => { if (!newItem) return; list2.unshift(newItem); saveList(); }); const removeListItem = (idx) => { list2.splice(idx, 1); saveList(); }; const processed = vue.ref(false); vue.watch(() => props.inSetting, (inSetting) => { if (!inSetting) { getListFromStorage(); if (!processed.value) { processed.value = true; const now = Date.now(); const settings = JSON.parse(localStorage.getItem(SettingsStorageKey) || "{}"); const autoDeleteDay = parseInt(settings[SettingsKeyAutoDeleteDay]); const autoDelete404 = settings[SettingsKeyAutoDelete404]; if (typeof autoDeleteDay === "number" && autoDeleteDay > 0) { list2.splice(0, list2.length, ...list2.filter((item) => { return item.timestamp > now - oneDay * autoDeleteDay; })); } saveList(); if (autoDelete404) { const allUrls = new Set(list2.map((item) => item.url)); (async () => { for (const url of allUrls) { const biggest = list2.filter((item) => item.url === url).map((item) => item.lastCheck404 || 0).sort((a, b) => a - b).pop(); if (biggest < now - oneDay) { const res = await fetch(url); if (res.status === 404) { list2.splice(0, list2.length, ...list2.filter((item) => { return item.url !== url; })); } await new Promise((resolve) => setTimeout(resolve, 1e3)); } list2.forEach((item) => { if (item.url === url) { item.lastCheck404 = now; } }); saveList(); } })(); } } } }, { immediate: true }); return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$1, [ vue.createElementVNode("table", null, [ vue.createElementVNode("thead", null, [ vue.createElementVNode("tr", null, [ (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, vue.renderList(["日期", "类型", "内容", "网址", "操作"], (i) => { return vue.createElementVNode("th", null, vue.toDisplayString(i), 1); }), 64)) ]) ]), vue.createElementVNode("tbody", null, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(list2, (item, idx) => { return vue.openBlock(), vue.createElementBlock("tr", null, [ vue.createElementVNode("td", null, vue.toDisplayString(new Date(item.timestamp).toLocaleString()), 1), vue.createElementVNode("td", null, vue.toDisplayString(item.isCreate ? "自己创建" : "评论吐槽"), 1), vue.createElementVNode("td", null, vue.toDisplayString(item.content), 1), vue.createElementVNode("td", null, [ vue.createElementVNode("a", { target: "_blank", href: item.urlWithAnchor || item.url }, "点击前往", 8, _hoisted_2) ]), vue.createElementVNode("td", null, [ vue.createElementVNode("button", { onClick: ($event) => removeListItem(idx) }, "删除", 8, _hoisted_3) ]) ]); }), 256)), list2.length === 0 ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_4, "一条都没有,赶快去吐槽吧!")) : vue.createCommentVNode("", true) ]) ]) ]); }; } }); const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const list = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-4294c7c5"]]); const _hoisted_1 = { class: "settings-container" }; const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({ __name: "settings", props: { inSetting: { type: Boolean } }, setup(__props) { const props = __props; const settings = vue.reactive({}); const refreshSettings = () => { Object.assign(settings, JSON.parse(localStorage.getItem(SettingsStorageKey) || "{}")); }; const inputAutoDeleteDay = (e) => { const val = parseInt(e.target.value || ""); localStorage.setItem(SettingsStorageKey, JSON.stringify({ ...vue.toRaw(settings), [SettingsKeyAutoDeleteDay]: isNaN(val) || val < 1 ? 0 : val })); }; const inputAutoDelete404 = (e) => { localStorage.setItem(SettingsStorageKey, JSON.stringify({ ...vue.toRaw(settings), [SettingsKeyAutoDelete404]: e.target.checked })); refreshSettings(); }; vue.watch(() => props.inSetting, (inSetting) => { if (inSetting) { refreshSettings(); } }, { immediate: true }); return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [ vue.createElementVNode("div", null, [ vue.createTextVNode(" 自动删除 "), vue.withDirectives(vue.createElementVNode("input", { type: "number", min: "0", step: "1", "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => settings[vue.unref(SettingsKeyAutoDeleteDay)] = $event), onInput: inputAutoDeleteDay, onFocusout: refreshSettings }, null, 544), [ [vue.vModelText, settings[vue.unref(SettingsKeyAutoDeleteDay)]] ]), vue.createTextVNode(" 天前的记录(默认设置为0则不自动删除) ") ]), vue.createElementVNode("div", null, [ vue.withDirectives(vue.createElementVNode("input", { type: "checkbox", "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => settings[vue.unref(SettingsKeyAutoDelete404)] = $event), onChange: inputAutoDelete404 }, null, 544), [ [vue.vModelCheckbox, settings[vue.unref(SettingsKeyAutoDelete404)]] ]), vue.createTextVNode(" 自动删除已失效(404)的记录 ") ]) ]); }; } }); const _sfc_main = /* @__PURE__ */ vue.defineComponent({ __name: "modal", setup(__props) { const showModal = vue.ref(false); emitter.on(ShowModalEvent, () => { showModal.value = true; }); const inSetting = vue.ref(false); return (_ctx, _cache) => { return vue.withDirectives((vue.openBlock(), vue.createElementBlock("div", { id: "jandan-recorder-modal", onMousedown: _cache[2] || (_cache[2] = ($event) => showModal.value = false) }, [ vue.createElementVNode("div", { class: "inner", onMousedown: _cache[1] || (_cache[1] = (e) => e.stopPropagation()) }, [ vue.createElementVNode("button", { class: "switcher", onClick: _cache[0] || (_cache[0] = ($event) => inSetting.value = !inSetting.value) }, vue.toDisplayString(inSetting.value ? "返回列表(设置会自动保存)" : "前往设置"), 1), vue.withDirectives(vue.createVNode(list, { inSetting: inSetting.value }, null, 8, ["inSetting"]), [ [vue.vShow, !inSetting.value] ]), vue.withDirectives(vue.createVNode(_sfc_main$1, { inSetting: inSetting.value }, null, 8, ["inSetting"]), [ [vue.vShow, inSetting.value] ]) ], 32) ], 544)), [ [vue.vShow, showModal.value] ]); }; } }); function processResponse(url, requestData, res) { let item = null; const now = Date.now(); switch (url) { case "/api/comment/create": item = { url: `/t/${res}`, urlWithAnchor: `/t/${res}`, isCreate: true, content: requestData.comment, timestamp: now, lastCheck404: now }; break; case "/api/tucao/create": if (res.msg == "success") { const isPost = _window.location.pathname.startsWith("/p/"); item = { url: isPost ? `/p/${requestData.comment_post_ID}` : `/t/${requestData.comment_id}`, urlWithAnchor: isPost ? `/p/${requestData.comment_post_ID}#${res.data.comment_ID}` : `/t/${requestData.comment_id}#tucao-${res.data.comment_ID}`, isCreate: false, content: requestData.content, timestamp: now, lastCheck404: now }; } break; case "/api/forum/replies": if (res.msg == "success") { item = { url: `/bbs#/topic/${requestData.post_id}`, urlWithAnchor: `/bbs#/topic/${requestData.post_id}`, isCreate: false, content: requestData.content, timestamp: now, lastCheck404: now }; } break; } item && emitter.emit(PushRecordEvent, item); } function parseRequestData(requestData) { let result = requestData; const parsedObj = {}; if (typeof requestData == "string") { try { return JSON.parse(requestData); } catch { for (const [key, value] of new URLSearchParams(requestData)) { parsedObj[key] = value; } result = parsedObj; } } else if (requestData instanceof FormData) { requestData.forEach(function(value, key) { parsedObj[key] = value; }); result = parsedObj; } return result; } if ($) { $(document).on("ajaxSuccess", function(_event, _jqXHR, settings, data) { try { const url = settings.url; if (InterruptUrls.includes(url)) { processResponse(url, parseRequestData(settings.data), data); } } catch { } }); } if (_window.axios) { _window.axios.interceptors.response.use((response) => { try { processResponse(response.config.url, parseRequestData(response.config.data), response.data); } catch { } return response; }); } const App = vue.createApp(_sfc_main); App.mount( (() => { const app = document.createElement("div"); document.body.append(app); return app; })() ); const memberLink = document.querySelector('a[href="/member"]'); const myPost = document.createElement("a"); myPost.classList.add("nav-link", "jandan-record-link"); myPost.innerText = "我的吐槽"; myPost.onclick = () => { emitter.emit(ShowModalEvent); }; memberLink.parentElement.appendChild(myPost); console.log("煎蛋吐槽记录器加载成功!"); })(Vue);