您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add a button to download all letters in a text file
// ==UserScript== // @name SlowlyWebUtility // @namespace heshui // @version 1.5 // @description Add a button to download all letters in a text file // @license GPLv3 // @author heshui // @match web.slowly.app/* // @icon https://www.google.com/s2/favicons?sz=64&domain=slowly.app // @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js // @grant none // ==/UserScript== (function() { 'use strict'; const API_URL = `https://api.getslowly.com` /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts, that detects and handles AJAXed content. From: https://git.io/vMmuf */ function waitForKeyElements(selectorTxt, /* Required: The jQuery selector string that specifies the desired element(s). */ actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */ bWaitOnce, /* Optional: If false, will continue to scan for new elements even after the first match is found. */ iframeSelector /* Optional: If set, identifies the iframe to search. */ ) { var targetNodes, btargetsFound; if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt); else targetNodes = $(iframeSelector).contents().find(selectorTxt); if (targetNodes && targetNodes.length > 0) { btargetsFound = true; /*--- Found target node(s). Go through each and act if they are new. */ targetNodes.each(function() { var jThis = $(this); var alreadyFound = jThis.data('alreadyFound') || false; if (!alreadyFound) { //--- Call the payload function. var cancelFound = actionFunction(jThis); if (cancelFound) btargetsFound = false; else jThis.data('alreadyFound', true); } }); } else { btargetsFound = false; } //--- Get the timer-control variable for this selector. var controlObj = waitForKeyElements.controlObj || {}; var controlKey = selectorTxt.replace(/[^\w]/g, "_"); var timeControl = controlObj[controlKey]; //--- Now set or clear the timer as appropriate. if (btargetsFound && bWaitOnce && timeControl) { //--- The only condition where we need to clear the timer. clearInterval(timeControl); delete controlObj[controlKey] } else { //--- Set a timer, if needed. if (!timeControl) { timeControl = setInterval(function() { waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector); }, 300); controlObj[controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } function download(filename, text) { let element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } function getFriends(token) { return fetch(API_URL + "/users/me/friends/v2?token=" + token).then((res)=>res.json()).then((res)=>res).catch((error)=>{ throw error; } ); } function getLetters(token, id, page) { return fetch(API_URL + "/friend/" + id + "/all?ver=70200&token=" + token + "&page=" + page).then((res)=>res.json()).then((res)=>res).catch((error)=>{ throw error; } ); } async function getAllLetters(token, id) { let page = 1 let letters = await getLetters(token, id, page) while (letters.comments.next_page_url) { let nextLetters = await getLetters(token, id, ++page) letters.comments.data = letters.comments.data.concat(nextLetters.comments.data) letters.comments.next_page_url = nextLetters.comments.next_page_url } return letters } function parseLetter(letter, index, letters) { return `${index+1} / ${letters.length}\nSent: ${letter.created_at}\nDelivered: ${letter.deliver_at}\nFrom: ${letter.name}, ${letter.sent_from}\nStamp: ${letter.stamp}\n\n${letter.body}` } function loadMe(){ var me = JSON.parse(JSON.parse(localStorage["persist:slowly"])['me']) return me } async function downloadAllLetters() { let me = loadMe() let token = me.token let friends = await getFriends(token) let currentFriendElem = document.getElementsByClassName('friend-header-wrapper') if (!currentFriendElem.length) { throw new Error('No friend selected') } let friendName = currentFriendElem[0].getElementsByClassName('text-primary')[0].textContent let friend = friends.friends.find(obj=>obj.name === friendName); let letters = await getAllLetters(token, friend.id) let lettersText = letters.comments.data.reverse().map(parseLetter).join('\n\n------------------------------------------------------\n') download(friendName + '.txt', lettersText) } waitForKeyElements(".h5.mb-1.ml-n1", addDownloadButton) waitForKeyElements(".table.table-borderless.profile-list.table-sm", addLetterCounter) function addDownloadButton() { var button = document.createElement("span") , btnStyle = button.style button.className = "badge badge-pill badge-primary badge-search link" button.innerHTML = `<i class="icon-download"></i> Download ` button.onclick = downloadAllLetters var badgeList = document.getElementsByClassName("h5 mb-1 ml-n1")[0] if (!badgeList) { return; } badgeList.insertBefore(button, badgeList.children[0].nextSibling) } function addLetterCounter(){ let me = loadMe() let ratioText = me.ratioText let ratio = me.ratio let profileTable = document.getElementsByClassName("table table-borderless profile-list table-sm")[0] if (!profileTable || !window.location.pathname.startsWith("/profile")) { return; } profileTable = profileTable.children[0] var ratioLine = document.createElement("tr") ratioLine.innerHTML = `<td class="icon-td"><i class="icon-stats text-calm"></i></td><td>Sent:Received Ratio ( ${ratioText} )</td>` profileTable.insertBefore(ratioLine, profileTable.children[3]) var srLine = document.createElement("tr") srLine.innerHTML = `<td></td><td class="pt-0 mt-0 small muted text-lighter">Sent: ${ratio.sent} Received: ${ratio.received}</td>` profileTable.insertBefore(srLine, ratioLine.nextSibling) } } )();