Size
7.0 KB
Version
1.0.1
Created
Dec 23, 2025
Updated
30 days ago
1// ==UserScript==
2// @name Extension for x.com
3// @description A new extension
4// @version 1.0.1
5// @match https://*.x.com/*
6// @icon https://abs.twimg.com/favicons/twitter.3.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 // アフィリエイトIDのパターン(除外対象)
12 const affiliatePatterns = [
13 /af_id/i,
14 /affid/i,
15 /affiliate/i,
16 /voucher/i,
17 /aff_/i,
18 /partner_id/i,
19 /ref_id/i,
20 /tracking/i
21 ];
22
23 // 検索対象のIDパターン
24 const targetIdPatterns = [
25 /[?&]cid=([^&]+)/i,
26 /[?&]pid=([^&]+)/i,
27 /[?&]id=([^&]+)/i,
28 /[?&]product_id=([^&]+)/i,
29 /[?&]item_id=([^&]+)/i,
30 /\/cid[=\/]([^\/&?]+)/i,
31 /\/pid[=\/]([^\/&?]+)/i,
32 /\/id[=\/]([^\/&?]+)/i,
33 ];
34
35 // URLデコード処理
36 function decodeURL(url) {
37 try {
38 return decodeURIComponent(url);
39 } catch (e) {
40 console.log('[ID Extractor] デコードエラー:', e);
41 return url;
42 }
43 }
44
45 // URLパラメーターから実際のURLを抽出
46 function extractRealURL(url) {
47 const urlParams = [/[?&]lurl=([^&]+)/i, /[?&]url=([^&]+)/i, /[?&]link=([^&]+)/i, /[?&]target=([^&]+)/i];
48
49 for (let pattern of urlParams) {
50 const match = url.match(pattern);
51 if (match) {
52 const decoded = decodeURL(match[1]);
53 console.log('[ID Extractor] 実際のURLを抽出:', decoded);
54 return decoded;
55 }
56 }
57 return url;
58 }
59
60 // アフィリエイトIDかチェック
61 function isAffiliateId(paramName) {
62 return affiliatePatterns.some(pattern => pattern.test(paramName));
63 }
64
65 // IDを抽出
66 function extractID(text) {
67 if (!text) return null;
68
69 const decodedText = decodeURL(text);
70 const realURL = extractRealURL(decodedText);
71
72 console.log('[ID Extractor] ID抽出を試行:', realURL.substring(0, 100));
73
74 for (let pattern of targetIdPatterns) {
75 const match = realURL.match(pattern);
76 if (match && match[1]) {
77 const id = match[1];
78 // アフィリエイトIDでないことを確認
79 const fullMatch = match[0];
80 if (!isAffiliateId(fullMatch)) {
81 console.log('[ID Extractor] IDを発見:', id);
82 return id;
83 }
84 }
85 }
86 return null;
87 }
88
89 // リンク要素を処理
90 function processLink(link) {
91 let extractedID = null;
92 const href = link.getAttribute('href');
93
94 console.log('[ID Extractor] リンクを処理中:', href);
95
96 // 1. リンクのテキストコンテンツから抽出(X.comのt.coリンクの場合、実際のURLがテキストに含まれる)
97 if (link.textContent && link.textContent.trim()) {
98 extractedID = extractID(link.textContent);
99 if (extractedID) {
100 console.log('[ID Extractor] テキストコンテンツからIDを抽出:', extractedID);
101 }
102 }
103
104 // 2. href属性から抽出
105 if (!extractedID && href) {
106 extractedID = extractID(href);
107 if (extractedID) {
108 console.log('[ID Extractor] href属性からIDを抽出:', extractedID);
109 }
110 }
111
112 // 3. alt属性から抽出(画像リンクの場合)
113 if (!extractedID) {
114 const img = link.querySelector('img');
115 if (img && img.alt) {
116 extractedID = extractID(img.alt);
117 }
118 }
119
120 // 4. data属性からも検索
121 if (!extractedID) {
122 for (let attr of link.attributes) {
123 if (attr.name.startsWith('data-')) {
124 extractedID = extractID(attr.value);
125 if (extractedID) break;
126 }
127 }
128 }
129
130 // IDが見つかった場合、DuckDuckGo検索リンクに書き換え
131 if (extractedID) {
132 const duckduckgoURL = `https://duckduckgo.com/?q=${encodeURIComponent(extractedID)}`;
133 link.setAttribute('href', duckduckgoURL);
134 link.setAttribute('data-original-href', href);
135 link.setAttribute('data-extracted-id', extractedID);
136 link.style.borderBottom = '2px solid #DE5833'; // 視覚的な識別用
137
138 // リンクテキストも更新(テキストがある場合)
139 if (link.textContent && link.textContent.trim()) {
140 const originalText = link.textContent;
141 link.setAttribute('data-original-text', originalText);
142 link.textContent = `🔍 ${extractedID} (DuckDuckGo検索)`;
143 }
144
145 console.log('[ID Extractor] リンクを書き換えました:', extractedID, '->', duckduckgoURL);
146 return true;
147 }
148
149 return false;
150 }
151
152 // すべてのリンクを処理
153 function processAllLinks() {
154 const links = document.querySelectorAll('a[href]');
155 let processedCount = 0;
156
157 console.log('[ID Extractor] リンクを処理中... 合計:', links.length);
158
159 links.forEach(link => {
160 // 既に処理済みのリンクはスキップ
161 if (link.hasAttribute('data-extracted-id')) {
162 return;
163 }
164
165 if (processLink(link)) {
166 processedCount++;
167 }
168 });
169
170 if (processedCount > 0) {
171 console.log(`[ID Extractor] ${processedCount}個のリンクを書き換えました`);
172 } else {
173 console.log('[ID Extractor] 書き換え対象のリンクが見つかりませんでした');
174 }
175 }
176
177 // デバウンス関数
178 function debounce(func, wait) {
179 let timeout;
180 return function executedFunction(...args) {
181 const later = () => {
182 clearTimeout(timeout);
183 func(...args);
184 };
185 clearTimeout(timeout);
186 timeout = setTimeout(later, wait);
187 };
188 }
189
190 // 初回実行(ページ読み込み後少し待つ)
191 setTimeout(() => {
192 console.log('[ID Extractor] 初回処理を開始');
193 processAllLinks();
194 }, 1000);
195
196 // 動的に追加されるコンテンツを監視(デバウンス付き)
197 const debouncedProcess = debounce(processAllLinks, 500);
198
199 const observer = new MutationObserver((mutations) => {
200 let shouldProcess = false;
201
202 mutations.forEach((mutation) => {
203 if (mutation.addedNodes.length > 0) {
204 shouldProcess = true;
205 }
206 });
207
208 if (shouldProcess) {
209 debouncedProcess();
210 }
211 });
212
213 // 監視開始
214 if (document.body) {
215 observer.observe(document.body, {
216 childList: true,
217 subtree: true
218 });
219 console.log('[ID Extractor] DOM監視を開始しました');
220 }
221
222 console.log('[ID Extractor] スクリプトが起動しました');
223})();