// ==UserScript==
// @name 煎蛋吐槽记录器
// @namespace yunyuyuan/jandan-recorder
// @version 1.0.0
// @author monkey
// @description 煎蛋吐槽记录器,自动记录发送过的主题和评论
// @license MIT
// @match *://*.jandan.net/*
// @require https://cdn.jsdelivr.net/npm/[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-93b9bbf0]{overflow:auto;flex-grow:1;align-self:stretch}table[data-v-93b9bbf0]{width:100%;border-collapse:collapse}table thead[data-v-93b9bbf0]{border-radius:12px 12px 0 0}table thead th[data-v-93b9bbf0]{padding:10px 0;font-size:16px;position:sticky;top:0;z-index:1;background:#c8c8c8}table tbody td[data-v-93b9bbf0]{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-93b9bbf0]{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",
setup(__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.onMounted(() => {
refreshList();
saveList();
});
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-93b9bbf0"]]);
const _hoisted_1 = { class: "settings-container" };
const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
__name: "settings",
setup(__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.onMounted(refreshSettings);
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),
!inSetting.value ? (vue.openBlock(), vue.createBlock(list, { key: 0 })) : (vue.openBlock(), vue.createBlock(_sfc_main$1, { key: 1 }))
], 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);