🔍

X(旧Twitter)で特定の投稿の前後を検索するブックマークレット

2024/05/07に公開

作ったきっかけ

Xの投稿を見ていると引用リポストせずに文脈依存の投稿をしているものを見かけることがあります。また、リプライせずに流れでつぶやいているものなど、前後の文脈が気になることがあります。

そこでブックマークレットで特定の投稿の前後を検索するブックマークレットを作成しました。

ブックマークレット作成の流れ

以下で紹介するJavaScriptはすべて起点とする投稿のページ(例:https://x.com/X/status/1234567890123456789)を開いた状態を前提にしています。
また、動作確認はPCのChromeでのみ行いました。

ブックマークレットは以下の処理を行います。

  1. 特定の投稿のURLを開いていることを確認する
  2. ユーザー名を取得する
  3. 投稿時刻を取得する
  4. 検索URLを組み上げる
  5. 組み上げたURLを新しいタブで開く

上記処理を行うJavaScriptを作成したら、そのコードをClosure Compiler Serviceで圧縮し、ブックマークレットの形に修正したら完成です。

特定の投稿より前のポストを検索するブックマークレット

JavaScriptのコードを作成する

まずは処理をJavaScriptで記述します。

// Get current URL
var currentURL = window.location.href;

// Regular expression pattern to match the username part in the URL
var pattern = /^https:\/\/x\.com\/([^\/]+)\/status\/\d+$/;

// If the current URL is not in the specified format, throw an error and display an alert
if (!pattern.test(currentURL)) {
  alert("This is not the specified format of URL.");
  throw new Error("This is not the specified format of URL.");
}

// Get all time elements within anchor tags
var timeElements = document.querySelectorAll("a > time");

// Initialize datetimeValue
var datetimeValue = null;

// Loop through each time element to find the corresponding href that is included in the current URL
for (var i = 0; i < timeElements.length; i++) {
  var parentAnchor = timeElements[i].parentNode;
  var hrefValue = parentAnchor ? parentAnchor.getAttribute("href") : null;

  if (hrefValue) {
    // Remove '/history' from hrefValue if it exists
    hrefValue = hrefValue.replace(/\/history$/, '');
  }

  // Check if hrefValue is null or currentURL contains hrefValue
  if (hrefValue && currentURL.includes(hrefValue)) {
    datetimeValue = timeElements[i].getAttribute("datetime");
    break;
  }
}

// Convert the time to the format for searching
var formattedDatetime = datetimeValue ? datetimeValue.replace(/T/, '_').replace(/\.000Z$/, '_UTC') : null;

// Match the pattern in the URL to get the username part
var matches = currentURL.match(pattern);

// Get the username from the matched part
var username = matches ? matches[1] : null;

// Create the search URL
var twitterSearchURL = formattedDatetime && username ? "https://x.com/search?q=from%3A" + username + "%20until%3A" + formattedDatetime : null;

if (twitterSearchURL) {
  // Open the URL in a new tab
  window.open(twitterSearchURL, '_blank');
} else {
  alert("Unable to find the time element or href attribute.");
  throw new Error("Unable to find the time element or href attribute.");
}

投稿時刻の取得方法だけ少し工夫が必要だったため解説します。

投稿時刻の取得方法

投稿時刻を取得している部分は以下です。

// Get all time elements within anchor tags
var timeElements = document.querySelectorAll("a > time");

// Initialize datetimeValue
var datetimeValue = null;

// Loop through each time element to find the corresponding href that is included in the current URL
for (var i = 0; i < timeElements.length; i++) {
  var parentAnchor = timeElements[i].parentNode;
  var hrefValue = parentAnchor ? parentAnchor.getAttribute("href") : null;

  if (hrefValue) {
    // Remove '/history' from hrefValue if it exists
    hrefValue = hrefValue.replace(/\/history$/, '');
  }

  // Check if hrefValue is null or currentURL contains hrefValue
  if (hrefValue && currentURL.includes(hrefValue)) {
    datetimeValue = timeElements[i].getAttribute("datetime");
    break;
  }
}

まず特定のDOM要素を取得するのにdocument.querySelectorAll("a > time")を実行しています。
CSSセレクタでDOM要素を取得するメソッドはほかにdocument.querySelector()があります。しかしdocument.querySelector()を使った場合はリプライツリーの途中や引用リポストの場合にうまく動作しないため今回は使いませんでした。

document.querySelectorAll("a > time")を使って候補となるDOM要素を取得し、親要素のhrefプロパティが現在のURLであるものに絞ることで一意の投稿日時を取得できます。

投稿が編集されている場合は親要素のhrefプロパティの末尾に/historyが含まれるため、hrefValue.replace(/\/history$/, '')という処理で削除しています。

Closure Compiler Serviceで圧縮する

上記のコードをClosure Compiler Serviceで圧縮したいのですが、そのままコピー&ペーストして実行してもエラーが出ます。
そこで先頭に(function(){を追加し、末尾に})();を追加します。

エラー回避の修正を行ったコード
(function(){
// Get current URL
var currentURL = window.location.href;

// Regular expression pattern to match the username part in the URL
var pattern = /^https:\/\/x\.com\/([^\/]+)\/status\/\d+$/;

// If the current URL is not in the specified format, throw an error and display an alert
if (!pattern.test(currentURL)) {
  alert("This is not the specified format of URL.");
  throw new Error("This is not the specified format of URL.");
}

// Get all time elements within anchor tags
var timeElements = document.querySelectorAll("a > time");

// Initialize datetimeValue
var datetimeValue = null;

// Loop through each time element to find the corresponding href that is included in the current URL
for (var i = 0; i < timeElements.length; i++) {
  var parentAnchor = timeElements[i].parentNode;
  var hrefValue = parentAnchor ? parentAnchor.getAttribute("href") : null;

  if (hrefValue) {
    // Remove '/history' from hrefValue if it exists
    hrefValue = hrefValue.replace(/\/history$/, '');
  }

  // Check if hrefValue is null or currentURL contains hrefValue
  if (hrefValue && currentURL.includes(hrefValue)) {
    datetimeValue = timeElements[i].getAttribute("datetime");
    break;
  }
}

// Convert the time to the format for searching
var formattedDatetime = datetimeValue ? datetimeValue.replace(/T/, '_').replace(/\.000Z$/, '_UTC') : null;

// Match the pattern in the URL to get the username part
var matches = currentURL.match(pattern);

// Get the username from the matched part
var username = matches ? matches[1] : null;

// Create the search URL
var twitterSearchURL = formattedDatetime && username ? "https://x.com/search?q=from%3A" + username + "%20until%3A" + formattedDatetime : null;

if (twitterSearchURL) {
  // Open the URL in a new tab
  window.open(twitterSearchURL, '_blank');
} else {
  alert("Unable to find the time element or href attribute.");
  throw new Error("Unable to find the time element or href attribute.");
}
})();

これをClosure Compiler Serviceでコンパイルレベルをシンプル(Optimization: Simple)で圧縮すると以下のようになります。

'use strict';(function(){var a=window.location.href,f=/^https:\/\/x\.com\/([^\/]+)\/status\/\d+$/;if(!f.test(a))throw alert("This is not the specified format of URL."),Error("This is not the specified format of URL.");for(var c=document.querySelectorAll("a > time"),e=null,d=0;d<c.length;d++){var b=c[d].parentNode;(b=b?b.getAttribute("href"):null)&&(b=b.replace(/\/history$/,""));if(b&&a.includes(b)){e=c[d].getAttribute("datetime");break}}c=e?e.replace(/T/,"_").replace(/\.000Z$/,"_UTC"):null; a=(a=a.match(f))?a[1]:null;if(a=c&&a?"https://x.com/search?q=from%3A"+a+"%20until%3A"+c:null)window.open(a,"_blank");else throw alert("Unable to find the time element or href attribute."),Error("Unable to find the time element or href attribute.");})();

ブックマークレットの形に修正

ブックマークレットは先頭にjavascript:をつける必要があるため、そこだけ修正します。
以下でブックマークレットは完成です。

javascript:'use strict';(function(){var a=window.location.href,f=/^https:\/\/x\.com\/([^\/]+)\/status\/\d+$/;if(!f.test(a))throw alert("This is not the specified format of URL."),Error("This is not the specified format of URL.");for(var c=document.querySelectorAll("a > time"),e=null,d=0;d<c.length;d++){var b=c[d].parentNode;(b=b?b.getAttribute("href"):null)&&(b=b.replace(/\/history$/,""));if(b&&a.includes(b)){e=c[d].getAttribute("datetime");break}}c=e?e.replace(/T/,"_").replace(/\.000Z$/,"_UTC"):null; a=(a=a.match(f))?a[1]:null;if(a=c&&a?"https://x.com/search?q=from%3A"+a+"%20until%3A"+c:null)window.open(a,"_blank");else throw alert("Unable to find the time element or href attribute."),Error("Unable to find the time element or href attribute.");})();

特定の投稿以後のポストを検索するブックマークレット

Xの検索はuntilで特定日時より前、sinceで特定日時から後を指定します。そのためブックマークレットはほぼ同じで、違いはuntilsinceの部分だけです。
そのため途中は省略し、完成形のブックマークレットのみ以下に示します。

javascript:'use strict';(function(){var a=window.location.href,f=/^https:\/\/x\.com\/([^\/]+)\/status\/\d+$/;if(!f.test(a))throw alert("This is not the specified format of URL."),Error("This is not the specified format of URL.");for(var c=document.querySelectorAll("a > time"),e=null,d=0;d<c.length;d++){var b=c[d].parentNode;(b=b?b.getAttribute("href"):null)&&(b=b.replace(/\/history$/,""));if(b&&a.includes(b)){e=c[d].getAttribute("datetime");break}}c=e?e.replace(/T/,"_").replace(/\.000Z$/,"_UTC"):null; a=(a=a.match(f))?a[1]:null;if(a=c&&a?"https://x.com/search?q=from%3A"+a+"%20since%3A"+c:null)window.open(a,"_blank");else throw alert("Unable to find the time element or href attribute."),Error("Unable to find the time element or href attribute.");})();

ブックマークレットの登録方法

ブックマークレットはブックマークと同じようにブラウザに登録して使います。最もシンプルな方法は以下の手順です。

  1. 適当なページ(このページなど)をブックマークに登録する
  2. 登録したブックマークを編集する
    1. ブックマークレットの名称に変更する
    2. URLを作成したブックマークレットの文字列に変更する

これでブックマークレットの登録が完了です。
使いたいページでブックマークを開くようにブックマークレットを開けば動作します。

補足

ちなみにuntilの検索結果は起点となる投稿を含みませんが、sinceの検索結果は起点となる投稿を含みます。

Discussion