Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save nikolat/c945aa7dce03dd86a43c947a32fb83b9 to your computer and use it in GitHub Desktop.

Select an option

Save nikolat/c945aa7dce03dd86a43c947a32fb83b9 to your computer and use it in GitHub Desktop.

Revisions

  1. nikolat revised this gist Jun 8, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion nostr-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -82,7 +82,7 @@
    switch (document.domain) {
    //nostter
    case 'nostter.app':
    root = e.target.closest('main > div.timeline > ul.timeline > li > article');
    root = e.target.closest('main > div.timeline div > div > article');
    avatar_url = root.querySelector('div > a > img.picture').src;
    display_name = root.querySelector('.display_name').textContent.trim();
    acct = root.querySelector('.name').textContent.trim();
  2. nikolat revised this gist Feb 25, 2024. 1 changed file with 4 additions and 32 deletions.
    36 changes: 4 additions & 32 deletions bluesky-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -4,9 +4,7 @@
    // @version 0.1
    // @contributor unarist https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb
    // @author nikolat https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9
    // @match https://penpenpng.github.io/skylight/*
    // @match https://flat-bs.vercel.app/*
    // @match https://tokimekibluesky.vercel.app/*
    // @match https://tokimeki.blue/*
    // @grant GM.xmlHttpRequest
    // @homepage https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9
    // @downloadURL https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9/raw/4d488784ae5c9a38ab4957a21239b7668da46ed5/bluesky-sstp-over-http.user.js
    @@ -81,26 +79,8 @@
    let body;
    let media_urls;
    switch (document.domain) {
    //Skylight
    case 'penpenpng.github.io':
    root = e.target.closest('.tile-post');
    avatar_url = root.querySelector('.tile-icon img').src;
    display_name = root.querySelector('.width-user-name').textContent.trim();
    acct = '';
    body = root.querySelector('.tile-subtitle span').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.image-container img')].map(x => x.src);
    break;
    //Flat
    case 'flat-bs.vercel.app':
    root = e.target.closest('article[class*="container-"]');
    avatar_url = root.querySelector('.Avatar img') ? root.querySelector('.Avatar img').src : '';
    display_name = root.querySelector('a[class*="displayName-"]').textContent.trim();
    acct = root.querySelector('span[class*="name-"]').textContent.trim();
    body = root.querySelector('p[class*="prose-"]').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('div[class*="embed-"] img')].map(x => x.src);
    break;
    //TOKIMEKI Bluesky
    case 'tokimekibluesky.vercel.app':
    case 'tokimeki.blue':
    root = e.target.closest('.timeline__item');
    avatar_url = root.querySelector('.timeline__image img') ? root.querySelector('.timeline__image img').src : '';
    display_name = root.querySelector('.timeline__user').textContent.trim();
    @@ -131,16 +111,8 @@ Charset: UTF-8`).then(console.debug);
    new MutationObserver(() => {
    let q;
    switch (document.domain) {
    //Skylight
    case 'penpenpng.github.io':
    q = '.mt-2:not(.__ukabutton)';
    break;
    //Flat
    case 'flat-bs.vercel.app':
    q = 'ul[class^="reactionList-"]:not(.__ukabutton)';
    break;
    //TOKIMEKI Bluesky
    case 'tokimekibluesky.vercel.app':
    case 'tokimeki.blue':
    q = '.timeline-reaction:not(.__ukabutton)';
    break;
    default:
    @@ -155,4 +127,4 @@ Charset: UTF-8`).then(console.debug);
    }));
    }
    }).observe(document.body, {childList: 1, subtree:1});
    })();
    })();
  3. nikolat revised this gist Feb 25, 2024. 1 changed file with 9 additions and 51 deletions.
    60 changes: 9 additions & 51 deletions nostr-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -4,11 +4,8 @@
    // @version 0.1
    // @contributor unarist https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb
    // @author nikolat https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9
    // @match https://snort.social/*
    // @match https://iris.to/*
    // @match https://nostter.vercel.app/*
    // @match https://syusui-s.github.io/rabbit/*
    // @match https://astraea.mousedev.page/*
    // @match https://nostter.app/*
    // @match https://rabbit.syusui.net/*
    // @grant GM.xmlHttpRequest
    // @homepage https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9
    // @downloadURL https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9/raw/4d488784ae5c9a38ab4957a21239b7668da46ed5/nostr-sstp-over-http.user.js
    @@ -83,51 +80,24 @@
    let body;
    let media_urls;
    switch (document.domain) {
    //Snort
    case 'snort.social':
    root = e.target.closest('.note');
    avatar_url = root.querySelector('.avatar').getAttribute('style').replace(/--img-url:url\((http.+)\);/g, '$1').replace(/\\/g, '').trim();
    display_name = root.querySelector('.display-name div').textContent.trim();
    acct = '';
    body = root.querySelector('.body .text').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.body .text img')].map(x => x.src);
    break;
    //iris
    case 'iris.to':
    root = e.target.closest('.msg-content');
    avatar_url = root.querySelector('.identicon img').src;
    display_name = root.querySelector('.display-name').textContent.trim();
    acct = root.querySelector('.user-name') ? root.querySelector('.user-name').textContent.trim() : '';
    body = root.querySelector('.text').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.img-container img')].map(x => x.src);
    break;
    //nostter
    case 'nostter.vercel.app':
    root = e.target.closest('main > ul > li > article');
    case 'nostter.app':
    root = e.target.closest('main > div.timeline > ul.timeline > li > article');
    avatar_url = root.querySelector('div > a > img.picture').src;
    display_name = root.querySelector('.display_name').textContent.trim();
    acct = root.querySelector('.name').textContent.trim();
    body = root.querySelector('.content').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.media img')].map(x => x.src);
    break;
    //Rabbit
    case 'syusui-s.github.io':
    case 'rabbit.syusui.net':
    root = e.target.closest('.shrink-0');
    avatar_url = root.querySelector('.author-icon img').src;
    display_name = root.querySelector('.author-name').textContent.trim();
    acct = root.querySelector('.author-username').textContent.trim();
    body = root.querySelector('.content span').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.content img')].map(x => x.src);
    break;
    //Astraea
    case 'astraea.mousedev.page':
    root = e.target.closest('div.border.p-4.rounded.mt-4');
    avatar_url = root.querySelector('.flex.text-inherit.w-fit img').src;
    display_name = root.querySelector('div.pl-4 > div').textContent.trim();
    acct = '';
    body = root.querySelector('.mt-4 > div > p').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.flex.svelte-lightbox-thumbnail img')].map(x => x.src);
    break;
    default:
    break;
    }
    @@ -151,25 +121,13 @@ Charset: UTF-8`).then(console.debug);
    new MutationObserver(() => {
    let q;
    switch (document.domain) {
    //Snort
    case 'snort.social':
    q = '.footer-reactions:not(.__ukabutton)';
    break;
    //iris
    case 'iris.to':
    q = '.below-text:not(.__ukabutton)';
    break;
    //nostter
    case 'nostter.vercel.app':
    case 'nostter.app':
    q = '.action-menu:not(.__ukabutton)';
    break;
    //Rabbit
    case 'syusui-s.github.io':
    q = '.min-w-0.flex-auto > .items-center:not(.__ukabutton)';
    break;
    //Astraea
    case 'astraea.mousedev.page':
    q = '.mt-4.flex > div > .flex.gap-4:not(.__ukabutton)';
    case 'rabbit.syusui.net':
    q = '.actions:not(.__ukabutton)';
    break;
    default:
    break;
    @@ -183,4 +141,4 @@ Charset: UTF-8`).then(console.debug);
    }));
    }
    }).observe(document.body, {childList: 1, subtree:1});
    })();
    })();
  4. nikolat revised this gist Mar 13, 2023. 2 changed files with 6 additions and 6 deletions.
    6 changes: 3 additions & 3 deletions bluesky-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -3,13 +3,13 @@
    // @namespace https://github.com/nikolat/
    // @version 0.1
    // @contributor unarist https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb
    // @author nikolat https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @author nikolat https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9
    // @match https://penpenpng.github.io/skylight/*
    // @match https://flat-bs.vercel.app/*
    // @match https://tokimekibluesky.vercel.app/*
    // @grant GM.xmlHttpRequest
    // @homepage https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @downloadURL https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b/raw/949153803b1b36fad6a67c25f351410723e7703e/bluesky-sstp-over-http.user.js
    // @homepage https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9
    // @downloadURL https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9/raw/4d488784ae5c9a38ab4957a21239b7668da46ed5/bluesky-sstp-over-http.user.js
    // @run-at document-idle
    // @noframes
    // @license Unknown (as-is)
    6 changes: 3 additions & 3 deletions nostr-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -3,15 +3,15 @@
    // @namespace https://github.com/nikolat/
    // @version 0.1
    // @contributor unarist https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb
    // @author nikolat https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @author nikolat https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9
    // @match https://snort.social/*
    // @match https://iris.to/*
    // @match https://nostter.vercel.app/*
    // @match https://syusui-s.github.io/rabbit/*
    // @match https://astraea.mousedev.page/*
    // @grant GM.xmlHttpRequest
    // @homepage https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @downloadURL https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b/raw/949153803b1b36fad6a67c25f351410723e7703e/bluesky-sstp-over-http.user.js
    // @homepage https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9
    // @downloadURL https://gist.github.com/nikolat/c945aa7dce03dd86a43c947a32fb83b9/raw/4d488784ae5c9a38ab4957a21239b7668da46ed5/nostr-sstp-over-http.user.js
    // @run-at document-idle
    // @noframes
    // @license Unknown (as-is)
  5. nikolat revised this gist Mar 13, 2023. 1 changed file with 186 additions and 0 deletions.
    186 changes: 186 additions & 0 deletions nostr-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,186 @@
    // ==UserScript==
    // @name Nostr - SSTP over HTTP で喋らせるボタンを生やすやつ
    // @namespace https://github.com/nikolat/
    // @version 0.1
    // @contributor unarist https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb
    // @author nikolat https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @match https://snort.social/*
    // @match https://iris.to/*
    // @match https://nostter.vercel.app/*
    // @match https://syusui-s.github.io/rabbit/*
    // @match https://astraea.mousedev.page/*
    // @grant GM.xmlHttpRequest
    // @homepage https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @downloadURL https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b/raw/949153803b1b36fad6a67c25f351410723e7703e/bluesky-sstp-over-http.user.js
    // @run-at document-idle
    // @noframes
    // @license Unknown (as-is)
    // ==/UserScript==



    /*
    使い方
    * SSP 2.6.33 以上を起動しておく
    * このUserScriptを入れると返信ボタンとかに並んでうかどんアイコンが出てるので、それを押す
    * 「127.0.0.1に接続していいか?本当か?」みたいに言われたら、このドメインを許可とかする(SSPへの接続用)
    * その投稿の内容を SSTP over HTTP で送信して、ゴーストが喋る
    よくある質問
    * SSPはCORS全許可でしょ?なんでGM_XHRがいるの? → Mastodon側が持ってるCSPでブロックされるので…
    * うかどん以外の一部鯖で使ったら127.0.0.1以外にアクセス許可を求められるのはなんで? → 画像を別オリジンに置いてる鯖はそっちも許可がいるので…
    * @connect 127.0.0.1 書いとけばよくない? → それだけ書くと鯖ごとの別オリジンが聞かれもしないし、 @connect * は心理的に微妙だったので…
    */

    (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) => {
    if (!url) {
    return "";
    }
    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 => {
    let root;
    let avatar_url;
    let display_name;
    let acct;
    let body;
    let media_urls;
    switch (document.domain) {
    //Snort
    case 'snort.social':
    root = e.target.closest('.note');
    avatar_url = root.querySelector('.avatar').getAttribute('style').replace(/--img-url:url\((http.+)\);/g, '$1').replace(/\\/g, '').trim();
    display_name = root.querySelector('.display-name div').textContent.trim();
    acct = '';
    body = root.querySelector('.body .text').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.body .text img')].map(x => x.src);
    break;
    //iris
    case 'iris.to':
    root = e.target.closest('.msg-content');
    avatar_url = root.querySelector('.identicon img').src;
    display_name = root.querySelector('.display-name').textContent.trim();
    acct = root.querySelector('.user-name') ? root.querySelector('.user-name').textContent.trim() : '';
    body = root.querySelector('.text').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.img-container img')].map(x => x.src);
    break;
    //nostter
    case 'nostter.vercel.app':
    root = e.target.closest('main > ul > li > article');
    avatar_url = root.querySelector('div > a > img.picture').src;
    display_name = root.querySelector('.display_name').textContent.trim();
    acct = root.querySelector('.name').textContent.trim();
    body = root.querySelector('.content').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.media img')].map(x => x.src);
    break;
    //Rabbit
    case 'syusui-s.github.io':
    root = e.target.closest('.shrink-0');
    avatar_url = root.querySelector('.author-icon img').src;
    display_name = root.querySelector('.author-name').textContent.trim();
    acct = root.querySelector('.author-username').textContent.trim();
    body = root.querySelector('.content span').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.content img')].map(x => x.src);
    break;
    //Astraea
    case 'astraea.mousedev.page':
    root = e.target.closest('div.border.p-4.rounded.mt-4');
    avatar_url = root.querySelector('.flex.text-inherit.w-fit img').src;
    display_name = root.querySelector('div.pl-4 > div').textContent.trim();
    acct = '';
    body = root.querySelector('.mt-4 > div > p').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.flex.svelte-lightbox-thumbnail img')].map(x => x.src);
    break;
    default:
    break;
    }

    let script = "";
    script += `\\![set,balloonwait,0]\\0\\_b["${await getDataURL(avatar_url, 32, 32)}",inline,--option=use_self_alpha]\\_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,--option=use_self_alpha]`))).join(" ");
    }

    script += "\\e";

    postSSTP(`NOTIFY SSTP/1.1
    Sender: ぶらうざのゆーざーすくりぷと
    Script: ${script}
    Charset: UTF-8`).then(console.debug);
    };

    new MutationObserver(() => {
    let q;
    switch (document.domain) {
    //Snort
    case 'snort.social':
    q = '.footer-reactions:not(.__ukabutton)';
    break;
    //iris
    case 'iris.to':
    q = '.below-text:not(.__ukabutton)';
    break;
    //nostter
    case 'nostter.vercel.app':
    q = '.action-menu:not(.__ukabutton)';
    break;
    //Rabbit
    case 'syusui-s.github.io':
    q = '.min-w-0.flex-auto > .items-center:not(.__ukabutton)';
    break;
    //Astraea
    case 'astraea.mousedev.page':
    q = '.mt-4.flex > div > .flex.gap-4:not(.__ukabutton)';
    break;
    default:
    break;
    }
    for (const el of document.querySelectorAll(q)) {
    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});
    })();
  6. nikolat revised this gist Mar 12, 2023. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions bluesky-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -2,17 +2,17 @@
    // @name Bluesky - SSTP over HTTP で喋らせるボタンを生やすやつ
    // @namespace https://github.com/nikolat/
    // @version 0.1
    // @homepage https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @downloadURL https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b/raw/949153803b1b36fad6a67c25f351410723e7703e/bluesky-sstp-over-http.user.js
    // @contributor unarist https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb
    // @author nikolat https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @license Unknown (as-is)
    // @match https://penpenpng.github.io/skylight/*
    // @match https://flat-bs.vercel.app/*
    // @match https://tokimekibluesky.vercel.app/*
    // @grant GM.xmlHttpRequest
    // @homepage https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @downloadURL https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b/raw/949153803b1b36fad6a67c25f351410723e7703e/bluesky-sstp-over-http.user.js
    // @run-at document-idle
    // @noframes
    // @license Unknown (as-is)
    // ==/UserScript==


  7. nikolat revised this gist Mar 12, 2023. 2 changed files with 7 additions and 7 deletions.
    12 changes: 6 additions & 6 deletions bluesky-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -2,17 +2,17 @@
    // @name Bluesky - SSTP over HTTP で喋らせるボタンを生やすやつ
    // @namespace https://github.com/nikolat/
    // @version 0.1
    // @homepage https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @downloadURL https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b/raw/949153803b1b36fad6a67c25f351410723e7703e/bluesky-sstp-over-http.user.js
    // @contributor unarist https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb
    // @author nikolat https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @license Unknown (as-is)
    // @match https://penpenpng.github.io/skylight/*
    // @match https://flat-bs.vercel.app/*
    // @match https://tokimekibluesky.vercel.app/*
    // @grant GM.xmlHttpRequest
    // @homepage https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @downloadURL https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b/raw/949153803b1b36fad6a67c25f351410723e7703e/bluesky-sstp-over-http.user.js
    // @run-at document-idle
    // @noframes
    // @license Unknown (as-is)
    // ==/UserScript==


    @@ -87,7 +87,7 @@
    avatar_url = root.querySelector('.tile-icon img').src;
    display_name = root.querySelector('.width-user-name').textContent.trim();
    acct = '';
    body = root.querySelector('.tile-subtitle span').textContent.trim().replace(new RegExp(`^[^,]+, (.*), [^,]+, ${acct.slice(1)}(, [^,]+)?$`, "s"), "$1").trim().replace(/\n/g, "\\n");
    body = root.querySelector('.tile-subtitle span').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.image-container img')].map(x => x.src);
    break;
    //Flat
    @@ -96,7 +96,7 @@
    avatar_url = root.querySelector('.Avatar img') ? root.querySelector('.Avatar img').src : '';
    display_name = root.querySelector('a[class*="displayName-"]').textContent.trim();
    acct = root.querySelector('span[class*="name-"]').textContent.trim();
    body = root.querySelector('p[class*="prose-"]').textContent.trim().replace(new RegExp(`^[^,]+, (.*), [^,]+, ${acct.slice(1)}(, [^,]+)?$`, "s"), "$1").trim().replace(/\n/g, "\\n");
    body = root.querySelector('p[class*="prose-"]').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('div[class*="embed-"] img')].map(x => x.src);
    break;
    //TOKIMEKI Bluesky
    @@ -105,7 +105,7 @@
    avatar_url = root.querySelector('.timeline__image img') ? root.querySelector('.timeline__image img').src : '';
    display_name = root.querySelector('.timeline__user').textContent.trim();
    acct = '';
    body = root.querySelector('.timeline__text').textContent.trim().replace(new RegExp(`^[^,]+, (.*), [^,]+, ${acct.slice(1)}(, [^,]+)?$`, "s"), "$1").trim().replace(/\n/g, "\\n");
    body = root.querySelector('.timeline__text').textContent.trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.timeline-images-wrap img')].map(x => x.src);
    break;
    default:
    2 changes: 1 addition & 1 deletion mastodon-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -69,7 +69,7 @@
    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 body = root.getAttribute('aria-label').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 = "";
  8. nikolat revised this gist Mar 12, 2023. 1 changed file with 158 additions and 0 deletions.
    158 changes: 158 additions & 0 deletions bluesky-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,158 @@
    // ==UserScript==
    // @name Bluesky - SSTP over HTTP で喋らせるボタンを生やすやつ
    // @namespace https://github.com/nikolat/
    // @version 0.1
    // @contributor unarist https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb
    // @author nikolat https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @match https://penpenpng.github.io/skylight/*
    // @match https://flat-bs.vercel.app/*
    // @match https://tokimekibluesky.vercel.app/*
    // @grant GM.xmlHttpRequest
    // @homepage https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b
    // @downloadURL https://gist.github.com/nikolat/d66eee687101b5b5b50d13bf01a9a01b/raw/949153803b1b36fad6a67c25f351410723e7703e/bluesky-sstp-over-http.user.js
    // @run-at document-idle
    // @noframes
    // @license Unknown (as-is)
    // ==/UserScript==



    /*
    使い方
    * SSP 2.6.33 以上を起動しておく
    * このUserScriptを入れると返信ボタンとかに並んでうかどんアイコンが出てるので、それを押す
    * 「127.0.0.1に接続していいか?本当か?」みたいに言われたら、このドメインを許可とかする(SSPへの接続用)
    * その投稿の内容を SSTP over HTTP で送信して、ゴーストが喋る
    よくある質問
    * SSPはCORS全許可でしょ?なんでGM_XHRがいるの? → Mastodon側が持ってるCSPでブロックされるので…
    * うかどん以外の一部鯖で使ったら127.0.0.1以外にアクセス許可を求められるのはなんで? → 画像を別オリジンに置いてる鯖はそっちも許可がいるので…
    * @connect 127.0.0.1 書いとけばよくない? → それだけ書くと鯖ごとの別オリジンが聞かれもしないし、 @connect * は心理的に微妙だったので…
    */

    (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) => {
    if (!url) {
    return "";
    }
    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 => {
    let root;
    let avatar_url;
    let display_name;
    let acct;
    let body;
    let media_urls;
    switch (document.domain) {
    //Skylight
    case 'penpenpng.github.io':
    root = e.target.closest('.tile-post');
    avatar_url = root.querySelector('.tile-icon img').src;
    display_name = root.querySelector('.width-user-name').textContent.trim();
    acct = '';
    body = root.querySelector('.tile-subtitle span').textContent.trim().replace(new RegExp(`^[^,]+, (.*), [^,]+, ${acct.slice(1)}(, [^,]+)?$`, "s"), "$1").trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.image-container img')].map(x => x.src);
    break;
    //Flat
    case 'flat-bs.vercel.app':
    root = e.target.closest('article[class*="container-"]');
    avatar_url = root.querySelector('.Avatar img') ? root.querySelector('.Avatar img').src : '';
    display_name = root.querySelector('a[class*="displayName-"]').textContent.trim();
    acct = root.querySelector('span[class*="name-"]').textContent.trim();
    body = root.querySelector('p[class*="prose-"]').textContent.trim().replace(new RegExp(`^[^,]+, (.*), [^,]+, ${acct.slice(1)}(, [^,]+)?$`, "s"), "$1").trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('div[class*="embed-"] img')].map(x => x.src);
    break;
    //TOKIMEKI Bluesky
    case 'tokimekibluesky.vercel.app':
    root = e.target.closest('.timeline__item');
    avatar_url = root.querySelector('.timeline__image img') ? root.querySelector('.timeline__image img').src : '';
    display_name = root.querySelector('.timeline__user').textContent.trim();
    acct = '';
    body = root.querySelector('.timeline__text').textContent.trim().replace(new RegExp(`^[^,]+, (.*), [^,]+, ${acct.slice(1)}(, [^,]+)?$`, "s"), "$1").trim().replace(/\n/g, "\\n");
    media_urls = [...root.querySelectorAll('.timeline-images-wrap img')].map(x => x.src);
    break;
    default:
    break;
    }

    let script = "";
    script += `\\![set,balloonwait,0]\\0\\_b["${await getDataURL(avatar_url, 32, 32)}",inline,--option=use_self_alpha]\\_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,--option=use_self_alpha]`))).join(" ");
    }

    script += "\\e";

    postSSTP(`NOTIFY SSTP/1.1
    Sender: ぶらうざのゆーざーすくりぷと
    Script: ${script}
    Charset: UTF-8`).then(console.debug);
    };

    new MutationObserver(() => {
    let q;
    switch (document.domain) {
    //Skylight
    case 'penpenpng.github.io':
    q = '.mt-2:not(.__ukabutton)';
    break;
    //Flat
    case 'flat-bs.vercel.app':
    q = 'ul[class^="reactionList-"]:not(.__ukabutton)';
    break;
    //TOKIMEKI Bluesky
    case 'tokimekibluesky.vercel.app':
    q = '.timeline-reaction:not(.__ukabutton)';
    break;
    default:
    break;
    }
    for (const el of document.querySelectorAll(q)) {
    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});
    })();
  9. @unarist unarist revised this gist Mar 5, 2023. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions mastodon-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -73,11 +73,11 @@
    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,0]\\0\\_b["${await getDataURL(avatar_url, 32, 32)}",inline,--option=use_self_alpha]\\_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 += "\\n" + (await Promise.all(media_urls.map(async url => `\\_b["${await getDataURL(url, 240, 80)}",inline,--option=use_self_alpha]`))).join(" ");
    }

    script += "\\e";
  10. @unarist unarist revised this gist Mar 5, 2023. 1 changed file with 4 additions and 3 deletions.
    7 changes: 4 additions & 3 deletions mastodon-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,6 @@
    // @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
    @@ -17,11 +16,13 @@
    使い方
    * SSP 2.6.33 以上を起動しておく
    * このUserScriptを入れると返信ボタンとかに並んでうかどんアイコンが出てるので、それを押す
    * 「127.0.0.1に接続していいか?本当か?」みたいに言われたら、このドメインを許可とかする(SSPへの接続用)
    * その投稿の内容を SSTP over HTTP で送信して、ゴーストが喋る
    よくある質問
    * SSPはCORS全許可でしょ?なんでGM_XHRがいるの? → CSPでブロックされるので…
    * うかどん以外の一部鯖で使ったら追加のアクセス許可を求められるのはなんで? → 画像を別オリジンに置いてる鯖はそっちも許可がいるので…
    * SSPはCORS全許可でしょ?なんでGM_XHRがいるの? → Mastodon側が持ってるCSPでブロックされるので…
    * うかどん以外の一部鯖で使ったら127.0.0.1以外にアクセス許可を求められるのはなんで? → 画像を別オリジンに置いてる鯖はそっちも許可がいるので…
    * @connect 127.0.0.1 書いとけばよくない? → それだけ書くと鯖ごとの別オリジンが聞かれもしないし、 @connect * は心理的に微妙だったので…
    */

  11. @unarist unarist revised this gist Mar 5, 2023. No changes.
  12. @unarist unarist revised this gist Mar 5, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion mastodon-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    // @match https://ukadon.shillest.net/*
    // @grant GM.xmlHttpRequest
    // @connect 127.0.0.1
    // @downloadURL https://gist.github.com/unarist/_/raw/_.user.js
    // @downloadURL https://gist.github.com/unarist/3134f59953569a4a8ea692185b94eaeb/raw/mastodon-sstp-over-http.user.js
    // @run-at document-idle
    // @noframes
    // ==/UserScript==
  13. @unarist unarist created this gist Mar 5, 2023.
    100 changes: 100 additions & 0 deletions mastodon-sstp-over-http.user.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,100 @@
    // ==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/_/raw/_.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});
    })();