// ==UserScript== // @name 煎蛋吐槽记录器 // @namespace yunyuyuan/jandan-recorder // @version 1.0.4 // @author monkey // @description 煎蛋吐槽记录器,自动记录发送过的主题和评论 // @license MIT // @match *://*.jandan.net/* // @require https://unpkg.com/vue@3.4.21/dist/vue.global.prod.js // @grant GM_addStyle // @grant unsafeWindow // @downloadURL none // ==/UserScript== (d=>{if(typeof GM_addStyle=="function"){GM_addStyle(d);return}const e=document.createElement("style");e.textContent=d,document.head.append(e)})(" .table-container[data-v-76dd3495]{overflow:auto;flex-grow:1;align-self:stretch}table[data-v-76dd3495]{width:100%;border-collapse:collapse}table thead[data-v-76dd3495]{border-radius:12px 12px 0 0}table thead th[data-v-76dd3495]{padding:10px 0;font-size:16px;position:sticky;top:0;z-index:1;background:#c8c8c8}table tbody td[data-v-76dd3495]{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-76dd3495]{min-width:80px}}#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"; 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 StorageKey = "jandan-recorder"; const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({ __name: "list", props: { inSetting: { type: Boolean } }, setup(__props) { const props = __props; const list2 = vue.reactive([]); const refreshList = () => { const autoDeleteDay = parseInt(JSON.parse(localStorage.getItem(SettingsStorageKey) || "{}")["auto-delete-day"]); list2.splice( 0, list2.length, ...JSON.parse(localStorage.getItem(StorageKey) || "[]").map((item) => { return { ...item, time: new Date(item.timestamp) }; }).filter((item) => { if (item.time instanceof Date && typeof autoDeleteDay === "number" && autoDeleteDay > 0) { return item.time.getTime() > Date.now() - 1e3 * 60 * 60 * 24 * autoDeleteDay; } return true; }) ); }; const saveList = () => { localStorage.setItem(StorageKey, JSON.stringify(vue.toRaw(list2))); refreshList(); }; emitter.on(PushRecordEvent, (newItem) => { if (!newItem) return; list2.unshift(newItem); saveList(); }); const removeListItem = (idx) => { list2.splice(idx, 1); saveList(); }; vue.watch(() => props.inSetting, (inSetting) => { if (!inSetting) { refreshList(); 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) => { var _a; return vue.openBlock(), vue.createElementBlock("tr", null, [ vue.createElementVNode("td", null, vue.toDisplayString((_a = item.time) == null ? void 0 : _a.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.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-76dd3495"]]); 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 inputNumber = (e) => { const val = parseInt(e.target.value || ""); localStorage.setItem(SettingsStorageKey, JSON.stringify({ ...vue.toRaw(settings), "auto-delete-day": isNaN(val) || val < 1 ? 0 : val })); }; 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["auto-delete-day"] = $event), onInput: inputNumber, onFocusout: refreshSettings }, null, 544), [ [vue.vModelText, settings["auto-delete-day"]] ]), vue.createTextVNode(" 天前的记录(设置为0则不自动删除) ") ]) ]); }; } }); 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; switch (url) { case "/api/comment/create": item = { url: `/t/${res}`, isCreate: true, content: requestData.comment, timestamp: Date.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}#${res.data.comment_ID}` : `/t/${requestData.comment_id}#tucao-${res.data.comment_ID}`, isCreate: false, content: requestData.content, timestamp: Date.now() }; } break; case "/api/forum/replies": if (res.msg == "success") { item = { url: `/bbs#/topic/${requestData.post_id}`, isCreate: false, content: requestData.content, timestamp: Date.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);