Forked from unarist/mastodon-sstp-over-http.user.js
Last active
June 8, 2024 11:54
-
-
Save nikolat/c945aa7dce03dd86a43c947a32fb83b9 to your computer and use it in GitHub Desktop.
SSTP over HTTP で喋らせるボタンを生やすやつ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Mastodon - SSTP over HTTP で喋らせるボタンを生やすやつ | |
| // @namespace https://github.com/unarist/ | |
| // @version 0.1 | |
| // @author unarist | |
| // @match https://mstdn.maud.io/* | |
| // @match https://ukadon.shillest.net/* | |
| // @grant GM.xmlHttpRequest | |
| // @connect 127.0.0.1 | |
| // @downloadURL https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb/raw/mastodon-sstp-over-http.user.js | |
| // @run-at document-idle | |
| // @noframes | |
| // ==/UserScript== | |
| /* | |
| 使い方 | |
| * SSP 2.6.33 以上を起動しておく | |
| * このUserScriptを入れると返信ボタンとかに並んでうかどんアイコンが出てるので、それを押す | |
| * その投稿の内容を SSTP over HTTP で送信して、ゴーストが喋る | |
| よくある質問 | |
| * SSPはCORS全許可でしょ?なんでGM_XHRがいるの? → CSPでブロックされるので… | |
| * うかどん以外の一部鯖で使ったら追加のアクセス許可を求められるのはなんで? → 画像を別オリジンに置いてる鯖はそっちも許可がいるので… | |
| */ | |
| (function() { | |
| 'use strict'; | |
| const tag = (name, props = {}, children = []) => { | |
| const e = Object.assign(document.createElement(name), props); | |
| if (typeof props.style === "object") Object.assign(e.style, props.style); | |
| (children.forEach ? children : [children]).forEach(c => e.appendChild(c)); | |
| return e; | |
| }; | |
| const fetchBlob = url => new Promise((res,rej) => GM.xmlHttpRequest({ | |
| method:'GET', | |
| url, | |
| responseType:"blob", | |
| onload:res, | |
| onerror:rej, | |
| })).then(x=>x.response); | |
| const postSSTP = body => new Promise((res,rej) => GM.xmlHttpRequest({ | |
| method:'POST', | |
| url:'http://127.0.0.1:9801/api/sstp/v1', | |
| headers: {"Content-Type": "text/plain"}, | |
| data: body, | |
| responseType:"text", | |
| onload:res, | |
| onerror:rej, | |
| })).then(x=>x.response) | |
| const getDataURL = async (url, maxw, maxh) => { | |
| const img = await createImageBitmap(await fetchBlob(url)); | |
| const scale = Math.min(1, maxw / img.width, maxh / img.height); | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = img.width * scale; | |
| canvas.height = img.height * scale; | |
| const ctx = canvas.getContext("2d"); | |
| ctx.drawImage(img, 0, 0, canvas.width, canvas.height); | |
| return canvas.toDataURL(); | |
| } | |
| const onClick = async e => { | |
| const root = e.target.closest('.status__wrapper'); | |
| const avatar_url = root.querySelector('.account__avatar img').src; | |
| const display_name = root.querySelector('.display-name__html').textContent.trim(); | |
| const acct = root.querySelector('.display-name__account').textContent.trim(); | |
| const body = root.ariaLabel.replace(new RegExp(`^[^,]+, (.*), [^,]+, ${acct.slice(1)}(, [^,]+)?$`, "s"), "$1").trim().replace(/\n/g, "\\n"); | |
| const media_urls = [...root.querySelectorAll('.media-gallery__item-thumbnail img')].map(x => x.src); | |
| let script = ""; | |
| script += `\\![set,balloonwait,0]\\0\\_b["${await getDataURL(avatar_url, 32, 32)}",inline]\\_l[@4,]${display_name}\\n\\_l[@36,]${acct}\\_l[0,36]`; | |
| script += `\\![set,balloonwait]${body}`; | |
| if (media_urls) { | |
| script += "\\n" + (await Promise.all(media_urls.map(async url => `\\_b["${await getDataURL(url, 240, 80)}",inline]`))).join(" "); | |
| } | |
| script += "\\e"; | |
| postSSTP(`NOTIFY SSTP/1.1 | |
| Sender: ぶらうざのゆーざーすくりぷと | |
| Script: ${script} | |
| Charset: UTF-8`).then(console.debug); | |
| }; | |
| new MutationObserver(() => { | |
| for (const el of document.querySelectorAll('.status__action-bar:not(.__ukabutton)')) { | |
| el.classList.add('__ukabutton'); | |
| el.append(tag('button', { | |
| className: 'icon-button', | |
| style: "width: 18px; height: 18px; background: no-repeat center/18px url(https://ukadon.shillest.net/favicon.ico); opacity: 0.5;", | |
| onclick: onClick | |
| })); | |
| } | |
| }).observe(document.body, {childList: 1, subtree:1}); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment