Twitterの画像をScrapboxに引用するスクリプトを作る
(のりしろ)
自分で作ることにした
できたもの
どうやって対応したの?……ってtwitterのwebページからscrapingしたのか

Scrapboxから実行することにこだわらないほうが簡単で便利かも?わからん。
使い方がよくわからなかった

同意

https://scrapbox.io/api/code/takker/GM_fetch/scrapbox.user.js
を
TamperMonkeyにいれて
https://scrapbox.io/api/code/motoso/Tweet%E3%82%92%E5%8F%96%E3%82%8A%E8%BE%BC%E3%82%80Popup_menu_v2/script.js
をUserScriptに追加すればいいのか?
そうです

---
できるまでのログ
Twitterの画像をScrapboxに引用するスクリプトを作る
ブラウザアクセスをすると document.querySelector("#id__a06mgrsipeq > div > div > div > div > div > a > div > div.r-1p0dtai.r-1pi2tsx.r-1d2f490.r-u8s1d.r-ipm5af.r-13qz1uu > div > img")
にある
ただし #id__a06mgrsipeq
はアクセスごとに毎回変わる
Denoだとこんな感じでHTMLから復元できるなぁ
tsimport { load } from "https://esm.sh/cheerio@1.0.0-rc.12";
import puppeteer from "https://deno.land/x/puppeteer@16.2.0/mod.ts";
const ACCOUNT_NAME = "motoso";
const ID = '1637434235534589952';
async function getLatestTweet() {
const browser = await puppeteer.launch();
// TwitterのプロフィールページのURLを構築する
const url = `https://twitter.com/${ACCOUNT_NAME}/status/${ID}`;
const page = await browser.newPage();
// UAを設定しないとサポートされていないブラウザ扱いになる
// https://stackoverflow.com/questions/67010016/twitter-blocked-in-headless-chrome-puppeteer
page.setUserAgent(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"
)
await page.goto(url);
// ツイート表示まで待つ。要素表示まで待つに変更したい
await new Promise(r => setTimeout(r, 2000));
const html = await page.content();
await browser.close();
// Cheerioを使用して、HTMLからツイートのテキストを抽出する
const $ = load(html);
const lines :string[] = [];
const tweetTextElement = $('#react-root > div > div > div.css-1dbjc4n.r-18u37iz.r-13qz1uu.r-417010 > main > div > div > div > div > div > section > div > div > div:nth-child(1) > div > div > article > div > div > div:nth-child(3) > div:nth-child(2)')
const tweetText = tweetTextElement.text()
lines.push(tweetText)
// 最初のidが毎回変わるみたい
const imageElement = $('[id^="id__"] > div > div > div > div > div > a > div > div.r-1p0dtai.r-1pi2tsx.r-1d2f490.r-u8s1d.r-ipm5af.r-13qz1uu > div > img')
const imageURl = imageElement[0].attribs.src
// TODO: 画像をGyazoに送りつける
lines.push(imageURl)
return(lines.join('\n'))
}
console.log(await getLatestTweet());
こういうのをUserScriptから呼び出せるかな?
流石に重そう
API serverを立てて呼び出せば作れる

いい方法はあるだろうか
deno、プロジェクトを作るのを考えずにコードを書いたらすぐ動くのいいな〜
方法は2つ


さんの試したやつ
twitter.comの内部APIを借りて取得する
現状見る限りTwitterのAPIまわりはめちゃくちゃになっていそうなので、真面目に守る必要性をあんまり感じない
ここまでTwitterの公式APIが壊れてるなら、なりふり構ってられない
こんな感じかな

そんな感じです

もう作っているひといたのか
自分だけrobots.txt守っているのもばからしいな(モラルハザード)
もっといろんなAPIを叩いていた覚えがあるのだが……
まあ試せばわかるか
jswindow.getTweet = async (id, lang = "en") => {
const params = new URLSearchParams([
["features", "tfw_timeline_list:linktr.ee,tr.ee,terra.com.br,www.linktr.ee,www.tr.ee,www.terra.com.br;tfw_horizon_timeline_12034:treatment;tfw_tweet_edit_backend:on;tfw_refsrc_session:on;tfw_chin_pills_14741:color_icons;tfw_tweet_result_migration_13979:tweet_result;tfw_sensitive_media_interstitial_13963:interstitial;tfw_experiments_cookie_expiration:1209600;tfw_duplicate_scribes_to_settings:on;tfw_video_hls_dynamic_manifests_15082:true_bitrate;tfw_show_blue_verified_badge:off;tfw_related_videos_15128:many_vids;tfw_tweet_edit_frontend:on"],
["id", id],
// 不要だった
// ["lang", lang],
]);
const res = await GM_fetch(`https://cdn.syndication.twimg.com/tweet-result?${params}`, {
headers: {
"content-type": "application/json; charset=utf-8",
},
});
return await res.json();
};
い け ま し た

展開先URLも含まれている
text
: tweet本文
photos
: 展開先画像URL
entities.urls
: 展開先外部リンク
倫理面もクリア!!!
試した。やりかた

$ await window.getTweet('1637434235534589952')
こんな感じで置換したら良さそう

jsconst lines = [];
const text = tweet.text
// URLの置換
let replacedText = '';
tweet.entities.urls.forEach(i => {
replacedText = text.replace(i.url, i.expanded_url)
})
lines.push(replacedText)
// TODO: Gyazoにアップロードする
lines.push(tweet.photos.map(u => u.url).join('\n'))
lines.join('\n')
とりあえず動くものはできた

TODO
TwitterのURLの場合だけこっちが読み出されるようにしたい
Gyazoアップロード機能
連ツイはどうすれば取得できる?
これはAPIでも取れなかったので
Karel Capekではin_reply_toからやや複雑なSQLで自前で構築してます

当時、内部向けにしかないのかーと思った
2.0なら取れるかも?
oEmbed APIを使うのが良さそう
認証不要、RateLimitなし
APIなのでrobot.txtで不許可にもしていない
画像ではなくHTMLが取れる
画像URLがとれないんですよね

あ、そうか、これを使うときはwidgets.jsが取ってきて埋めてるのかな?

widgets.jsがiframeを作っていた、残念