您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Saves chat history. For Chat 2.0.
当前为
// ==UserScript== // @name Chat2.0Recorder // @namespace http://tampermonkey.net/ // @version 1.2 // @description Saves chat history. For Chat 2.0. // @author bot_7420 [2937420] // @match https://www.torn.com/* // @run-at document-start // @grant GM_addStyle // ==/UserScript== (function () { "use strict"; let db = null; window.onload = function () { initCSS(); initControlPanel(); }; // Hook fetch chat const originalFetch = unsafeWindow.fetch; unsafeWindow.fetch = async (...args) => { let [resource, config] = args; let response = await originalFetch(resource, config); if (response.url.indexOf("sendbird.com/v3/group_channels/") != -1 && response.url.indexOf("/messages?") != -1) { response.clone().json().then((data) => { if (Array.isArray(data.messages)) { // console.log(data.messages); dbWriteArray(data.messages); } }); } return response; }; // Hook chat Websocket on receive message const originalSend = WebSocket.prototype.send; window.sockets = []; WebSocket.prototype.send = function (...args) { if (window.sockets.indexOf(this) === -1 && this.url.indexOf("sendbird.com") > -1) { console.log("ChatRecorder: found chat2.0 websocket"); window.sockets.push(this); this.addEventListener("message", function (event) { if (event.data.startsWith("MESG")) { const messageObj = JSON.parse(event.data.substring(4)); handleMessage(messageObj); } }); } return originalSend.call(this, ...args); }; initIndexDB(); function handleMessage(message) { if (!message || !message.channel_url) { return; } dbWrite(message); } function initIndexDB() { const openRequest = indexedDB.open("ScriptChat2.0RecorderDB", 2); openRequest.onupgradeneeded = function (e) { db = e.target.result; if (!db.objectStoreNames.contains("messageStore")) { console.log("ChatRecorder: initIndexDB open onupgradeneeded create store"); const objectStore = db.createObjectStore("messageStore", { keyPath: "messageId", autoIncrement: false }); objectStore.createIndex("targetPlayerId", "targetPlayerId", { unique: false }); } }; openRequest.onsuccess = function (e) { console.log("ChatRecorder: initIndexDB open onsuccess"); db = e.target.result; }; openRequest.onerror = function (e) { console.error("ChatRecorder: initIndexDB open onerror"); console.dir(e); }; } function dbWrite(message) { if (!db) { console.error("ChatRecorder: dbWrite db is null"); } let msg = {}; const targetPlayer = getTargetPlayerFromMessage(message); if (!targetPlayer) { return; } msg.targetPlayerId = targetPlayer.id; msg.targetPlayerName = targetPlayer.name; msg.senderPlayerId = message.user.guest_id; msg.senderPlayerName = message.user.name; msg.timestamp = message.ts; msg.messageText = message.message; msg.messageId = message.msg_id; // console.log(msg); const transaction = db.transaction(["messageStore"], "readwrite"); transaction.oncomplete = (event) => { }; transaction.onerror = (event) => { console.error("ChatRecorder: dbWrite transaction onerror [" + msg.targetPlayerId + " " + msg.senderName + ": " + msg.messageText + "]"); }; const store = transaction.objectStore("messageStore"); const request = store.put(msg); request.onsuccess = (event) => { }; } function dbWriteArray(messageArray) { if (!db) { console.error("ChatRecorder: dbWriteArray db is null"); } const transaction = db.transaction(["messageStore"], "readwrite"); transaction.oncomplete = (event) => { }; transaction.onerror = (event) => { console.error("ChatRecorder: dbWrite transaction onerror [" + msg.targetPlayerId + " " + msg.senderName + ": " + msg.messageText + "]"); }; const store = transaction.objectStore("messageStore"); for (const message of messageArray) { const targetPlayer = getTargetPlayerFromMessage(message); if (targetPlayer) { let msg = {}; msg.targetPlayerId = targetPlayer.id; msg.targetPlayerName = targetPlayer.name; msg.senderPlayerId = message.user.user_id; msg.senderPlayerName = message.user.nickname; msg.timestamp = message.created_at; msg.messageText = message.message; msg.messageId = message.message_id; store.put(msg); } } } function dbReadByTargetPlayerId(targetPlayerId) { if (!db) { console.error("ChatRecorder: dbReadByTargetPlayerId db is null"); } const transaction = db.transaction(["messageStore"], "readonly"); transaction.oncomplete = (event) => { }; transaction.onerror = (event) => { console.error("ChatRecorder: dbReadByTargetPlayerId transaction onerror [" + targetPlayerId + "]"); }; const store = transaction.objectStore("messageStore"); const index = store.index("targetPlayerId"); const keyRange = IDBKeyRange.only(targetPlayerId); return new Promise((resolve, reject) => { const resultList = []; index.openCursor(keyRange).onerror = (event) => { resolve(resultList); }; index.openCursor(keyRange).onsuccess = (event) => { const cursor = event.target.result; if (cursor) { resultList.push(cursor.value); cursor.continue(); } else { resolve(resultList); } }; }); } function dbReadAllPlayerId() { if (!db) { console.error("ChatRecorder: dbReadAllPlayerId db is null"); } const transaction = db.transaction(["messageStore"], "readonly"); transaction.oncomplete = (event) => { }; transaction.onerror = (event) => { console.error("ChatRecorder: dbReadAllPlayerId transaction onerror"); }; const store = transaction.objectStore("messageStore"); const index = store.index("targetPlayerId"); const keyRange = null; return new Promise((resolve, reject) => { const resultList = []; index.openCursor(keyRange, "nextunique").onerror = (event) => { resolve(resultList); }; index.openCursor(keyRange, "nextunique").onsuccess = (event) => { const cursor = event.target.result; if (cursor) { resultList.push(cursor.value); cursor.continue(); } else { resolve(resultList); } }; }); } function getTargetPlayerFromMessage(message) { if (message.channel_url.startsWith("public_")) { return null; // Ignore Globla, Trade, etc. } else if (message.channel_url.startsWith("faction-")) { return { id: "faction", name: "Faction" }; // Faction chat. } // Private chats. const selfId = getSelfIdFromSession(); const selfName = getSelfNameFromSession(); if (!selfId || !selfName) { return { id: "others", name: "Other" }; } const strList = message.channel_url.split("-"); if (strList.length !== 3) { return { id: "others", name: "Other" }; } let target = { id: "others", name: "Other" }; if (parseInt(strList[1]) === selfId) { target.id = strList[2]; target.name = strList[2]; } else if (parseInt(strList[2]) === selfId) { target.id = strList[1]; target.name = strList[1]; } else { return { id: "others", name: "Other" }; } return target; } function getSelfIdFromSession() { let index = Object.keys(sessionStorage).findIndex((item) => item.startsWith("sidebarData")); if (index >= 0) { let sidebarData = JSON.parse(sessionStorage.getItem(sessionStorage.key(index))); let userID = sidebarData.user.userID; return userID; } return null; } function getSelfNameFromSession() { let index = Object.keys(sessionStorage).findIndex((item) => item.startsWith("sidebarData")); if (index >= 0) { let sidebarData = JSON.parse(sessionStorage.getItem(sessionStorage.key(index))); let userID = sidebarData.user.name; return userID; } return null; } function initCSS() { const isDarkmode = $("body").hasClass("dark-mode"); GM_addStyle(`.chat-control-panel-popup { position: fixed; top: 10%; left: 15%; border-radius: 10px; padding: 10px; background: ${isDarkmode ? "#282828" : "#F0F0F0"}; z-index: 1000; display: none; } .chat-control-panel-results { padding: 10px; } .chat-control-player { margin: 4px 4px 4px 4px !important; display: inline-block !important; } .chat-control-panel-overlay { position: fixed; top: 0; left: 0; background: ${isDarkmode ? "#404040" : "#B0B0B0"}; width: 100%; height: 100%; opacity: 0.7; z-index: 900; display: none; } div#chat-player-list { overflow-y: scroll; height: 50px; } a.chat-history-search:hover { color: #318CE7 !important; } .chat-control-panel-item { display: inline-block; margin: 2px 2px 2px 2px; }`); } function initControlPanel() { const $title = $("div#top-page-links-list"); if ($title.length === 0) { console.error("ChatRecorder: nowhere to put control panel button"); } const $controlBtn = $(`<a id="chatHistoryControl" class="t-clear h c-pointer right last"> <span class="icon-wrap svg-icon-wrap"> <span class="link-icon-svg"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 10.33"><defs><style>.cls-1{opacity:0.35;}.cls-2{fill:#fff;}.cls-3{fill:#777;}</style></defs><g id="Слой_2" data-name="Слой 2"><g id="icons"><g class="cls-1"><path class="cls-2" d="M10,5.67a2,2,0,0,1-4,0,1.61,1.61,0,0,1,0-.39A1.24,1.24,0,0,0,7.64,3.7a2.19,2.19,0,0,1,.36,0A2,2,0,0,1,10,5.67ZM8,1C3,1,0,5.37,0,5.37s3.22,5,8,5c5.16,0,8-5,8-5S13.14,1,8,1ZM8,9a3.34,3.34,0,1,1,3.33-3.33A3.33,3.33,0,0,1,8,9Z"></path></g><path class="cls-3" d="M10,4.67a2,2,0,0,1-4,0,1.61,1.61,0,0,1,0-.39A1.24,1.24,0,0,0,7.64,2.7a2.19,2.19,0,0,1,.36,0A2,2,0,0,1,10,4.67ZM8,0C3,0,0,4.37,0,4.37s3.22,5,8,5c5.16,0,8-5,8-5S13.14,0,8,0ZM8,8a3.34,3.34,0,1,1,3.33-3.33A3.33,3.33,0,0,1,8,8Z"></path></g></g></svg> </span> </span> <span>ChatRecorder</span> </a>`); $title.append($controlBtn); const $controlPanelDiv = $(`<div id="chatControlPanel" class="chat-control-panel-popup">control</div>`); const $controlPanelOverlayDiv = $(`<div id="chatControlOverlayPanel" class="chat-control-panel-overlay"></div>`); $controlPanelDiv.html(` <input type="text" class="chat-control-panel-item" id="chat-target-id-input" placeholder="Player ID" size="10" /> <button id="chat-search" class="chat-control-panel-item" style="cursor: pointer;">Search</button><br> <div id="chat-player-list"></div><br> <textarea readonly id="chat-results" cols="120" rows="30"></textarea> `); // Control panel onClick listeners $controlPanelDiv.find("button#chat-search").click(function () { const inputId = $controlPanelDiv.find("input#chat-target-id-input").val(); dbReadByTargetPlayerId(inputId).then((result) => { let text = ""; for (const message of result) { const timeStr = formatDateString(new Date(message.timestamp)); text += timeStr + " " + message.senderPlayerName + ": " + message.messageText + "\n"; } text += "Found " + result.length + " records\n"; const $textarea = $("textarea#chat-results"); $textarea.val(text); $textarea.scrollTop($textarea[0].scrollHeight); }); }); $title.append($controlPanelDiv); $title.append($controlPanelOverlayDiv); $controlBtn.click(function () { dbReadAllPlayerId().then((result) => { const $playerListDiv = $controlPanelDiv.find("div#chat-player-list"); $playerListDiv.empty(); let num = 0; for (const message of result) { if (num == 8) { $playerListDiv.append($(`<br>`)); num = -1; } num++; let a = $(`<a class="chat-control-player">${message.targetPlayerName}</a>`); a.click(() => { $controlPanelDiv.find("input#chat-target-id-input").val(message.targetPlayerId); $controlPanelDiv.find("button#chat-search").trigger("click"); }); $playerListDiv.append(a); } $controlPanelDiv.fadeToggle(200); $controlPanelOverlayDiv.fadeToggle(200); }); }); $controlPanelOverlayDiv.click(function () { $controlPanelDiv.fadeOut(200); $controlPanelOverlayDiv.fadeOut(200); }); } function formatDateString(date) { const pad = (v) => { return v < 10 ? "0" + v : v; }; let year = date.getFullYear(); let month = pad(date.getMonth() + 1); let day = pad(date.getDate()); let hour = pad(date.getHours()); let min = pad(date.getMinutes()); let sec = pad(date.getSeconds()); return year + "/" + month + "/" + day + " " + hour + ":" + min + ":" + sec; } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址