Amino Chat Grabber

A utility to grab and compile chat histories, for parsing, archiving or viewing in an accompanying WIP chat history viewer.

当前为 2022-08-03 提交的版本,查看 最新版本

// ==UserScript==
// @name         Amino Chat Grabber
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  A utility to grab and compile chat histories, for parsing, archiving or viewing in an accompanying WIP chat history viewer.
// @author       Rasutei
// @match        https://aminoapps.com
// @icon         https://www.google.com/s2/favicons?sz=64&domain=aminoapps.com
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==
/* eslint-disable curly */

const logcss = `
	font-family: Hack, monospace;
	text-shadow: 0 0 10px black, 0 0 10px black;
	background: linear-gradient(to right, #4d94ff 0%, #4d94ff 8px, rgb(77 148 255 / 30%) 8px, transparent 50px);
	color: #4d94ff;
	padding: 2px 0 2px 30px;
	`
const warncss = `
	font-family: Hack, monospace;
	text-shadow: 0 0 10px black, 0 0 10px black;
	background: linear-gradient(to right, #ffa621 0%, #ffa621 0% 8px, rgb(255 166 33 / 30%) 8px, transparent 50px);
	color: #ffa621;
	padding: 2px 0 2px 30px;
	`
const log = (e) => console.log('%c'+e, logcss)
const warn = (e) => console.log('%c'+e, warncss)
const warn_element = (e) => console.log('%c%o', warncss, e)

function Community(name){
	this.name = name
	this.link = ''
	this.icon = ''
	this.chats = []
}
function Entry(username){
	this.username = username
	this.link = ''
	this.avatar = ''
	this.cover = ''
	this.oldest_timestamp = ''
	this.history = []
}
function Message(){
	this.type = ''
	this.user = ''
	this.content = ''
}

let chat
let msglist
let rb = 0

window.addEventListener('load', function(){
	log('Chat Grabber: Loading...')
	let hackfont = document.createElement('link')
	hackfont.setAttribute('rel','stylesheet')
	hackfont.setAttribute('type','text/css')
	hackfont.setAttribute('href','//cdn.jsdelivr.net/npm/[email protected]/build/web/hack-subset.css')
	document.head.appendChild(hackfont);
	let btnCSS = `
		box-sizing: border-box;
		width: 260px;
		background: rgb(10 10 10);
		border: solid 2px hsl(var(--rainbow), 100%, 50%);
		color: hsl(var(--rainbow), 100%, 90%);
		font-family: Hack;
		font-size: 14px;
		margin-top: -2px;
		cursor: pointer;
		opacity: 0;
		display: none;`
	let wrapper = document.createElement("wrapper")
	wrapper.className = "master-wrapper"
	wrapper.style.cssText = `
		position:fixed;
		top:0;
		left:0;
		z-index:99999999;
		display:flex;
		flex-direction:column;`
	wrapper.style.setProperty('--rainbow', '0deg')
	let alwayson = document.createElement("div")
	alwayson.style.order = 0
	wrapper.appendChild(alwayson)
	let toggle = document.createElement("button")
	toggle.className = "ras_button toggle nohide"
	toggle.innerText = '▣'
	toggle.style.cssText = btnCSS
	toggle.style.fontSize = '16px'
	toggle.style.display = 'block'
	toggle.style.float = 'left'
	toggle.style.opacity = 1
	// toggle.style.order = 0
	toggle.style.width = '21px'
	toggle.style.height = '21px'
	toggle.style.padding = 0
	toggle.style.margin = 0
	toggle.onclick = function(){
		wrapper.querySelectorAll(".ras_button").forEach(m => {
		    if (!m.classList.contains('nohide')){
				 m.style.opacity = +!+(m.style.opacity)
				 if (m.style.display != 'none') m.style.display = 'none'
				 else m.style.display = 'inline-block'
			 }
		})
	}
	alwayson.appendChild(toggle)
	let title = document.createElement("button")
	title.className = "ras_button title nohide"
	title.innerText = 'Chat Grabber 1.6, by Rasutei'
	title.style.cssText = btnCSS
	title.style.display = 'block'
	title.style.opacity = 1
	// title.style.order = 1
	title.style.height = '21px'
	title.style.width = '241px'
	title.style.float = 'left'
	title.style.padding = 0
	title.style.margin = '0 0 0 -2px'
	alwayson.appendChild(title)
	let genBase = document.createElement("button")
	genBase.className = "ras_button gen-base"
	genBase.innerText = 'Generate base JSON structure'
	genBase.onclick = GenBase
	genBase.style.cssText = btnCSS
	genBase.style.order = 2
	genBase.style.opacity = 0
	wrapper.appendChild(genBase)
	let cmpCommunity = document.createElement("button")
	cmpCommunity.className = "ras_button gen-community"
	cmpCommunity.innerText = 'GenJSON cur. comm'
	cmpCommunity.onclick = GrabCommunity
	cmpCommunity.style.cssText = btnCSS
	cmpCommunity.style.order = 3
	cmpCommunity.style.opacity = 0
	wrapper.appendChild(cmpCommunity)
	let cmpCurrent = document.createElement("button")
	cmpCurrent.className = "ras_button gen-chat"
	cmpCurrent.innerText = 'GenJSON cur. chat'
	cmpCurrent.onclick = GrabCurrent
	cmpCurrent.style.cssText = btnCSS
	cmpCurrent.style.order = 4
	cmpCurrent.style.opacity = 0
	wrapper.appendChild(cmpCurrent)
	document.body.insertBefore(wrapper, document.body.firstChild)
	setInterval(function(){
		wrapper.style.setProperty('--rainbow', rb+++'deg')
		if (rb == 360) rb = 0
	}, 20)
	log('Chat Grabber: Loaded.')
})

function Update(){
	chat = document.querySelector("iframe").contentDocument
	msglist = chat.querySelector(".message-list")
}
function GenBase(){
	let gen =
`{
    "gen_version": 1.6,
    "cur_version": 1.6,
	"communities":[
		// Add JSON structures of communities
		// here, separated by commas.
		// Example structure:

		// {
		//	"name": "",
		//	"link": "",
		//	"chats": [
		//      {
		//          "username": "",
		//          "link": "",
		//          "avatar": "",
		//          "cover": "",
		//          "oldest_timestamp": "",
		//          "history": [
		//              {
		//                  "type": "", "user": "",
		//                  "content": ""
		//              },
		//              [...]
		//              {
		//                  "type": "", "user": "",
		//                  "content": ""
		//              }
		//          ]
		//		},
		//	]
		// },
		// [...]


		// ### IMPORTANT! ###
		// DELETE ALL COMMENTS BEFORE USE
		// (Comments are lines starting in "//")
	]
}
`
	console.log(gen)
	if(confirm('Done. Would you like to have the resulting JSON string copied to the clipboard?'))
		setTimeout(() => navigator.clipboard.writeText(gen), 200)
	else alert('JSON string not copied to the clipboard.\nAccess the browser\'s console to view or copy it.')
}
async function GrabCommunity(){
	Update()
	let entry = new Community(JSONSafe(chat.querySelector(".community-title").textContent.trim()))
	/* Grab community name, link and icon */
	entry.link = chat.querySelector(".community-title :first-child").href
	entry.icon = chat.querySelector(".community-title img.logo").src
	/* Post resulting entry */
	let gen = JSON.stringify(entry, null, "\t")
	console.log(gen)
	/* Copying resulting entry to clipboard */
	if(confirm('Done. Would you like to have the resulting JSON string copied to the clipboard?'))
		setTimeout(() => navigator.clipboard.writeText(gen), 200)
	else alert('JSON string not copied to the clipboard.\nAccess the browser\'s console to view or copy it.')
}
async function GrabCurrent(){
	Update()
	/* Create entry for current chat */
	let user = JSONSafe(chat.querySelector(".thread-title").textContent.trim())
	let entry = await new Entry(user)
	/* Grab images */
	chat.querySelector(".user-message:not(.from-me)").querySelector(".message-author.cover-img").click()
	await new Promise(r => setTimeout(r, 500));
	let profile = chat.querySelector(".user-profile")
	if (profile.querySelector(".user-cover .img-cover"))
		entry.cover = profile.querySelector(".user-cover .img-cover").src
	if (profile.querySelector(".user-link"))
		entry.link = profile.querySelector(".user-link").href
	if (profile.querySelector(".avatar"))
		entry.avatar = profile.querySelector(".avatar").src
	/* Scroll chat history up as far as possible */
	let message_count = 0;
	let prv_msg_count;
	while (message_count != prv_msg_count){
		prv_msg_count = message_count
		// console.log("Scrolling...")
		msglist.scrollTo(top)
		await new Promise(r => setTimeout(r, 300))
		message_count = msglist.childElementCount
	}
	/* Grab oldest timestamp */
	if (chat.querySelector(".timestamp"))
		entry.oldest_timestamp = chat.querySelector(".timestamp").textContent.trim()
	/* Compile messages */
	let messages = Array.from(msglist.children);
	messages.forEach(m => {
		if (m.classList.contains("user-message")){
			/* Create message object */
			let msg = new Message()
			/* Set message author */
			if (m.classList.contains("from-me"))
				msg.user = "Me"
			else
				msg.user = user
			/* Set message content */
			/* If message is a sticker */
			if (m.querySelector(".sticker-message")){
				msg.type = "sticker"
				msg.content = m.querySelector(".sticker-message").firstChild.src
			}
			/* If message is an image */
			else if (m.querySelector(".img-msg")){
				msg.type = "image"
				msg.content = m.querySelector(".img-msg").firstChild.src
			}
			/* If message is a voice message */
			else if (m.querySelector(".voice-message-container")){
				msg.type = "audio"
				msg.content = m.querySelector(".voice-message-container audio").src
			}
			/* If message is text */
			else if (m.querySelector(".text-msg")){
				msg.type = "text"
				msg.content = JSONSafe(m.querySelector(".text-msg").innerHTML)
				/* Replace tags and restore formatting information */
				msg.content = msg.content.replaceAll('<p>','')
				msg.content = msg.content.replaceAll('</p>','\n')
				msg.content = msg.content.replaceAll('<p class="', '[')
				msg.content = msg.content.replaceAll('">', ']')
				msg.content = msg.content.replaceAll('center', 'C')
				msg.content = msg.content.replaceAll('italic', 'I')
				msg.content = msg.content.replaceAll('bolder', 'B')
				msg.content = msg.content.replaceAll('strike', 'S')
				msg.content = msg.content.replaceAll('underline', 'U')
				let toreplace = msg.content.substring(msg.content.indexOf('[')+1,msg.content.indexOf(']'))
				msg.content = msg.content.replace(toreplace, toreplace.replaceAll(' ', ''))
				/* Remove trailing line break */
				msg.content = msg.content.substring(0, msg.content.length-1)
				/* Try to check if message is meant to be a comment in a scene, or out-of-character */
				let rawmsg = msg.content.substring(((msg.content.indexOf(']') == -1)? 0 : msg.content.indexOf(']')+2))
				let cmt = rawmsg.startsWith('||') || rawmsg.startsWith('((') || rawmsg.endsWith('||') || rawmsg.endsWith('))')
				if (cmt) msg.type += " comment"
			}
			/* If message is unknown type */
			else{
				warn("Uncaught message type: "+m.querySelector(".message-content :first-child").className)
				console.group("Message")
				warn_element(m)
				console.groupEnd()
			}
			entry.history.push(msg)
		}
	})
	/* Post resulting entry */
	let gen = JSON.stringify(entry, null, "\t").replaceAll(',\n\t\t\t"user',', "user').replaceAll('},\n\t\t{','},{').replaceAll('\n','\n\t\t\t\t')
	console.log(gen)
	/* Copying resulting entry to clipboard */
	if(confirm('Done. Would you like to have the resulting JSON string copied to the clipboard?'))
		setTimeout(() => navigator.clipboard.writeText(gen), 200)
	else alert('JSON string not copied to the clipboard. Access the browser\'s console to view or copy it.')
}
function JSONSafe(s){
	return s.replaceAll('"', '\"')
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址