Twitterを少し便利に。

私の作ったスクリプトをまとめたもの。と追加要素。

目前為 2023-10-26 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Twitterを少し便利に。
  3. // @name:ja Twitterを少し便利に。
  4. // @name:en Make Twitter little useful.
  5. // @namespace https://gf.qytechs.cn/ja/users/1023652
  6. // @version 1.0.0.4
  7. // @description 私の作ったスクリプトをまとめたもの。と追加要素。
  8. // @description:ja 私の作ったスクリプトをまとめたもの。と追加要素。
  9. // @description:en A compilation of scripts I've made.
  10. // @author ゆにてぃー
  11. // @match https://twitter.com/*
  12. // @match https://mobile.twitter.com/*
  13. // @match https://x.com/*
  14. // @match https://X.com/*
  15. // @connect twitter.com
  16. // @connect api.twitter.com
  17. // @connect api.fanbox.cc
  18. // @connect pbs.twimg.com
  19. // @connect abs.twimg.com
  20. // @connect video.twimg.com
  21. // @connect discord.com
  22. // @connect booth.pm
  23. // @connect carrd.co
  24. // @connect creatorlink.net
  25. // @connect fantia.jp
  26. // @connect html.co.jp
  27. // @connect linktr.ee
  28. // @connect lit.link
  29. // @connect potofu.me
  30. // @connect profcard.info
  31. // @connect skeb.jp
  32. // @connect sketch.pixiv.net
  33. // @connect tumblr.com
  34. // @connect twpf.jp
  35. // @connect lab.syncer.jp
  36. // @connect geek-website.com
  37. // @icon data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJkSURBVHgB7VZBbtpQEH3zIW0WVYuXVaH4Bs0NSk4AOUFhEarskhMknIDsqkKlcIT0BNAT1D1B3ZJK3dmVuirwp/MhVmzAxiagKBJv9+ePZ97M/JkxsMMODwzChlD84FWQp3MxeCDHAhiumB+MJrr1+8Ryw3p/9+H4DctfIPCq49Xlw8Kv99YlMuB19885gy/i7llziwGfFFWJyR02XzSCuwiBUse7BlFVaz5LS8KQVkRXaXRJsqImfDjKSZBNyzEyFWFKVJ4KFbWLElUao6KbSk8i9TXgTPaorxTskPwOxa7/9baGt4zg8oQbNyfWYJlRU0/KUx9ZwNwYNq1ecFRzl18QpW0bB0Ks//KjV1uwlbuLJA3GxEdh5wb5yGEPl3qMd2xecYQHKnlFlVLX95kxYCFKGg5IlU2a0uLpCM68LEJA+sJ/Dm6Jy3aMjQIRakRUm+UuvfOp/X34iQSejeFo0Hdx4optG5uFH/R+GHNvANcm3VtwLs+Lvy2TRwhIOnrYHhysIuDKcCDwGbYAjglOzQt+HssElF6dvoNNOZeuCSbfSgIGMjILMo4/ExZf7TqghNLmlwm1gpSC2tmaLAZMvWGz0Iu7XpqBm2NrQNN5cD+Y5ZOTdZyok3RZMusZOJUN+QZrQFb0oQkG6xIIYHe8A03Unx/Ryd6jS2ctAsbxmFRVynGKlM5na5ePVkUe0p+h9MmraS2zXqYgmSWjOPtElHbLTVB3Q79gqQlMScxqXpeav0UWiGMmXKSNOpZAAPvKs/U/1MRoxRxl+5WD+psUy2D5IdmRVoWjnqDnLlkyO+zwaPAf1zXwZL751PUAAAAASUVORK5CYII=
  38. // @grant GM_xmlhttpRequest
  39. // @grant GM_registerMenuCommand
  40. // @license MIT
  41. // @run-at document-idle
  42. // ==/UserScript==
  43.  
  44. (function(){
  45. 'use strict';
  46. const commonselectors = {
  47. 'tweet_field': 'article[data-testid="tweet"]',
  48. 'retweeted': '[data-testid="socialContext"]',
  49. 'liked_color': 'r-vkub15',
  50. 'liked': 'M20.884 13.19c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z',
  51. 'info_field': '.css-1dbjc4n.r-1d09ksm.r-1471scf.r-18u37iz.r-1wbh5a2',
  52. 'click_media_field': '.css-1dbjc4n.r-1p0dtai.r-1mlwlqe.r-1d2f490.r-dnmrzs.r-1udh08x.r-u8s1d.r-zchlnj.r-ipm5af.r-417010',
  53. 'profile_field_Header_Items': '[data-testid="UserProfileHeader_Items"]',
  54. 'engagementsTextColor': {
  55. "0": {"count": "rgb(15, 20, 25)","text": "rgb(83, 100, 113)"},
  56. "1": {"count": "rgb(247, 249, 249)","text": "rgb(139, 152, 165)"},
  57. "2": {"count": "rgb(231, 233, 234)","text": "rgb(113, 118, 123)"}
  58. },
  59. }
  60. const desktop_selectors = {
  61. 'time_line_media_field': '.css-1dbjc4n.r-1p0dtai.r-1mlwlqe.r-1d2f490.r-11wrixw.r-61z16t.r-1udh08x.r-u8s1d.r-zchlnj.r-ipm5af.r-417010',
  62. 'media_field': '.css-1dbjc4n.r-1ssbvtb.r-1s2bzr4',
  63. 'profileField': '.css-1dbjc4n.r-1ifxtd0.r-ymttw5.r-ttdzmv',
  64. 'followersLink': '.css-4rbku5.css-18t94o4.css-901oao.r-vlxjld.r-1loqt21.r-1tl8opc.r-a023e6.r-16dba41.r-rjixqe.r-bcqeeo.r-qvutc0',
  65. };
  66. const mobile_selectors = {
  67. 'time_line_media_field': '.css-1dbjc4n.r-1p0dtai.r-1mlwlqe.r-1d2f490.r-1udh08x.r-u8s1d.r-zchlnj.r-ipm5af.r-417010',
  68. 'media_field': '.css-1dbjc4n.r-1s367qj.r-a1ub67',
  69. 'profileField': '.css-1dbjc4n.r-ku1wi2.r-1j3t67a.r-1b3ntt7',
  70. 'followersLink': '.css-4rbku5.css-18t94o4.css-901oao.r-vlxjld.r-1loqt21.r-1tl8opc.r-1b43r93.r-16dba41.r-hjklzo.r-bcqeeo.r-qvutc0',
  71. };
  72. const deny_names = ["home", "explore", "notifications", "messages", "i", "settings", "tos", "privacy", "compose", "search"];
  73. const denyNamesRegex = new RegExp(`https://twitter\\.com/((?!${deny_names.join('|')})[^/]+)`, 'i');
  74. let currentUrl = document.location.href;
  75. let updating = false;
  76. const userAgent = navigator.userAgent || navigator.vendor || window.opera;
  77. const timeZoneObject = Intl.DateTimeFormat().resolvedOptions();
  78. let env_selector;
  79. let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
  80. if(isMobile){
  81. env_selector = {...commonselectors,...mobile_selectors};
  82. }else{
  83. env_selector = {...commonselectors,...desktop_selectors};
  84. }
  85. let fetchedTweets = {};
  86. let fetchedTweetsUserData = {};
  87. let fetchedTweetsUserDataByUserName = {};
  88. let isFunctionRunning = {
  89. Hello_tweet_where_are_you_from: false,
  90. Engagement_Restorer: false,
  91. };
  92. let storedSettings = {
  93. 'Make_Twitter_little_useful': JSON.parse(localStorage.getItem('Make_Twitter_little_useful') || '{}'),
  94. 'webhook_brings_tweets_to_discord': JSON.parse(localStorage.getItem('webhook_brings_tweets_to_discord') || '{}'),
  95. 'Hello_tweet_where_are_you_from': JSON.parse(localStorage.getItem('Hello_tweet_where_are_you_from') || '{}'),
  96. 'Show_me_your_Pixiv': JSON.parse(localStorage.getItem('Show_me_your_Pixiv') || '{}'),
  97. 'Note_Tweet_expander': JSON.parse(localStorage.getItem('Note_Tweet_expander') || '{}'),
  98. 'sneakilyFavorite': JSON.parse(localStorage.getItem('sneakilyFavorite') || '{}'),
  99. 'Engagement_Restorer': JSON.parse(localStorage.getItem('Engagement_Restorer') || '{}'),
  100. };
  101. let script_settings = {};
  102. script_settings['webhook_brings_tweets_to_discord'] = {
  103. "displayMethod": storedSettings['webhook_brings_tweets_to_discord'].displayMethod || 'method1',
  104. "lang": storedSettings['webhook_brings_tweets_to_discord'].lang || GetCookie("lang") || 'en',
  105. "defaultWebhook": storedSettings['webhook_brings_tweets_to_discord'].defaultWebhook,
  106. "downloadVideo": storedSettings['webhook_brings_tweets_to_discord'].downloadVideo || false,
  107. "webHooks": (function(data){
  108. let webhooks = {};
  109. if(data && Array.isArray(data)){
  110. data.forEach(item => {
  111. if(item.name && item.value){
  112. webhooks[item.name] = item.value;
  113. }
  114. });
  115. }
  116. return webhooks;
  117. })(storedSettings['webhook_brings_tweets_to_discord'].data),
  118. };
  119. script_settings['Make_Twitter_little_useful'] = {
  120. "featuresToggle": {
  121. "webhook_brings_tweets_to_discord": storedSettings.Make_Twitter_little_useful?.featuresToggle?.webhook_brings_tweets_to_discord || false,
  122. "Engagement_Restorer": storedSettings.Make_Twitter_little_useful?.featuresToggle?.Engagement_Restorer || false,
  123. "sneakilyFavorite": storedSettings.Make_Twitter_little_useful?.featuresToggle?.sneakilyFavorite || false,
  124. "Hello_tweet_where_are_you_from": storedSettings.Make_Twitter_little_useful?.featuresToggle?.Hello_tweet_where_are_you_from || false,
  125. "Note_Tweet_expander": storedSettings.Make_Twitter_little_useful?.featuresToggle?.Note_Tweet_expander || false,
  126. "Show_me_your_Pixiv": storedSettings.Make_Twitter_little_useful?.featuresToggle?.Show_me_your_Pixiv || false,
  127. "showFollowers": storedSettings.Make_Twitter_little_useful?.featuresToggle?.showFollowers || false,
  128. "hideAnalytics": storedSettings.Make_Twitter_little_useful?.featuresToggle?.hideAnalytics || false,
  129. "shareTweet_Restorer_for_mobile": storedSettings.Make_Twitter_little_useful?.featuresToggle?.shareTweet_Restorer_for_mobile || false,
  130. },
  131. "lang": storedSettings.Make_Twitter_little_useful?.featuresToggle?.lang || GetCookie("lang") || "en",
  132. };
  133. let scriptDataStore = {
  134. "Show_me_your_Pixiv": JSON.parse(localStorage.getItem('user_pixvi_link_collection') || {}),
  135. };
  136. let Text = {};
  137. Text.ja = {
  138. "settings": "の設定",
  139. "close": "閉じる",
  140. "general": "全体",
  141. "featureToggle": "使う機能の切り替え",
  142. "style": {
  143. "functionName": "スタイル",
  144. },
  145. "Advanced": {
  146. "functionName": "高度な設定",
  147. "invalidJson": "不正なJSONです。",
  148. "ExportSettings": "設定の書き出し",
  149. "ImportSettings": "設定の読み込み",
  150. "Export": "エクスポート",
  151. "Inport": "インポート",
  152. },
  153. "webhook_brings_tweets_to_discord": {
  154. "functionName": "WebhookがTweetを連れてくるわ今日も",
  155. "link_to_tweet": "ツイートへ",
  156. "link_to_image": "画像へのリンク",
  157. "engagement": "エンゲージメント",
  158. "likes": "いいね",
  159. "retweets": "リツイート",
  160. "units": "万",
  161. "roundingScale": 10000,
  162. "decimalPlaces": 2,
  163. "postedDate": "投稿日時",
  164. "quotedTweet": "↓♻️引用元♻️↓",
  165. "submit": "送信",
  166. "display_everywhere": "どこでも表示する",
  167. "tweet_details_only": "詳細表示したときだけ",
  168. "when_webhook_name_duplicate": "Webhookの名前が重複しています。",
  169. "cancel": "キャンセル",
  170. "save_settings": "設定を保存",
  171. "display_method": "表示方法",
  172. "default": "デフォルトの",
  173. "language": "送信時言語",
  174. "webhook_not_set": "ウェブフックが設定されていません。",
  175. "when_webhook_url_invalid": "正しいDiscordのWebhookのURLではありません。",
  176. "when_post_failed": "以下のURLのポストに失敗しました。",
  177. "various_links": "各種リンク",
  178. "Video_Download_Option": "動画をダウンロードしてファイルとして送信する",
  179. },
  180. "Engagement_Restorer": {
  181. "functionName": "返ってこい!リツイート欄!",
  182. "retweet": "リツイート",
  183. "quoted": "件の引用",
  184. "like": "いいね",
  185. "units": "万",
  186. "roundingScale": 10000,
  187. "decimalPlaces": 2,
  188. },
  189. "sneakilyFavorite": {
  190. "functionName": "こっそりいいね",
  191. "favorite": "いいね!",
  192. },
  193. "Hello_tweet_where_are_you_from": {
  194. "functionName": "あなたのツイートはどこから?",
  195. },
  196. "Note_Tweet_expander": {
  197. "functionName": "長いツイートをTLで展開",
  198. },
  199. "Show_me_your_Pixiv": {
  200. "functionName": "PixivのリンクをTweetに添えて",
  201. },
  202. "showFollowers": {
  203. "functionName": "フォロワーを直接表示",
  204. },
  205. "hideAnalytics": {
  206. "functionName": "アナリティクスを非表示にする",
  207. },
  208. "shareTweet_Restorer_for_mobile": {
  209. "functionName": "ツイートを共有ボタンを復活させる(モバイル用)",
  210. },
  211. };
  212.  
  213. Text.en = {
  214. "settings": "Settings",
  215. "close": "Close",
  216. "general": "General",
  217. "featureToggle": "Feature Toggle",
  218. "style": {
  219. "functionName": "style",
  220. },
  221. "Advanced": {
  222. "functionName": "Advanced",
  223. "invalidJson": "Invalid JSON",
  224. "ExportSettings": "Export Settings",
  225. "ImportSettings": "Import Settings",
  226. "Export": "Export",
  227. "Inport": "Inport",
  228. },
  229. "webhook_brings_tweets_to_discord": {
  230. "functionName": "webhook brings tweets to discord",
  231. "link_to_tweet": "To Tweet",
  232. "link_to_image": "Link to Image",
  233. "engagement": "Engagement",
  234. "likes": "Likes",
  235. "retweets": "Retweets",
  236. "units": "k",
  237. "roundingScale": 1000,
  238. "decimalPlaces": 1,
  239. "postedDate": "Posted Date",
  240. "quotedTweet": "↓♻️Quoted Tweet♻️↓",
  241. "submit": "submit",
  242. "display_everywhere": "Display everywhere",
  243. "tweet_details_only": "Tweet Details Only",
  244. "when_webhook_name_duplicate": "Duplicate webhook name.",
  245. "cancel": "cancel",
  246. "save_settings": "saveSettings",
  247. "display_method": "Display Method",
  248. "default": "default",
  249. "language": "use language when sending",
  250. "webhook_not_set": "Webhook is not set.",
  251. "when_webhook_url_invalid": "It is not a valid Discord Webhook URL.",
  252. "when_post_failed": "Failed to post the following URL.",
  253. "various_links": "Various Links",
  254. "Video_Download_Option": "Download videos and send them.",
  255. },
  256. "Engagement_Restorer": {
  257. "functionName": "Engagement Restorer",
  258. "retweet": "Retweets",
  259. "quoted": "Quotes",
  260. "like": "Likes",
  261. "units": "k",
  262. "roundingScale": 1000,
  263. "decimalPlaces": 1,
  264. },
  265. "sneakilyFavorite": {
  266. "functionName": "sneakilyFavorite",
  267. "favorite": "favorite!",
  268. },
  269. "Hello_tweet_where_are_you_from": {
  270. "functionName": "Hello tweet where are you from?",
  271. },
  272. "Note_Tweet_expander": {
  273. "functionName": "Note Tweet expander",
  274. },
  275. "Show_me_your_Pixiv": {
  276. "functionName": "Show me your Pixiv",
  277. },
  278. "showFollowers": {
  279. "functionName": "showFollowers",
  280. },
  281. "hideAnalytics": {
  282. "functionName": "hideAnalytics",
  283. },
  284. "shareTweet_Restorer_for_mobile": {
  285. "functionName": "shareTweet Restorer(for mobile)",
  286. },
  287. };
  288.  
  289. let env_Text = Text[script_settings.lang] || Text.ja;
  290.  
  291. locationChange();
  292.  
  293. async function main(refresh = false){
  294. const selector = refresh ? 'article[data-testid="tweet"]' : 'article[data-testid="tweet"]:not(.checked-tweet)';
  295. const tweetNodes = Array.from(document.querySelectorAll(selector)).map(tweet => {
  296. const link = tweet.querySelector(`[data-testid="User-Name"] a[aria-label], ${env_selector.info_field} a[aria-label]`);
  297. if(link){
  298. const match = link.href.match(/twitter\.com\/[^/]+\/status\/(\d+)/);
  299. if(match){
  300. tweet.classList.add('checked-tweet');
  301. return { id: match[1], link: link.href, node: tweet };
  302. }
  303. }
  304. }).filter(Boolean);
  305. //console.log(tweetNodes)
  306. const featurestoggle = script_settings.Make_Twitter_little_useful.featuresToggle;
  307. const isTweetDetail = !!currentUrl.match(/twitter\.com\/[\w]*\/status\/[0-9]*/);
  308. if(isTweetDetail && featurestoggle.Hello_tweet_where_are_you_from){
  309. Hello_tweet_where_are_you_from();
  310. }
  311. if(tweetNodes && featurestoggle.Note_Tweet_expander){
  312. Note_Tweet_expander(tweetNodes);
  313. }
  314. if(tweetNodes && featurestoggle.Show_me_your_Pixiv){
  315. Show_me_your_Pixiv(tweetNodes);
  316. }
  317. if(isTweetDetail && featurestoggle.Engagement_Restorer){
  318. Engagement_Restorer();
  319. }
  320. if(tweetNodes && featurestoggle.sneakilyFavorite){
  321. sneakilyFavorite(tweetNodes);
  322. }
  323. if(tweetNodes && featurestoggle.webhook_brings_tweets_to_discord){
  324. webhook_brings_tweets_to_discord(tweetNodes);
  325. }
  326. if(featurestoggle.showFollowers){
  327. showFollowers();
  328. }
  329. if(tweetNodes && featurestoggle.hideAnalytics){
  330. hideAnalytics(tweetNodes);
  331. }
  332. if(isMobile && featurestoggle.shareTweet_Restorer_for_mobile){
  333. shareTweet_Restorer_for_mobile(tweetNodes);
  334. }
  335. //if(currentUrl.match(/https?:\/\/twitter\.com\/[\w]*\/status\/[0-9]*/))console.log(await waitForTweetData(extractTweetId(currentUrl)))
  336. }
  337. function shareTweet_Restorer_for_mobile(tweetNodes){
  338. tweetNodes.forEach(async (tweet)=>{
  339. let color = ['rgb(83, 100, 113)','rgb(139, 152, 165)','rgb(113, 118, 123)'];
  340. let footer = tweet.node.querySelector('div[id][role="group"]');
  341. let lastChild = footer.lastElementChild;
  342. let clonedNode = lastChild.cloneNode(true);
  343. clonedNode.style.marginLeft = "1em";
  344. let clonedSvg = clonedNode.querySelector('svg');
  345. while(clonedSvg.firstChild){
  346. clonedSvg.removeChild(clonedSvg.firstChild);
  347. }
  348. let newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  349. newPath.setAttribute('d', 'M17 4c-1.1 0-2 .9-2 2 0 .33.08.65.22.92C15.56 7.56 16.23 8 17 8c1.1 0 2-.9 2-2s-.9-2-2-2zm-4 2c0-2.21 1.79-4 4-4s4 1.79 4 4-1.79 4-4 4c-1.17 0-2.22-.5-2.95-1.3l-4.16 2.37c.07.3.11.61.11.93s-.04.63-.11.93l4.16 2.37c.73-.8 1.78-1.3 2.95-1.3 2.21 0 4 1.79 4 4s-1.79 4-4 4-4-1.79-4-4c0-.32.04-.63.11-.93L8.95 14.7C8.22 15.5 7.17 16 6 16c-2.21 0-4-1.79-4-4s1.79-4 4-4c1.17 0 2.22.5 2.95 1.3l4.16-2.37c-.07-.3-.11-.61-.11-.93zm-7 4c-1.1 0-2 .9-2 2s.9 2 2 2c.77 0 1.44-.44 1.78-1.08.14-.27.22-.59.22-.92s-.08-.65-.22-.92C7.44 10.44 6.77 10 6 10zm11 6c-.77 0-1.44.44-1.78 1.08-.14.27-.22.59-.22.92 0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2z');
  350. clonedSvg.appendChild(newPath);
  351. clonedSvg.addEventListener('click', () => {
  352. copyToClipboard(tweet.link);
  353. });
  354. clonedNode.addEventListener('click', (event) => {
  355. event.stopPropagation();
  356. });
  357. footer.appendChild(clonedNode);
  358. });
  359. function copyToClipboard(text){
  360. navigator.clipboard.writeText(text).then(function() {
  361. //console.log('クリップボードにコピーしました!');
  362. }).catch(function(err) {
  363. console.error('コピーに失敗しました:', err);
  364. });
  365. }
  366. }
  367. async function showFollowers(){
  368. try{
  369. const screenName = extractUserName(currentUrl);
  370. if(!currentUrl.match(new RegExp(`${screenName}$`)))return;
  371. (await wait_load_Element(`${env_selector.profileField} ${env_selector.followersLink}`,500,5)).forEach(e=>{
  372. if(e.href.match(/verified_followers$/)){
  373. e.href = e.href.replace(/verified_followers$/,'followers');
  374. e.addEventListener('click',async (event) => {
  375. (await wait_load_Element('a[role="tab"]')).forEach(t=>{
  376. if(t.href.match(/followers$/))t.click();
  377. });
  378. });
  379. }
  380. });
  381. }catch(error){console.error(error)}
  382. }
  383. function hideAnalytics(tweetNodes){
  384. try{
  385. tweetNodes.forEach(t=>{
  386. if(t.id === extractTweetId(currentUrl))return;
  387. let analytics = t.node.querySelector('div[id][role="group"] a[role="link"]').parentNode || t.node.querySelector('[d="M8.75 21V3h2v18h-2zM18 21V8.5h2V21h-2zM4 21l.004-10h2L6 21H4zm9.248 0v-7h2v7h-2z"]').findParent('div.css-1dbjc4n.r-13awgt0.r-18u37iz.r-1h0z5md');
  388. analytics.style.display = "none";
  389. });
  390. }catch(error){console.error(error)}
  391. }
  392. async function webhook_brings_tweets_to_discord(tweetNodes){
  393. let textData = env_Text.webhook_brings_tweets_to_discord;
  394. let thisScriptSettings = script_settings['webhook_brings_tweets_to_discord'];
  395. tweetNodes.forEach(function(tweetNode){
  396. let element = tweetNode.node;
  397. if(element.querySelector(".quickDimg")) return;
  398. let tweet_link = tweetNode.link;
  399. let fotter = element.querySelector('div[id][role="group"]');
  400. const flexContainer = document.createElement('div');
  401. flexContainer.classList.add('quickDimg');
  402. flexContainer.style.display = 'flex';
  403.  
  404. // 1つ目のドロップダウン(サーバー選択)
  405. const dropdown_select_server = document.createElement('select');
  406. dropdown_select_server.className = "quickDimgPullDown quickDimgPullDown1";
  407. for(const server in thisScriptSettings.webHooks){
  408. const option = document.createElement('option');
  409. option.value = thisScriptSettings.webHooks[server];
  410. option.textContent = server;
  411. if(server == thisScriptSettings.defaultWebhook){
  412. option.selected = true;
  413. }
  414. dropdown_select_server.appendChild(option);
  415. }
  416. flexContainer.appendChild(dropdown_select_server);
  417. dropdown_select_server.addEventListener('click', (event) => {
  418. event.stopPropagation();
  419. });
  420.  
  421. const dropdown_send_image = document.createElement('select');
  422. dropdown_send_image.className = "quickDimgPullDown quickDimgPullDown2";
  423. for(let i = 1; i <= 5; i++){
  424. const option = document.createElement('option');
  425. option.value = i;
  426. option.textContent = i;
  427. if(i === 5){
  428. option.selected = true;
  429. }
  430. dropdown_send_image.appendChild(option);
  431. }
  432. flexContainer.appendChild(dropdown_send_image);
  433.  
  434. dropdown_send_image.addEventListener('click', (event) => {
  435. event.stopPropagation();
  436. });
  437. const dropdown_post_quote = document.createElement('select');
  438. dropdown_post_quote.className = "quickDimgPullDown quickDimgPullDown3";
  439. ['false','true'].forEach(value => {
  440. const option = document.createElement('option');
  441. option.value = value;
  442. option.textContent = value;
  443. dropdown_post_quote.appendChild(option);
  444. });
  445. flexContainer.appendChild(dropdown_post_quote);
  446.  
  447. dropdown_post_quote.addEventListener('click', (event) => {
  448. event.stopPropagation();
  449. });
  450.  
  451. const dropdown_use_graphql = document.createElement('select');
  452. dropdown_use_graphql.className = "quickDimgPullDown quickDimgPullDown4";
  453. ['false','true'].forEach(value => {
  454. const option = document.createElement('option');
  455. option.value = value;
  456. option.textContent = value;
  457. dropdown_use_graphql.appendChild(option);
  458. });
  459. flexContainer.appendChild(dropdown_use_graphql);
  460.  
  461. dropdown_use_graphql.addEventListener('click', (event) => {
  462. event.stopPropagation();
  463. });
  464. // ボタンを作成
  465. const button = document.createElement('button');
  466. button.className = "quickDimgButton";
  467. button.textContent = textData.submit;
  468. flexContainer.appendChild(button);
  469.  
  470. dropdown_select_server.addEventListener('change', () => {
  471. button.disabled = false;
  472. });
  473. dropdown_send_image.addEventListener('change', () => {
  474. button.disabled = false;
  475. });
  476. dropdown_post_quote.addEventListener('change', () => {
  477. button.disabled = false;
  478. });
  479. dropdown_use_graphql.addEventListener('change', () => {
  480. button.disabled = false;
  481. });
  482.  
  483. // ボタンのクリックイベントを監視
  484. button.addEventListener('click',async function(){
  485. // ここでドロップダウンの選択値に基づいて処理を行う
  486. this.disabled = true;
  487. const selectedServer = dropdown_select_server.value;
  488. const selectedNumber = dropdown_send_image.value;
  489. const send_post_tweet = dropdown_post_quote.value === 'true';
  490. const useGraphql = dropdown_use_graphql.value === 'true';
  491. if(!selectedServer){
  492. customAlert(textData.webhook_not_set);
  493. return;
  494. }
  495. let send_page;
  496. if(selectedNumber != 5){
  497. send_page = [selectedNumber-1]
  498. }else{
  499. send_page = [0,1,2,3]
  500. }
  501. const body = await make_send_data(tweet_link,send_page,send_post_tweet,useGraphql);
  502. await sleep(300)
  503. for(let target in body){
  504. let formData = new FormData();
  505. let payload = {};
  506. let tmp = body[target];
  507. if(tmp.embeds){
  508. payload.embeds = tmp.embeds;
  509. }
  510. if(tmp.content){
  511. payload.content = tmp.content;
  512. }
  513. formData.append('payload_json', JSON.stringify(payload));
  514. //console.log(formData)
  515. if(tmp.files){
  516. tmp.files.forEach((file, index) => {
  517. formData.append(`file${index}`, file.attachment, file.name);
  518. });
  519. }
  520. try{
  521. let res = await request(new sendObject_to_discord_webhook(selectedServer,formData));
  522. if(res.statusText == "Bad Request"){
  523. customAlert(`${textData.when_post_failed}`,payload.embeds[0].url);
  524. }
  525. }catch(error){
  526. customAlert(`${textData.when_post_failed}`,payload.embeds[0].url);
  527. console.log(error);
  528. }
  529. //console.log(res)
  530. await sleep(1000)
  531. }
  532. });
  533. fotter.parentNode.appendChild(flexContainer);
  534. });
  535. async function make_send_data(tweet_link,select_pages = [1],send_quoted_tweet,use_graphQL){
  536. const tweet_id = tweet_link.match(/https?:\/\/twitter\.com\/\w+\/status\/(\d+)/)[1];
  537. let tweet_data,return_object,apiType;
  538. try{
  539. if(use_graphQL){
  540. apiType = "graphQL";
  541. }else{
  542. apiType = "1_1"
  543. }
  544. tweet_data = await getTweetData(tweet_id,apiType);
  545. return_object = await make_embeds();
  546. }catch(error){
  547. customAlert(`${textData.when_post_failed}`,tweet_link);
  548. console.error(error);
  549. }
  550. if(send_quoted_tweet && return_object[return_object.length - 1].quoted_tweet_data){
  551. tweet_data = return_object.pop().quoted_tweet_data;
  552. return_object = return_object.concat([{content: textData.quotedTweet}],await make_embeds(1));
  553. }
  554. return return_object;
  555. async function make_embeds(quoted_tweet_mode = 0){
  556. let embeds = [];
  557. let tmpEmbed = {};
  558. let tmp_return_object = [];
  559. let twitter_user_data = {};
  560. let twitter_tweet_data = {};
  561. let tweet_user_data_json = {};
  562. let tweet_tweet_data_json = {};
  563. tweet_user_data_json = tweet_data.core?.user_results?.result || tweet_data.user;
  564. tweet_tweet_data_json = tweet_data.legacy || tweet_data;
  565. twitter_user_data.ID = tweet_user_data_json.rest_id || tweet_user_data_json.id_str;
  566. twitter_user_data.screen_name = tweet_user_data_json.legacy?.screen_name || tweet_user_data_json.screen_name;
  567. twitter_user_data.name = tweet_user_data_json.legacy?.name || tweet_user_data_json.name;
  568. twitter_user_data.profile_image = tweet_user_data_json.legacy?.profile_image_url_https.replace('_normal.','.') || tweet_user_data_json.profile_image_url_https.replace('_normal.','.');
  569. twitter_user_data.urls = tweet_user_data_json.legacy?.entities || tweet_user_data_json.entities || [];
  570. twitter_tweet_data.hashtags = tweet_tweet_data_json.entities.hashtags || [];
  571. twitter_tweet_data.user_mentions = tweet_tweet_data_json.entities.user_mentions || [];
  572. twitter_tweet_data.symbols = tweet_tweet_data_json.entities.symbols || [];
  573. try{
  574. twitter_user_data.pixiv_url = await find_pixiv_link(extractUrls(twitter_user_data.urls));
  575. }catch(error){
  576. console.log("pixivのURLの取得に失敗しました。");
  577. throw(error);
  578. }
  579. twitter_tweet_data.full_text = tweet_tweet_data_json.full_text || "";
  580. twitter_tweet_data.extended_entities = tweet_tweet_data_json.extended_entities;
  581. twitter_tweet_data.retweet_count = tweet_tweet_data_json.retweet_count;
  582. twitter_tweet_data.favorite_count = tweet_tweet_data_json.favorite_count;
  583. twitter_tweet_data.id = tweet_tweet_data_json.id_str;
  584. twitter_tweet_data.created_at = new Date(tweet_tweet_data_json.created_at).toLocaleString(timeZoneObject.locale, { timeZone: timeZoneObject.timeZone });
  585. twitter_tweet_data.urls = tweet_tweet_data_json.entities.urls;
  586. twitter_tweet_data.media = make_media_list(twitter_tweet_data.extended_entities,select_pages);
  587. try{
  588. //文が長すぎるとエラーになるので一定の長さで切る。
  589. //普通のツイートではそんなことありえないが、Blueでは長いツイートが可能なのでそれに対応している。
  590. if(tweet_data.API_type == "graphql"){
  591. let note_tweet = tweet_data.result.note_tweet?.note_tweet_results.result||tweet_data.result.tweet.note_tweet.note_tweet_results.result;
  592. twitter_tweet_data.full_text = note_tweet.text;
  593. twitter_tweet_data.urls = note_tweet.entity_set.urls;
  594. twitter_tweet_data.hashtags = get_only_particular_key_value(note_tweet.entity_set,"hashtags",[]);
  595. twitter_tweet_data.user_mentions = get_only_particular_key_value(note_tweet.entity_set,"user_mentions",[]);
  596. twitter_tweet_data.symbols = get_only_particular_key_value(note_tweet.entity_set,"symbols",[]);
  597. }
  598. }catch{}
  599. //console.log(twitter_tweet_data.full_text)
  600. // hashtags, mentions, symbolsを一つの配列に結合
  601. let combined = [].concat(
  602. twitter_tweet_data.hashtags.map(tag => ({
  603. type: 'hashtag',
  604. indices: tag.indices,
  605. text: tag.text
  606. })),
  607. twitter_tweet_data.user_mentions.map(mention => ({
  608. type: 'mention',
  609. indices: mention.indices,
  610. text: mention.screen_name
  611. })),
  612. twitter_tweet_data.symbols.map(symbol => ({
  613. type: 'symbol',
  614. indices: symbol.indices,
  615. text: symbol.text
  616. }))
  617. );
  618. // combinedをindicesの順にソート
  619. combined.sort((a, b) => b.indices[0] - a.indices[0]);
  620. let transformedText = twitter_tweet_data.full_text;
  621. function countSurrogatePairs(str){
  622. return Array.from(str).filter(char => char.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/)).length;
  623. }
  624. const currentTimeMillis = new Date().getTime();
  625. const linkTextStart = `linkTextStart${currentTimeMillis}`;
  626. const linkTextEnd = `linkTextEnd${currentTimeMillis}`;
  627. const linkUrlStart = `linkUrlStart${currentTimeMillis}`;
  628. const linkUrlEnd = `linkUrlEnd${currentTimeMillis}`;
  629. const hashtag = `hashtag${currentTimeMillis}`
  630. combined.forEach(item => {
  631. let start = item.indices[0];
  632. let end = item.indices[1];
  633. // サロゲートペアの数をカウントして調整
  634. const adjustment = countSurrogatePairs(transformedText.slice(0, end));
  635. start += adjustment;
  636. end += adjustment;
  637. let replacement = '';
  638. switch(item.type){
  639. case 'hashtag':
  640. replacement = `${linkTextStart}${hashtag}${item.text}${linkTextEnd}${linkUrlStart}https://twitter.com/hashtag/${item.text}${linkUrlEnd}`;
  641. break;
  642. case 'mention':
  643. replacement = `${linkTextStart}@${item.text}${linkTextEnd}${linkUrlStart}https://twitter.com/${item.text}${linkUrlEnd}`;
  644. break;
  645. case 'symbol':
  646. replacement = `${linkTextStart}$${item.text}${linkTextEnd}${linkUrlStart}https://twitter.com/search?q=%24${item.text}&src=cashtag_click${linkUrlEnd}`;
  647. break;
  648. }
  649. transformedText = transformedText.slice(0, start) + replacement + transformedText.slice(end);
  650. });
  651. twitter_tweet_data.full_text = str_max_length(transformedText,7000);
  652. //マークダウンにならないでほしいやつのエスケープ
  653. let escapeCharacters = ['|', '*', '_', '`', '~', '[', ']', '(', ')', '>', '#', '-'];
  654. escapeCharacters.forEach(char => {
  655. let regExp = new RegExp('\\' + char, 'g');
  656. twitter_tweet_data.full_text = twitter_tweet_data.full_text.replace(regExp, '\\' + char);
  657. });
  658. //マークダウンになって欲しいやつは戻す
  659. twitter_tweet_data.full_text = twitter_tweet_data.full_text.replace(new RegExp(linkTextStart, 'g'), '[');
  660. twitter_tweet_data.full_text = twitter_tweet_data.full_text.replace(new RegExp(linkTextEnd, 'g'), ']');
  661. twitter_tweet_data.full_text = twitter_tweet_data.full_text.replace(new RegExp(linkUrlStart, 'g'), '(');
  662. twitter_tweet_data.full_text = twitter_tweet_data.full_text.replace(new RegExp(linkUrlEnd, 'g'), ')');
  663. twitter_tweet_data.full_text = twitter_tweet_data.full_text.replace(new RegExp(hashtag, 'g'), '#');
  664. /*
  665. try{
  666. if(select_pages.length > 1 && ! twitter_tweet_data.media.every(v => v.media_type == "photo")){
  667. twitter_tweet_data.images = [twitter_tweet_data.media[0]];
  668. }
  669. }catch{}
  670. try{
  671. if(twitter_tweet_data.media.every(v => v.media_type.match(/(video|animated_gif)/))){
  672. twitter_tweet_data.videos = [twitter_tweet_data.media[0]];
  673. }
  674. }catch{}
  675. */
  676. let tweet_url;
  677. if(!quoted_tweet_mode == 1){
  678. tweet_url = tweet_link;
  679. }else{
  680. tweet_url = `https://twitter.com/${twitter_user_data.screen_name}/status/${twitter_tweet_data.id}`;
  681. }
  682. tmpEmbed.color = 1940464;
  683. tmpEmbed.title = "Tweet";
  684. tmpEmbed.url = tweet_url;
  685. tmpEmbed.author = {
  686. "name": `${twitter_user_data.name} (@${twitter_user_data.screen_name})`,
  687. "url": `https://twitter.com/${twitter_user_data.screen_name}`,
  688. "icon_url": `attachment://profile_images.${twitter_user_data.profile_image.split('.').pop()}`
  689. };
  690. tmpEmbed.description = replace_t_co_to_original_url(twitter_tweet_data.full_text,twitter_tweet_data.urls,twitter_tweet_data.media);
  691. tmpEmbed.thumbnail = {"url": "https://pbs.twimg.com/profile_images/1488548719062654976/u6qfBBkF_400x400.jpg"};
  692. tmpEmbed.fields = [
  693. {
  694. "name": `${textData.various_links}:link:`,
  695. "value": `[${textData.link_to_tweet}](${tweet_url})` + `\n[TwitterID: ${twitter_user_data.ID}](https://twitter.com/intent/user?user_id=${twitter_user_data.ID})` + if_exsit_return_text(twitter_tweet_data.media[0]?.url,`\n[${textData.link_to_image}](${image_url_to_original(twitter_tweet_data.media[0]?.url)})`) + if_exsit_return_text(twitter_user_data.pixiv_url,`\n[Pixiv](${twitter_user_data.pixiv_url})`)
  696. },{
  697. "name": textData.engagement,
  698. "value": `${textData.retweets} ${round_half_up(twitter_tweet_data.retweet_count,textData.roundingScale,textData.decimalPlaces,textData.units)}:recycle: ${textData.likes} ${round_half_up(twitter_tweet_data.favorite_count,textData.roundingScale,textData.decimalPlaces,textData.units)}:heart:`
  699. },{
  700. "name": textData.postedDate,
  701. "value": twitter_tweet_data.created_at
  702. }
  703. ];
  704. if(twitter_tweet_data.media.images[0]?.url){
  705. tmpEmbed.image = {
  706. "url": `attachment://${twitter_tweet_data.media.images[0].url.split('/').pop()}`
  707. }
  708. }
  709. embeds.push(tmpEmbed);
  710. if(twitter_tweet_data.media.images[1]?.url){
  711. for(let i=1;i<twitter_tweet_data.media.images.length;i++){
  712. embeds[i] = {
  713. "url": tweet_url,
  714. "image": {"url": `attachment://${twitter_tweet_data.media.images[i].url.split('/').pop()}`}
  715. }
  716. }
  717. }
  718. tmp_return_object.push({"embeds": embeds, "files": await fetchImages(twitter_tweet_data.media.images.concat([{"url": twitter_user_data.profile_image}]))});
  719. if(twitter_tweet_data.media.videos?.length >= 1 && thisScriptSettings.downloadVideo){
  720. const promises = twitter_tweet_data.media.videos.map(video => downloadVideo(video.url));
  721. await Promise.all(promises)
  722. .then(results => {
  723. results.forEach(obj => {
  724. tmp_return_object.push(obj);
  725. });
  726. })
  727. .catch(error => {
  728. console.error("Error downloading videos:", error);
  729. });
  730. }else if(twitter_tweet_data.media.videos?.length >= 1){
  731. twitter_tweet_data.media.videos.forEach(video => {
  732. tmp_return_object.push({"content": video.url});
  733. });
  734. }
  735. if(send_quoted_tweet && quoted_tweet_mode == 0 && (tweet_tweet_data_json.result?.quoted_status_result||tweet_tweet_data_json.quoted_status)){
  736. let tmp_quoted_data = tweet_tweet_data_json.result?.quoted_status_result||tweet_tweet_data_json.quoted_status;
  737. tmp_quoted_data.APIsource = tweet_data.APIsource;
  738. tmp_return_object.push({"quoted_tweet_data": tmp_quoted_data});
  739. }
  740. return tmp_return_object;
  741. }
  742. async function downloadVideo(url){
  743. return new Promise(async (resolve) => {
  744. if((await request(new requestObject_binary_head(url))).responseHeaders.match(/content-length: ?(\d+)/)[1] < 24117249){
  745. return resolve({"files": [{attachment: (await request(new requestObject_binary_data(url))).response, name: url.split('/').pop()}]});
  746. }else{
  747. return resolve({"content": url});
  748. }
  749. });
  750. }
  751. function extractUrls(entities){
  752. let urls = [];
  753. if(entities.description && entities.description.urls){
  754. entities.description.urls.forEach(urlObj => {
  755. urls.push(urlObj.expanded_url || urlObj.url);
  756. });
  757. }
  758. if(entities.url && entities.url.urls){
  759. entities.url.urls.forEach(urlObj => {
  760. urls.push(urlObj.expanded_url || urlObj.url);
  761. });
  762. }
  763. return urls;
  764. }
  765. function replace_t_co_to_original_url(full_text,urls,media_urls){
  766. //ツイート内のt.coで短縮されたリンクをもとにのリンクにもどす。
  767. try{
  768. if(typeof full_text !== "undefined"){
  769. full_text = decodeHtml(full_text);
  770. if(typeof urls !== "undefined"){
  771. for(let i=0;i<=urls.length-1;i++){
  772. if(urls[i].expanded_url.length > 200){
  773. full_text = full_text.replace(urls[i].url,`[${decodeURI(urls[i].expanded_url).slice(0,150)}](${decodeURI(urls[i].expanded_url)}) ... `);
  774. }else{
  775. full_text = full_text.replace(urls[i].url,decodeURI(urls[i].expanded_url));
  776. }
  777. }
  778. }
  779. //メディアがくっついてるツイートは末尾にメディアのURLが付随しているためそれを消す。
  780. if(typeof media_urls !== "undefined"){
  781. for(let i=0;i<=media_urls.length-1;i++){
  782. full_text = full_text.replace(media_urls[i].tco_url,"");
  783. }
  784. }
  785. }
  786. }catch{}
  787. return full_text;
  788. }
  789. function if_exsit_return_text(variable,return_text){
  790. if(!(variable === null || variable === undefined || variable === "")){
  791. return return_text
  792. }
  793. return "";
  794. }
  795. function make_media_list(extended_entities, select_pages){
  796. var result = {images: [],videos: []};
  797. if(extended_entities){
  798. for(const target in select_pages){
  799. try{
  800. if(extended_entities.media.length > select_pages[target]){
  801. let mediaItem = extended_entities.media[select_pages[target]];
  802. let tmp_object = {media_type: mediaItem.type, tco_url: mediaItem.url};
  803. if(tmp_object.media_type == "animated_gif" || tmp_object.media_type == "video"){
  804. tmp_object.url = mediaItem.video_info.variants.filter(function(obj){return obj.content_type == "video/mp4";}).reduce((a, b) => a.bitrate > b.bitrate ? a : b).url.split('?')[0];
  805. result.videos.push(tmp_object);
  806. }else if(tmp_object.media_type == "photo"){
  807. tmp_object.url = mediaItem.media_url_https;
  808. result.images.push(tmp_object);
  809. }
  810. }
  811. }catch(error){
  812. console.error("メディアリストの作成に失敗しました。:\n" + error);
  813. }
  814. }
  815. }
  816. return result;
  817. }
  818. function str_max_length(text, max_length,defaultValue = "……以下Discordの字数オーバー。"){
  819. var r = 0;
  820. for(var i = 0; i < text.length; i++){
  821. var c = text.charCodeAt(i);
  822. if((c >= 0x0 && c < 0x81) || (c == 0xf8f0) || (c >= 0xff61 && c < 0xffa0) || (c >= 0xf8f1 && c < 0xf8f4)){
  823. r += 1;
  824. }else{
  825. r += 2;
  826. }
  827. if(r >= max_length){
  828. text = `${text.slice(0, i - 1)} ${defaultValue}`;
  829. break;
  830. }
  831. }
  832. return text;
  833. }
  834. function image_url_to_original(image_url){
  835. //apiから帰ってくるURLをそのまま開くと小さい画像になってしまうので最大サイズの画像をダウンロードできるようにする。
  836. if(typeof image_url !== "undefined"){
  837. var extension = image_url.split(".").pop();
  838. return `${image_url.replace(`.${extension}`,"")}?format=${extension}&name=orig`
  839. }
  840. }
  841. async function fetchImages(mediaUrlArray){
  842. if(mediaUrlArray?.length == 0) return;
  843. let downloadPromises = mediaUrlArray.map(fetchImage);
  844. return remove_null_from_array(await Promise.all(downloadPromises));
  845. async function fetchImage(target) {
  846. let retryCount = 5; // リトライ回数を設定
  847. while(retryCount > 0){
  848. if(!target.url) return;
  849. let image;
  850. let name;
  851. if(target.url.match(/https?:\/\/pbs\.twimg\.com\/media\//)){
  852. image = await request(new requestObject_binary_data(image_url_to_original(target.url)),3);
  853. name = target.url.split('/').pop();
  854. }else{
  855. image = await request(new requestObject_binary_data(target.url),3);
  856. name = "profile_images." + target.url.split('.').pop();
  857. }
  858. // ダウンロードした画像データのサイズを確認
  859. if(image.response.size > 1024){
  860. return {
  861. "attachment": image.response,
  862. "name": name,
  863. };
  864. }else{
  865. retryCount--;
  866. }
  867. }
  868. console.warn(`Failed to download image after multiple retries: ${target.url}`);
  869. return null;
  870. }
  871. }
  872. }
  873. }
  874. async function sneakilyFavorite(tweetNodes){
  875. tweetNodes.forEach(function(element){
  876. const node = element.node;
  877. if(node.querySelector(".sneakilyFavorite") || ! node.querySelector(env_selector.retweeted) || !node.querySelector('[data-testid="like"]')) return;
  878. let tweet_link = element.link;
  879. let button = document.createElement('button');
  880. let fotter = node.querySelector('div[id][role="group"]');
  881. let like_element = fotter.querySelector('[data-testid="like"]');
  882. button.textContent = env_Text.sneakilyFavorite.favorite;
  883. button.style.fontSize = '0.7em';
  884. button.style.whiteSpace = 'nowrap';
  885. button.classList.add('sneakilyFavorite');
  886. button.addEventListener('click',async function(event){
  887. this.disabled = true;
  888. const status = await request(new requestObject_twitter_FavoriteTweet(tweet_link));
  889. if(status.response.data.favorite_tweet == "Done"){
  890. like_element.querySelector('div[dir="ltr"]').classList.add(env_selector.liked_color);
  891. like_element.querySelector('div[dir="ltr"]').style.color = "rgb(249, 24, 128)";
  892. like_element.querySelector("path").setAttribute('d',env_selector.liked);
  893. like_element.setAttribute('data-testid', 'unlike');
  894. }
  895. this.remove();
  896. });
  897. fotter.insertBefore(button, like_element.parentElement.nextSibling);
  898. });
  899. }
  900. async function Engagement_Restorer(){
  901. if(!currentUrl.match(/https?\:\/\/twitter\.com\/\w*\/status\/[0-9]*($|\?.*)/) || document.getElementById('restoreEngagements') || isFunctionRunning.Engagement_Restorer)return;
  902. isFunctionRunning.Engagement_Restorer = true;
  903. try{
  904. const tweetLink = currentUrl.match(/https?\:\/\/twitter\.com\/\w*\/status\/[0-9].*/)[0];
  905. const tweetId = tweetLink.match(/\/status\/(\d+)/)[1];
  906. const response = (await getTweetData(tweetId,"graphQL")).legacy;
  907. const engagemants = {"favorite_count": response.favorite_count,"quote_count": response.quote_count,"retweet_count": response.retweet_count};
  908. const target_node = Array.from((await wait_load_Element('article[data-testid="tweet"]',200,5))).find(node => {
  909. const timeParents = Array.from(node.querySelectorAll('time')).map(time => time.parentNode);
  910. return timeParents.some(parent => parent.href && parent.href.match(tweetId));
  911. });
  912. if(!target_node)return;
  913. const engagemants_aria = target_node.querySelector('[role="group"]');
  914. if(!engagemants_aria)return;
  915. let envEngagementsTextColor = env_selector.engagementsTextColor[GetCookie("night_mode") || "0"];
  916. const flexContainer = document.createElement('div');
  917. flexContainer.style.display = 'flex';
  918. flexContainer.style.justifyContent = 'space-between';
  919. flexContainer.style.width = '70%';
  920. flexContainer.id = 'restoreEngagements';
  921. const links = [
  922. {
  923. "name": "retweets",
  924. "href": tweetLink + "/retweets",
  925. "count": round_half_up(engagemants.retweet_count,env_Text.Engagement_Restorer.roundingScale,env_Text.Engagement_Restorer.decimalPlaces,env_Text.Engagement_Restorer.units),
  926. "text": env_Text.Engagement_Restorer.retweet
  927. },
  928. {
  929. "name": "quotes",
  930. "href": tweetLink + "/quotes",
  931. "count": round_half_up(engagemants.quote_count,env_Text.Engagement_Restorer.roundingScale,env_Text.Engagement_Restorer.decimalPlaces,env_Text.Engagement_Restorer.units),
  932. "text": env_Text.Engagement_Restorer.quoted,
  933. },
  934. {
  935. "name": "likes",
  936. "href": tweetLink + "/likes",
  937. "count": round_half_up(engagemants.favorite_count,env_Text.Engagement_Restorer.roundingScale,env_Text.Engagement_Restorer.decimalPlaces,env_Text.Engagement_Restorer.units),
  938. "text": env_Text.Engagement_Restorer.like,
  939. },
  940. ];
  941. links.forEach((a, index) => {
  942. const newLink = document.createElement('a');
  943. newLink.style.textDecoration = 'none';
  944. newLink.href = a.href;
  945. const countText = document.createElement('span');
  946. countText.textContent = a.count;
  947. countText.style.color = envEngagementsTextColor.count;
  948. newLink.appendChild(countText);
  949.  
  950. const textPart = document.createElement('span');
  951. textPart.textContent = " " + a.text;
  952. textPart.style.color = envEngagementsTextColor.text;
  953. newLink.appendChild(textPart);
  954.  
  955. newLink.addEventListener('click', (e) => {
  956. e.preventDefault();
  957. clickTab(a.name,target_node);
  958. });
  959. flexContainer.appendChild(newLink);
  960. });
  961. if(currentUrl.match(/https?\:\/\/twitter\.com\/\w*\/status\/[0-9]*($|\?.*)/))engagemants_aria.parentNode.prepend(flexContainer);
  962. }catch(error){
  963. console.error(error)
  964. }finally{
  965. isFunctionRunning.Engagement_Restorer = false;
  966. }
  967. async function clickTab(name,target_node){
  968. target_node.querySelector('[data-testid="caret"]').click();
  969. document.querySelector('[data-testid="tweetEngagements"]').click();
  970. const engagemants_aria = (await wait_load_Element('nav[aria-live="polite"]'))[0];
  971. const regex = new RegExp(name + '$');
  972. engagemants_aria.querySelectorAll('[role="presentation"] a').forEach((e)=>{
  973. if(e.href.match(regex)) e.click();
  974. });
  975. }
  976. }
  977. async function Hello_tweet_where_are_you_from(){
  978. if(document.querySelector('.display_twitter_client') || isFunctionRunning.Hello_tweet_where_are_you_from)return;
  979. isFunctionRunning.Hello_tweet_where_are_you_from = true;
  980. try{
  981. const tweet_data = await getTweetData(extractTweetId(currentUrl),"graphQL");
  982. const target_node = (await wait_load_Element(env_selector.info_field,300,4))[0];
  983. const info_selector = target_node.childNodes[0].className;
  984. let client_text = document.createElement("div");
  985. client_text.dir = "ltr";
  986. client_text.innerHTML = `<span class="display_twitter_client ${info_selector}" style="">${tweet_data.source.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'')}</span>`;
  987. target_node.appendChild(client_text);
  988. const media_data = tweet_data.legacy?.extended_entities?.media || tweet_data.extended_entities?.media;
  989. if(!media_data)return;
  990. const video_data = media_data.map((mediaItem, index) => {
  991. if (['video', 'animated_gif'].includes(mediaItem.type)){
  992. const highestBitrateVariant = mediaItem.video_info.variants.filter(obj => obj.content_type !== 'application/x-mpegURL').reduce((a, b) => a.bitrate > b.bitrate ? a : b);
  993. return {
  994. index: index,
  995. url: highestBitrateVariant.url.split('?')[0]
  996. };
  997. }
  998. }).filter(Boolean)
  999. if(video_data.length === 0)return;
  1000. let show_video_url = document.createElement("span");
  1001. show_video_url.className = "display_video_url";
  1002. show_video_url.style.whiteSpace = "pre";
  1003. for(let video of video_data){
  1004. let video_url = document.createElement("a");
  1005. video_url.href = video.url;
  1006. video_url.textContent = `${video.index + 1}:video_url`;
  1007. video_url.className = "css-4rbku5 css-18t94o4 css-901oao css-16my406 r-1cvl2hr r-1loqt21 r-poiln3 r-bcqeeo r-qvutc0";
  1008. video_url.style.color = "rgb(29, 155, 240)";
  1009. video_url.target = "_blank";
  1010. video_url.rel = "noopener";
  1011. show_video_url.appendChild(video_url);
  1012. if(video !== video_data[video_data.length - 1]){
  1013. show_video_url.appendChild(document.createTextNode(' '));
  1014. }
  1015. }
  1016. client_text.appendChild(document.createElement("br"));
  1017. client_text.appendChild(show_video_url);
  1018. }catch(error){
  1019. if(error === "Max retry count reached")return;
  1020. console.error(error);
  1021. }finally{
  1022. isFunctionRunning.Hello_tweet_where_are_you_from = false;
  1023. }
  1024. }
  1025.  
  1026. async function Note_Tweet_expander(tweetNodes){
  1027. const link_class = "r-18u37iz css-4rbku5 css-18t94o4 css-901oao css-16my406 r-1cvl2hr r-1loqt21 r-poiln3 r-bcqeeo r-qvutc0";
  1028. tweetNodes.forEach(async function(target){
  1029. const tweet_node = target.node;
  1030. const elements = tweet_node.querySelectorAll('[data-testid="tweetText"]');
  1031. const show_more_link = tweet_node.querySelectorAll('[data-testid="tweet-text-show-more-link"]');
  1032. if(show_more_link[0])show_more_link[0].style.display = "none";
  1033. if(show_more_link[1])show_more_link[1].style.display = "none";
  1034. elements.forEach(async (element,index) =>{
  1035. if(!element.innerText.match(/…$/)){
  1036. element.classList.add('tweetExpanderChecked');
  1037. //if(element.innerText.split('\n').length >= 10)
  1038. element.style.webkitLineClamp = null;
  1039. return;
  1040. }
  1041. let tweet_id,tweet_data,note_tweet;
  1042. if(index == 0){
  1043. tweet_id = target.id;
  1044. tweet_data = await getTweetData(tweet_id,"graphQL");
  1045. note_tweet = tweet_data.note_tweet?.note_tweet_results.result || tweet_data.note_tweet?.note_tweet_results.result || null;
  1046. }else{
  1047. tweet_data = await getTweetData(target.id,"graphQL");
  1048. tweet_data = await getTweetData(tweet_data.legacy.quoted_status_id_str,"graphQL");
  1049. note_tweet = tweet_data.note_tweet?.note_tweet_results.result || tweet_data.note_tweet?.note_tweet_results.result || null;
  1050. }
  1051. if(!note_tweet){
  1052. element.style.webkitLineClamp = null;
  1053. element.classList.add('tweetExpanderChecked');
  1054. return;
  1055. }
  1056. const hashtags = note_tweet.entity_set?.hashtags || [];
  1057. const user_mentions = note_tweet.entity_set?.user_mentions || [];
  1058. const symbols = note_tweet.entity_set?.symbols || [];
  1059. const urls = note_tweet.entity_set?.urls;
  1060. var new_tweet_text = note_tweet.text;
  1061. function countSurrogatePairs(str){
  1062. return Array.from(str).filter(char => char.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/)).length;
  1063. }
  1064. let combined = [].concat(
  1065. hashtags.map(tag => ({
  1066. type: 'hashtag',
  1067. indices: tag.indices,
  1068. text: tag.text
  1069. })),
  1070. user_mentions.map(mention => ({
  1071. type: 'mention',
  1072. indices: mention.indices,
  1073. text: mention.screen_name
  1074. })),
  1075. symbols.map(symbol => ({
  1076. type: 'symbol',
  1077. indices: symbol.indices,
  1078. text: symbol.text
  1079. }))
  1080. );
  1081. // combinedをindicesの順にソート
  1082. combined.sort((a, b) => b.indices[0] - a.indices[0]);
  1083. let transformedText = new_tweet_text;
  1084.  
  1085. combined.forEach(item => {
  1086. let start = item.indices[0];
  1087. let end = item.indices[1];
  1088.  
  1089. // サロゲートペアの数をカウントして調整
  1090. const adjustment = countSurrogatePairs(transformedText.slice(0, end));
  1091. start += adjustment;
  1092. end += adjustment;
  1093.  
  1094. let replacement = '';
  1095. switch(item.type){
  1096. case 'hashtag':
  1097. replacement = `<a class="${link_class}" dir="ltr" role="link" href="https://twitter.com/hashtag/${item.text}" target="_blank" rel="noopener">#${item.text}</a>`;
  1098. break;
  1099. case 'mention':
  1100. replacement = `<a class="${link_class}" dir="ltr" role="link" href="https://twitter.com/${item.text}" target="_blank" rel="noopener">@${item.text}</a>`;
  1101. break;
  1102. case 'symbol':
  1103. replacement = `<a class="${link_class}" dir="ltr" role="link" href="https://twitter.com/search?q=%24${item.text}&src=cashtag_click" target="_blank" rel="noopener">$${item.text}</a>`;
  1104. break;
  1105. }
  1106. transformedText = transformedText.slice(0, start) + replacement + transformedText.slice(end);
  1107. });
  1108. new_tweet_text = transformedText;
  1109. urls.forEach(target =>{
  1110. new_tweet_text = new_tweet_text.replace(new RegExp(`${target.url}(?=(\\s|$|\\u3000|\\W)(?!\\.|,))`, 'gu'), `<a class="${link_class}" dir="ltr" role="link" href="${target.url}" target="_blank" rel="noopener">${target.display_url}</a>`);
  1111. });
  1112. var new_tweet_node = document.createElement("span");
  1113. new_tweet_node.className = 'css-901oao css-16my406 r-1tl8opc r-bcqeeo r-qvutc0';
  1114. new_tweet_node.innerHTML = new_tweet_text;
  1115. while(element.firstChild){
  1116. element.removeChild(element.firstChild);
  1117. }
  1118. element.appendChild(new_tweet_node);
  1119. element.style.webkitLineClamp = null;
  1120. });
  1121. });
  1122. }
  1123.  
  1124. async function Show_me_your_Pixiv(tweetNodes){
  1125. let todo = [];
  1126. tweetNodes.forEach(item => {
  1127. const node = item.node.querySelector(`${env_selector.media_field}:not(.display_pixiv_link)`);
  1128. if(node){
  1129. todo.push({
  1130. "node": node,
  1131. "name": item.node.querySelector('[data-testid="User-Name"]>div>div>a').href.split("/").pop()
  1132. });
  1133. }
  1134. });
  1135. let currentScreenName = extractUserName(currentUrl);
  1136. const uniqueScreenNames = [...new Set(todo.map(item => item.name))];
  1137. if(currentScreenName){
  1138. uniqueScreenNames.push(currentScreenName);
  1139. }
  1140. let promises = uniqueScreenNames.map(async screen_name => {
  1141. if(!((scriptDataStore.Show_me_your_Pixiv[screen_name]?.pixiv_url) === undefined))return "Already exists";
  1142. if(((scriptDataStore.Show_me_your_Pixiv[screen_name]?.Create_date || 0) + 604800000) <= new Date().getTime()){
  1143. let userEntitiesData = await getTweetData(screen_name,"user_1_1");
  1144. userEntitiesData = userEntitiesData.legacy?.entities || userEntitiesData.entities;
  1145. const end_stat = await find_pixiv_link(extractUrls(userEntitiesData));
  1146. if(end_stat == "Too Many Requests"){
  1147. console.log("API limit.");
  1148. }else if(!end_stat){
  1149. scriptDataStore.Show_me_your_Pixiv[screen_name] = {"pixiv_url": null,"Create_date": new Date().getTime()};
  1150. return `${screen_name}: Pixivリンクなし`;
  1151. }else{
  1152. scriptDataStore.Show_me_your_Pixiv[screen_name] = {"pixiv_url": end_stat.replace(/^https?/,'https'),"Create_date": new Date().getTime()};
  1153. return `${screen_name}: ${end_stat}`;
  1154. }
  1155. }else{
  1156. return "Not yet time";
  1157. }
  1158. });
  1159. const results = await Promise.allSettled(promises);
  1160. results.forEach(result => {
  1161. if(result.status === 'fulfilled'){
  1162. //debug_log(`${result.value}`);
  1163. }else{
  1164. console.error(`Failure: ${result.reason}`);
  1165. }
  1166. });
  1167. todo.forEach(item => {
  1168. const node = item.node;
  1169. const screen_name = item.name;
  1170. const pixiv_url = scriptDataStore.Show_me_your_Pixiv[screen_name]?.pixiv_url;
  1171. if(pixiv_url && node && !(node?.querySelector(".display_pixiv_link")))node.appendChild(makeLinkElement(pixiv_url,"Pixiv🔗","display_pixiv_link"));
  1172. });
  1173. if(scriptDataStore.Show_me_your_Pixiv[currentScreenName]?.pixiv_url && !currentUrl.match(new RegExp(`${currentScreenName}/status`))){
  1174. const profile_field = (await wait_load_Element(env_selector.profile_field_Header_Items))[0];
  1175. currentScreenName = extractUserName(currentUrl);
  1176. if(!profile_field.querySelector(".display_pixiv_link") && scriptDataStore.Show_me_your_Pixiv[currentScreenName]?.pixiv_url){
  1177. let brElement = document.createElement("br");
  1178. profile_field.appendChild(brElement);
  1179. profile_field.appendChild(makeLinkElement(scriptDataStore.Show_me_your_Pixiv[currentScreenName]?.pixiv_url, "Pixiv🔗", "display_pixiv_link"));
  1180. }
  1181. }
  1182. localStorage.setItem('user_pixvi_link_collection', JSON.stringify(scriptDataStore.Show_me_your_Pixiv));
  1183. function makeLinkElement(href,text,additionalClass){
  1184. let new_content = document.createElement("a");
  1185. new_content.style.color = "rgb(29, 155, 240)";
  1186. new_content.href = href;
  1187. new_content.textContent = text;
  1188. new_content.className = `${additionalClass} css-4rbku5 css-18t94o4 css-901oao css-16my406 r-1cvl2hr r-1loqt21 r-poiln3 r-bcqeeo r-qvutc0`;
  1189. new_content.target = "_blank";
  1190. new_content.rel = "noopener";
  1191. return new_content;
  1192. }
  1193. function extractUrls(entities){
  1194. let urls = [];
  1195. if (entities.description && entities.description.urls){
  1196. entities.description.urls.forEach(urlObj => {
  1197. urls.push(urlObj.expanded_url || urlObj.url);
  1198. });
  1199. }
  1200. if (entities.url && entities.url.urls){
  1201. entities.url.urls.forEach(urlObj => {
  1202. urls.push(urlObj.expanded_url || urlObj.url);
  1203. });
  1204. }
  1205. return urls;
  1206. }
  1207. }
  1208.  
  1209.  
  1210. /////////////////////////////////////////////////////////////////////
  1211. ///////////////////////////ここから汎用関数///////////////////////////
  1212. /////////////////////////////////////////////////////////////////////
  1213.  
  1214. function update(){
  1215. if(updating) return;
  1216. updating = true;
  1217. //console.log({user: fetchedTweetsUserDataByUserName,tweets: fetchedTweets})
  1218. main();
  1219. setTimeout(() => {updating = false;}, 600);
  1220. }
  1221. function extractTweetId(url){
  1222. const match = url.match(/twitter\.com\/[^/]+\/status\/(\d+)/);
  1223. return match ? match[1] : null;
  1224. }
  1225. function extractUserName(url){
  1226. const match = url.match(denyNamesRegex);
  1227. return match ? match[1] : null;
  1228. }
  1229. function findParent(element, selector){
  1230. let current = element;
  1231. while(current !== null){
  1232. if(current.matches(selector)){
  1233. return current;
  1234. }
  1235. current = current.parentNode;
  1236. }
  1237. return null;
  1238. }
  1239. function locationChange(){
  1240. const observer = new MutationObserver(mutations => {
  1241. if(currentUrl !== document.location.href){
  1242. currentUrl = document.location.href;
  1243. try{
  1244. if(extractTweetId(currentUrl))getTweetData(extractTweetId(currentUrl),"graphQL");
  1245. if(extractUserName(currentUrl))getTweetData(extractUserName(currentUrl),"user");
  1246. }catch(error){console.error(error)}
  1247. main();
  1248. }
  1249. });
  1250. const target = document.getElementById("react-root");
  1251. const config = {childList: true,subtree: true};
  1252. observer.observe(target, config);
  1253. }
  1254. function wait_load_Element(Element_Name,interval = 100,retry = 25){
  1255. return new Promise((resolve, reject) => {
  1256. const MAX_RETRY_COUNT = retry;
  1257. let retry_counter = 0;
  1258. let set_interval_id = setInterval(find_target_element, interval);
  1259. function find_target_element(){
  1260. retry_counter++;
  1261. if(retry_counter > MAX_RETRY_COUNT){
  1262. clearInterval(set_interval_id);
  1263. return reject("Max retry count reached");
  1264. }
  1265. let target_elements = document.querySelectorAll(`${Element_Name}`);
  1266. if(target_elements.length > 0){
  1267. clearInterval(set_interval_id);
  1268. return resolve(target_elements);
  1269. }
  1270. }
  1271. });
  1272. }
  1273. function GetCookie(name){
  1274. let arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
  1275. if(arr = document.cookie.match(reg)){
  1276. return decodeURIComponent(arr[2]);
  1277. }else{
  1278. return null;
  1279. }
  1280. }
  1281. function remove_null_from_array(arr){
  1282. return arr.filter(function(x){return !(x === null || x === undefined || x === "")});
  1283. }
  1284. function get_only_particular_key_value(object, path, defaultValue = undefined){
  1285. const isArray = Array.isArray;
  1286. if(object == null || typeof object != 'object') return defaultValue;
  1287. return (isArray(object)) ? object.map(createProcessFunction(path)) : createProcessFunction(path)(object);
  1288. function createProcessFunction(path){
  1289. if(typeof path == 'string') path = path.split('.');
  1290. if(!isArray(path)) path = [path];
  1291. return function(object){
  1292. let index = 0,
  1293. length = path.length;
  1294. while(index < length){
  1295. const key = toString_(path[index++]);
  1296. if(object === undefined){
  1297. return defaultValue;
  1298. }
  1299. // 配列に対する処理
  1300. if(isArray(object)){
  1301. object = object.map(item => item[key]);
  1302. }else{
  1303. object = object[key];
  1304. }
  1305. }
  1306. return (index && index == length) ? object : void 0;
  1307. };
  1308. }
  1309. function toString_(value){
  1310. if(value == null) return '';
  1311. if(typeof value == 'string') return value;
  1312. if(isArray(value)) return value.map(toString) + '';
  1313. let result = value + '';
  1314. return '0' == result && 1 / value == -(1 / 0) ? '-0' : result;
  1315. }
  1316. }
  1317. async function sleep(time){
  1318. return new Promise((resolve)=>{
  1319. setTimeout(()=>{return resolve(time)}, time);
  1320. });
  1321. }
  1322. async function expand_shortening_link(urls){
  1323. let isInputArray = true;
  1324. if(typeof urls === 'string'){
  1325. urls = [urls];
  1326. isInputArray = false;
  1327. }
  1328. function isValidURL(url){
  1329. return url.match(/https?:\/\/[\w!\?/\+\-_~=;\.,\*&@#\$%\(\)'\[\]]+/);
  1330. }
  1331. async function expandURL(url){
  1332. if(!isValidURL(url)){
  1333. throw new Error(`Invalid URL: ${url}`);
  1334. }
  1335. const response = await request(new requestObject_url_expand(url));
  1336. return{
  1337. original: url,
  1338. expanded: response.response
  1339. };
  1340. }
  1341. const results = await Promise.all(urls.map(url => expandURL(url)));
  1342. if(!isInputArray){
  1343. return results[0];
  1344. }
  1345. return results;
  1346. }
  1347. async function find_pixiv_link(urls){
  1348. const Pixiv_url_regex = /^https?:\/\/(((www|touch)\.)?pixiv\.(net\/([a-z]{2}\/)?((member(_illust)?\.php\?id\=|(users|u)\/)[0-9]*)|me\/.*))/;
  1349. const Fanbox_url_regex = /^https?:(\/\/www\.pixiv\.net\/fanbox\/creator\/[0-9]*|\/\/.*\.fanbox\.cc\/?)/;
  1350. return new Promise(async function(resolve){
  1351. let tmp_urls = urls;
  1352. let Pixiv_url;
  1353. if(tmp_urls.length > 0){
  1354. Pixiv_url = await find_pixiv_link_from_profile_urls(tmp_urls);
  1355. if(Pixiv_url === undefined || Pixiv_url === null || Pixiv_url === false){
  1356. tmp_urls = get_only_particular_key_value((await expand_shortening_link(tmp_urls)),"expanded",[]);
  1357. Pixiv_url = await find_pixiv_link_from_profile_urls(tmp_urls);
  1358. return resolve(Pixiv_url);
  1359. }else{
  1360. return resolve(Pixiv_url);
  1361. }
  1362. }
  1363. return resolve(null);
  1364. });
  1365. async function find_pixiv_link_from_profile_urls(urls_in_profile){
  1366. return new Promise(async function(resolve,reject){
  1367. let tmp_pixiv_url;
  1368. tmp_pixiv_url = findMatch_from_array(urls_in_profile,Pixiv_url_regex,true);
  1369. if (tmp_pixiv_url !== undefined) return resolve(tmp_pixiv_url);
  1370. if(findMatch_from_array(urls_in_profile,Fanbox_url_regex) !== undefined){
  1371. tmp_pixiv_url = await when_fanbox(findMatch_from_array(urls_in_profile,Fanbox_url_regex,true));
  1372. if(Pixiv_url_regex.test(tmp_pixiv_url)){
  1373. return resolve(tmp_pixiv_url);
  1374. }
  1375. }else{
  1376. let get_url_promise_list = [];
  1377. urls_in_profile.forEach(target=>{
  1378. switch(true){
  1379. case /^https?:\/\/((skeb\.jp\/\@.*)|(fantia\.jp\/(fanclubs\/[0-9])?.*)|(.*\.booth\.pm)|(.*linktr\.ee)|(.*profcard\.info)|(.*lit\.link)|(potofu\.me)|(.*\.carrd\.co)|(.*\.tumblr\.com$)|(twpf\.jp))\/?/.test(target):
  1380. get_url_promise_list.push(new Promise(
  1381. async function(resolve,reject){
  1382. try{
  1383. return resolve(await when_general(target));
  1384. }catch(error){
  1385. return reject(error);
  1386. }
  1387. }
  1388. ));
  1389. break;
  1390. case /^https?:\/\/.*\.creatorlink\.net(\/.*)?/.test(target):
  1391. get_url_promise_list.push(new Promise(
  1392. async function(resolve,reject){
  1393. try{
  1394. return resolve(await when_general(`${target.match(/^https?:\/\/.*\.creatorlink\.net/)[0]}\/Contact`));
  1395. }catch(error){
  1396. return reject(error);
  1397. }
  1398. }
  1399. ));
  1400. break;
  1401. case /^https?:\/\/sketch\.pixiv\.net\//.test(target):
  1402. get_url_promise_list.push(new Promise(
  1403. async function(resolve,reject){
  1404. try{
  1405. return resolve(await when_pixiv_sketch(target));
  1406. }catch(error){
  1407. return reject(error);
  1408. }
  1409. }
  1410. ));
  1411. break;
  1412. default:
  1413. break;
  1414. }
  1415. });
  1416. if(get_url_promise_list.length > 0){
  1417. await Promise.any(get_url_promise_list).then((value) => {tmp_pixiv_url = value}).catch(() => {tmp_pixiv_url = undefined});
  1418. if(!Pixiv_url_regex.test(tmp_pixiv_url)) return resolve(undefined);
  1419. return resolve(tmp_pixiv_url.replace(/^https?/,'https').replace(/(\/|\\)$/,''));
  1420. }
  1421. }
  1422. return resolve(null);
  1423. async function when_general(target_url){
  1424. return new Promise(async function(resolve,reject){
  1425. const response_data = await request(new requestObject(target_url.replace(/^https?/,"https")));
  1426. let response_data_urls = response_data.response.split(/\"|\<|\>/).filter(function(data_str){return data_str.match(/^https?:(\/\/(((www|touch)\.)?pixiv\.(net\/([a-z]{2}\/)?((member(_illust)?\.php\?id\=|(users|u|fanbox\/creator)\/)[0-9].*)|me\/.*))|.*\.fanbox\.cc\/?)$/)});
  1427. if(response_data_urls.find(function(element){return element.match(Pixiv_url_regex)}) !== undefined){
  1428. return resolve(response_data_urls.find(function(element){return element.match(Pixiv_url_regex)}));
  1429. }else if(response_data_urls.find(function(element){return element.match(Fanbox_url_regex)}) !== undefined){
  1430. return resolve(await when_fanbox(response_data_urls.find(function(element){return element.match(Fanbox_url_regex)})));
  1431. }else{
  1432. return reject(undefined);
  1433. }
  1434. });
  1435. }
  1436. async function when_pixiv_sketch(target_url){
  1437. return new Promise(async function(resolve,reject){
  1438. const response_data = await request(new requestObject(target_url));
  1439. let User_id = response_data.response.split(',').filter(function(data_str){return data_str.match(/\\"pixiv_user_id\\":\\"[0-9]*\\"/)});
  1440. if(User_id){
  1441. return resolve("https://www.pixiv.net/users/" + User_id[0].split(/\"|\\/)[6]);
  1442. }else{
  1443. return reject(undefined);
  1444. }
  1445. });
  1446. }
  1447. async function when_fanbox(fanbox_url){
  1448. return new Promise(async function(resolve,reject){
  1449. if(fanbox_url.match(/^https?:\/\/www\.pixiv\.net\/fanbox\/creator\/[0-9]*/)){
  1450. return resolve(fanbox_url.replace('fanbox/creator', 'users'));
  1451. }else{
  1452. const fanbox_response = await request(new requestObject_fanbox(`https://api.fanbox.cc/creator.get?creatorId=${fanbox_url.replace(/(https?:\/\/|\.fanbox.*)/g,'')}`,fanbox_url.replace(/^http:/, 'https:').replace(/\/$/, '')));
  1453. if(fanbox_response.status == "404") return reject(undefined);
  1454. tmp_pixiv_url = findMatch_from_array(fanbox_response.response.body.profileLinks,Pixiv_url_regex,true);
  1455. if(tmp_pixiv_url !== undefined){
  1456. return resolve(tmp_pixiv_url);
  1457. }else{
  1458. return resolve(`https://www.pixiv.net/users/${fanbox_response.response.body.user.userId}`);
  1459. }
  1460. }
  1461. })
  1462. }
  1463. }
  1464. )}
  1465. }
  1466. function round_half_up(original_value,where_round_off,decimal_place = 0,unit_str = ""){
  1467. //四捨五入関数。
  1468. /*
  1469. original_valu: 元の値
  1470. where_round_off: どこで四捨五入するか(0.1, 1, 10, 100, 1000など)
  1471. decimal_place: 小数点以下を何桁にするか(1, 2, 3, 4, 5など)
  1472. unit_str: 単位を末尾につける(千,万など)
  1473. */
  1474. if(Number(original_value)>=Number(where_round_off)){
  1475. let tmp_value;
  1476. tmp_value = Math.round(Number(original_value) / Number(where_round_off) * Math.pow(10,Number(decimal_place))) / Math.pow(10,Number(decimal_place));
  1477. if(unit_str == ""){
  1478. return tmp_value;
  1479. }else{
  1480. return `${tmp_value}${unit_str}`
  1481. }
  1482. }else{
  1483. return original_value;
  1484. }
  1485. }
  1486. function findMatch_from_array(arr, regex, is_strict = false){
  1487. for(let i = 0; i < arr.length; i++){
  1488. if(regex.test(arr[i])){
  1489. if(is_strict === true){
  1490. return arr[i].match(regex)[0];
  1491. }else{
  1492. return arr[i];
  1493. }
  1494. }
  1495. }
  1496. return undefined;
  1497. }
  1498. async function fetchAndProcessTwitterApi(method,id = undefined,forceFetch = false){
  1499. return new Promise(async (resolve, reject) => {
  1500. try{
  1501. switch(method){
  1502. case 'TL':
  1503. case 'forYou':
  1504. await getTL();
  1505. break;
  1506. case 'graphQL':
  1507. if(fetchedTweets[id] && !(forceFetch === true))return resolve(fetchedTweets[id]);
  1508. await graphQL();
  1509. break;
  1510. case 'user':
  1511. await getUser();
  1512. break;
  1513. case 'user_1_1':
  1514. await getUser1_1();
  1515. break;
  1516. case '1_1':
  1517. await get1_1();
  1518. break;
  1519. default:
  1520. console.warn("なにか間違ってないか?")
  1521. return reject("something wrong.");
  1522. }
  1523. return resolve("OK!");
  1524. }catch(error){
  1525. console.error(error);
  1526. throw new Error(`Failed to fetch API data.\nmethod: ${method}\nid: ${id}`);
  1527. }
  1528. });
  1529. async function getTL(){
  1530. let requestObject;
  1531. if(method == 'TL'){
  1532. requestObject = new requestObject_twitter_time_line();
  1533. }else{
  1534. requestObject = new requestObject_twitter_time_line_forYou();
  1535. }
  1536. const response = await request(requestObject);
  1537. if(!response.status === "200")throw new Error(`Failed to fetch`);
  1538. processgraphQL(response.response.data.home.home_timeline_urt.instructions[0].entries);
  1539. }
  1540. async function graphQL(){
  1541. const response = await request(new requestObject_twitter_api_graphql(id));
  1542. if(!response.status === "200")throw new Error(`Failed to fetch`);
  1543. processgraphQL(response.response.data.threaded_conversation_with_injections_v2.instructions[0].entries);
  1544. }
  1545. async function getUser(){
  1546. if(fetchedTweetsUserDataByUserName[id]?.API_type === "graphQL")return;
  1547. const response = await request(new requestObject_twitter_get_user_by_screenname(id));
  1548. if(!response.status === "200")throw new Error(`Failed to fetch`);
  1549. const userData = response.response.data.user.result;
  1550. fetchedTweetsUserData[userData.rest_id] = {...userData,"API_type": "graphQL"};
  1551. fetchedTweetsUserDataByUserName[userData.legacy.screen_name] = fetchedTweetsUserData[userData.rest_id];
  1552. }
  1553. async function getUser1_1(){
  1554. if(fetchedTweetsUserDataByUserName[id])return;
  1555. const response = await request(new requestObject_twitter_get_user_by_screenname_1_1(id));
  1556. if(!response.status === "200")throw new Error(`Failed to fetch`);
  1557. if(response.response.status && !(fetchedTweets[response.response.status?.id_str]?.API_type === "graphQL")){
  1558. fetchedTweets[response.response.status.id_str] = {...response.response.status,"API_type": "1_1"};
  1559. response.response.status = fetchedTweets[response.response.status.id_str];
  1560. }
  1561. fetchedTweetsUserData[response.response.id_str] = {...response.response,"API_type": "1_1"};
  1562. fetchedTweetsUserDataByUserName[response.response.screen_name] = fetchedTweetsUserData[response.response.id_str];
  1563. }
  1564. async function get1_1(){
  1565. const idsToFetch = forceFetch ? id : id.filter(singleId => !(fetchedTweets[singleId]?.API_type === "graphQL"));
  1566. if(idsToFetch.length === 0) return;
  1567. const response = await request(new requestObject_twitter_api_v1_1(idsToFetch.join(",")));
  1568. if(!response.status === "200")throw new Error(`Failed to fetch`);
  1569. response.response.forEach((tweetData)=>{
  1570. if(tweetData.quoted_status){
  1571. let quoted = tweetData.quoted_status;
  1572. if(!(fetchedTweetsUserData[quoted.user.id_str]?.API_type === "graphQL")){
  1573. fetchedTweetsUserData[quoted.user.id_str] = {...quoted.user,"API_type": "1_1"};
  1574. fetchedTweetsUserDataByUserName[quoted.user.screen_name] = fetchedTweetsUserData[quoted.user.id_str];
  1575. }
  1576. quoted.user = fetchedTweetsUserData[quoted.user.id_str];
  1577. if(!(fetchedTweets[quoted.id_str]?.API_type === "graphQL")){
  1578. fetchedTweets[quoted.id_str] = {...quoted,"API_type": "1_1"};
  1579. }
  1580. tweetData.quoted_status = fetchedTweets[quoted.id_str];
  1581. }
  1582. if(!(fetchedTweetsUserData[tweetData.user.id_str]?.API_type === "graphQL")){
  1583. fetchedTweetsUserData[tweetData.user.id_str] = {...tweetData.user,"API_type": "1_1"};
  1584. fetchedTweetsUserDataByUserName[tweetData.user.screen_name] = fetchedTweetsUserData[tweetData.user.id_str];
  1585. }
  1586. tweetData.user = fetchedTweetsUserData[tweetData.user.id_str];
  1587. fetchedTweets[tweetData.id_str] = {...tweetData,"API_type": "1_1"};
  1588. });
  1589. }
  1590. function processgraphQL(entries){
  1591. if(!entries)return null;
  1592. entries.forEach(entry=>{
  1593. let tweetData = entry.content?.itemContent?.tweet_results?.result?.tweet || entry.content?.itemContent?.tweet_results?.result;
  1594. if(!tweetData)return;
  1595. if(tweetData.quoted_status_result){
  1596. let quoted = tweetData.quoted_status_result.result;
  1597. fetchedTweetsUserData[quoted.core.user_results.result.rest_id] = {...quoted.core.user_results.result,"API_type": "graphQL"};
  1598. fetchedTweetsUserDataByUserName[quoted.core.user_results.result.legacy.screen_name] = fetchedTweetsUserData[quoted.core.user_results.result.rest_id];
  1599. quoted.core.user_results.result = fetchedTweetsUserData[quoted.core.user_results.result.rest_id];
  1600. fetchedTweets[quoted.rest_id] = {...quoted,"API_type": "graphQL"};
  1601. tweetData.quoted_status_result.result = fetchedTweets[quoted.rest_id];
  1602. }
  1603. fetchedTweetsUserData[tweetData.core.user_results.result.rest_id] = {...tweetData.core.user_results.result,"API_type": "graphQL"};
  1604. fetchedTweetsUserDataByUserName[tweetData.core.user_results.result.legacy.screen_name] = fetchedTweetsUserData[tweetData.core.user_results.result.rest_id];
  1605. tweetData.core.user_results.result = fetchedTweetsUserData[tweetData.core.user_results.result.rest_id];
  1606. fetchedTweets[tweetData.rest_id] = {...tweetData,"API_type": "graphQL"};
  1607. });
  1608. }
  1609. }
  1610. async function getTweetData(id, method = '1_1', forceFetch = false){
  1611. const dataStore = method === 'user' || method === 'user_1_1' ? fetchedTweetsUserDataByUserName : fetchedTweets;
  1612. let ids;
  1613. if(typeof id === 'string'){
  1614. if(dataStore[id] && !forceFetch) return dataStore[id];
  1615. ids = [id];
  1616. }else if(Array.isArray(id)){
  1617. if(!forceFetch && id.every(singleId => dataStore[singleId])) return id.map(singleId => dataStore[singleId]);
  1618. ids = id;
  1619. }else{
  1620. throw new Error("Invalid ID type.");
  1621. }
  1622. if(method == "1_1") ids = [ids];
  1623. let promises = ids.map(singleId => fetchAndProcessTwitterApi(method, singleId, forceFetch));
  1624. await Promise.all(promises);
  1625. if(typeof id === 'string'){
  1626. if(dataStore[id]){
  1627. return dataStore[id];
  1628. }else{
  1629. throw new Error("Failed to fetch tweet data for ID: " + id);
  1630. }
  1631. }else if(Array.isArray(id)){
  1632. if(id.every(singleId => dataStore[singleId])){
  1633. return id.map(singleId => dataStore[singleId]);
  1634. }else{
  1635. throw new Error("Failed to fetch tweet data for some IDs.");
  1636. }
  1637. }
  1638. }
  1639. async function waitForTweetData(id, method = "1_1", retry = 30, span = 100){
  1640. let dataStore = method === 'user' || method === 'user_1_1' ? fetchedTweetsUserDataByUserName : fetchedTweets;
  1641. if(typeof id === 'string'){
  1642. if(dataStore[id]?.API_type === method)return dataStore[id];
  1643. id = [id];
  1644. }else if(Array.isArray(id)){
  1645. if(id.every(singleId => dataStore[singleId]?.API_type === method))return id.map(singleId => dataStore[singleId]);
  1646. }else{
  1647. throw new Error(`Invalid ID type: ${typeof id}. Expected string or array.`);
  1648. }
  1649. return new Promise((resolve, reject) => {
  1650. const MAX_RETRY_COUNT = retry;
  1651. let retry_counter = 0;
  1652. let set_interval_id = setInterval(isThereTweetData, span);
  1653. async function isThereTweetData(){
  1654. retry_counter++;
  1655. if(dataStore[id]){
  1656. clearInterval(set_interval_id);
  1657. return resolve(dataStore[id]);
  1658. }
  1659. if(retry_counter > MAX_RETRY_COUNT){
  1660. await getTweetData(id, method);
  1661. if(typeof id === 'string'){
  1662. if(dataStore[id]){
  1663. return resolve(dataStore[id]);
  1664. }else{
  1665. return reject("Max retry count reached");
  1666. }
  1667. }else if(Array.isArray(id)){
  1668. if(id.every(singleId => dataStore[singleId])){
  1669. return resolve(id.map(singleId => dataStore[singleId]));
  1670. }else{
  1671. return reject("Max retry count reached");
  1672. }
  1673. }
  1674. }
  1675. }
  1676. });
  1677. }
  1678. async function getFileSize(url){
  1679. return (await request(new requestObject_binary_head(url))).responseHeaders.match(/content-length: ?(\d+)/)[1];
  1680. }
  1681. function decodeHtml(html) {
  1682. var txt = document.createElement("textarea");
  1683. txt.innerHTML = html;
  1684. return txt.value;
  1685. }
  1686. function encodeBase64(data){
  1687. return btoa(data);
  1688. }
  1689. function decodeBase64(encodedData){
  1690. return atob(encodedData);
  1691. }
  1692. function openSettings(){
  1693. let container = document.createElement('div');
  1694. container.style.position = 'fixed';
  1695. container.style.width = '70vw';
  1696. container.style.height = '80vh';
  1697. container.style.backgroundColor = 'rgba(220, 200, 200, 0.9)';
  1698. container.style.zIndex = '9999';
  1699. container.style.display = 'flex';
  1700. container.style.maxHeight = '80vh';
  1701. container.style.top = '50%'; // 画面の上から50%の位置
  1702. container.style.left = '50%'; // 画面の左から50%の位置
  1703. container.style.transform = 'translate(-50%, -50%)'; // 要素の中心を基準に位置を調整
  1704. container.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
  1705. container.style.flexDirection = 'column';
  1706. container.className = `settingWindowContainer`
  1707. document.body.appendChild(container);
  1708. let pages = {};
  1709. let nav = document.createElement('div');
  1710. nav.style.position = 'static';
  1711. nav.style.top = '0';
  1712. nav.style.left = '0';
  1713. nav.style.width = '100%';
  1714. nav.style.zIndex = '2';
  1715. nav.style.display = 'flex';
  1716. nav.style.flexWrap = 'wrap';
  1717. container.appendChild(nav);
  1718. let contentWrapper = document.createElement('div');
  1719. contentWrapper.style.flexGrow = '1';
  1720. contentWrapper.style.position = 'relative';
  1721. contentWrapper.style.flexDirection = 'column';
  1722. contentWrapper.style.display = 'flex';
  1723. contentWrapper.style.overflowY = 'auto';
  1724. container.appendChild(contentWrapper);
  1725. let fotter = document.createElement('div');
  1726. fotter.style.position = 'static';
  1727. fotter.style.width = '100%';
  1728. fotter.style.zIndex = '2';
  1729. fotter.style.display = 'flex';
  1730. fotter.style.flexWrap = 'wrap';
  1731. fotter.style.left = '1em';
  1732. fotter.style.bottom = '1em';
  1733. container.appendChild(fotter);
  1734. let saveAllButton = document.createElement('button');
  1735. saveAllButton.textContent = 'Save All';
  1736. saveAllButton.addEventListener('click', saveAll);
  1737. fotter.appendChild(saveAllButton);
  1738. let cancelButton = document.createElement('button');
  1739. cancelButton.textContent = 'キャンセル';
  1740. cancelButton.addEventListener('click', () => {
  1741. container.remove();
  1742. document.head.querySelectorAll('style.settingCss').forEach(s=>{
  1743. s.remove();
  1744. });
  1745. });
  1746. fotter.appendChild(cancelButton);
  1747. /////////////////////////////////////////////
  1748. /*
  1749. function createPageTemplate(){
  1750. const pageName = "hogehoge";
  1751. const textData = env_Text[pageName];
  1752. const thisStoredSettings = storedSettings[pageName];
  1753. let thisScriptSettings = script_settings[pageName];
  1754. let contents = createPage(pageName,pageName,textData.functionName);
  1755. let mainContent = contents.main;
  1756. mainContent.innerHTML = `
  1757. <div class="${pageName}MainContents">
  1758. <span class="item_name">hogehoge</span>
  1759. </div>
  1760. `;
  1761. let style = document.createElement('style');
  1762. style.className = `settingCss ${pageName}`
  1763. style.textContent = `
  1764.  
  1765. `;
  1766. document.head.appendChild(style);
  1767. pages[pageName].saveFunction = save;
  1768. function save(){
  1769. console.log(pageName)
  1770. }
  1771. }
  1772. */
  1773. function creategGeneralPage(){
  1774. const pageName = "general"
  1775. let thisScript_settings = script_settings.Make_Twitter_little_useful;
  1776. let contents = createPage(pageName,env_Text.general,env_Text.general);
  1777. let mainContent = contents.main;
  1778. let header = contents.header;
  1779. const features = [
  1780. {id: 'Note_Tweet_expander', name: env_Text.Note_Tweet_expander.functionName},
  1781. {id: 'Engagement_Restorer', name: env_Text.Engagement_Restorer.functionName},
  1782. {id: 'Hello_tweet_where_are_you_from', name: env_Text.Hello_tweet_where_are_you_from.functionName},
  1783. {id: 'Show_me_your_Pixiv', name: env_Text.Show_me_your_Pixiv.functionName},
  1784. {id: 'sneakilyFavorite', name: env_Text.sneakilyFavorite.functionName},
  1785. {id: 'showFollowers', name: env_Text.showFollowers.functionName},
  1786. {id: 'hideAnalytics', name: env_Text.hideAnalytics.functionName},
  1787. {id: 'webhook_brings_tweets_to_discord', name: env_Text.webhook_brings_tweets_to_discord.functionName},
  1788. {id: 'shareTweet_Restorer_for_mobile', name: env_Text.shareTweet_Restorer_for_mobile.functionName},
  1789. ];
  1790. let featureListContent = features.map(feature => {
  1791. return `
  1792. <div class="featureItem">
  1793. <label class="switch">
  1794. <input type="checkbox" id="${feature.id}" class="featureToggle" ${thisScript_settings.featuresToggle[feature.id] === true ? 'checked' : ''}>
  1795. <span class="slider round"></span>
  1796. </label>
  1797. <span>${feature.name}</span>
  1798. </div>
  1799. `;
  1800. }).join('');
  1801. mainContent.innerHTML = `
  1802. <div class="${pageName}MainContents">
  1803. <span class="item_name">${env_Text.featureToggle}</span>
  1804. <div class="featureList">
  1805. ${featureListContent}
  1806. </div>
  1807. <span class="item_name">Language</span>
  1808. <select id="languageSelect">
  1809. ${Object.keys(Text).map(lang_ => `<option value="${lang_}" ${lang_ === thisScript_settings.lang ? 'selected' : ''}>${lang_}</option>`).join('')}
  1810. </select>
  1811. </div>
  1812. `;
  1813. let style = document.createElement('style');
  1814. style.className = `settingCss ${pageName}`
  1815. style.textContent = `
  1816. .switch {
  1817. position: relative;
  1818. display: inline-block;
  1819. width: 2.58em;
  1820. height: 1em;
  1821. }
  1822. .switch input {
  1823. opacity: 0;
  1824. width: 0;
  1825. height: 0;
  1826. }
  1827. /* スライダーのスタイル */
  1828. .slider {
  1829. position: absolute;
  1830. cursor: pointer;
  1831. top: 0;
  1832. left: 0;
  1833. right: 0;
  1834. bottom: 0;
  1835. background-color: #ccc;
  1836. transition: .4s;
  1837. border-radius: 1em; /* 高さに合わせてborder-radiusを変更 */
  1838. }
  1839. .slider:before {
  1840. position: absolute;
  1841. content: "";
  1842. height: 0.8em; /* 高さを0.8emに変更 */
  1843. width: 0.8em; /* 幅を0.8emに変更 */
  1844. left: 0.1em; /* 位置を調整 */
  1845. bottom: 0.1em; /* 位置を調整 */
  1846. background-color: white;
  1847. transition: .4s;
  1848. border-radius: 50%;
  1849. }
  1850. input:checked + .slider {
  1851. background-color: #2196F3;
  1852. }
  1853. input:checked + .slider:before {
  1854. transform: translateX(1.6em); /* スライダーの移動距離を調整 */
  1855. }
  1856. `;
  1857. document.head.appendChild(style);
  1858. pages[pageName].saveFunction = save;
  1859. function save(){
  1860. features.forEach(feature => {
  1861. const checkbox = document.getElementById(feature.id);
  1862. thisScript_settings.featuresToggle[feature.id] = checkbox.checked;
  1863. });
  1864. thisScript_settings.lang = document.getElementById('languageSelect').value;
  1865. localStorage.setItem('Make_Twitter_little_useful', JSON.stringify(thisScript_settings));
  1866. storedSettings[pageName] = JSON.parse(localStorage.getItem('webhook_brings_tweets_to_discord') || '{}');
  1867. env_Text = Text[thisScript_settings.lang];
  1868. }
  1869. }
  1870. function createAdvancedPage(){
  1871. const pageName = "Advanced";
  1872. const textData = env_Text[pageName];
  1873. //const thisStoredSettings = storedSettings[pageName];
  1874. //let thisScriptSettings = script_settings[pageName];
  1875. let contents = createPage(pageName);
  1876. contents.header.innerHTML = `<span style="font-size: 2em; font-weight: 700">${textData.functionName}</span>`;
  1877. contents.footer.innerHTML = "";
  1878. let mainContent = contents.main;
  1879. mainContent.innerHTML = `
  1880. <div class="${pageName}MainContents">
  1881. <span class="item_name">${textData.ExportSettings}</span>
  1882. <input type="button" id="exportButton" style="display: none;">
  1883. <label for="exportButton" class="customFileIO"">${textData.Export}</label><br>
  1884. <span class="item_name">${textData.ImportSettings}</span>
  1885. <input type="file" id="importFile" accept=".json" style="display: none;">
  1886. <label for="importFile" class="customFileIO">${textData.Inport}</label><br>
  1887. </div>
  1888. `;
  1889. let style = document.createElement('style');
  1890. style.className = `settingCss ${pageName}`
  1891. style.textContent = `
  1892. .customFileIO {
  1893. display: inline-block;
  1894. padding: 0.5em 1em;
  1895. background-color: rgb(30, 39, 50);
  1896. color: white;
  1897. cursor: pointer;
  1898. border-radius: 5px;
  1899. height: 1em;
  1900. line-height: 1em;
  1901. }
  1902. `;
  1903. document.head.appendChild(style);
  1904. const exportSettings = () => {
  1905. const data = {
  1906. Make_Twitter_little_useful_script_SettingsData: storedSettings,
  1907. };
  1908. const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data));
  1909. const downloadAnchorNode = document.createElement('a');
  1910. downloadAnchorNode.setAttribute("href", dataStr);
  1911. downloadAnchorNode.setAttribute("download", "scriptSettings.json");
  1912. document.body.appendChild(downloadAnchorNode);
  1913. downloadAnchorNode.click();
  1914. downloadAnchorNode.remove();
  1915. };
  1916. const importSettings = (event) => {
  1917. const file = event.target.files[0];
  1918. if(file){
  1919. const reader = new FileReader();
  1920. reader.onload = function(e) {
  1921. try{
  1922. const importedData = JSON.parse(e.target.result);
  1923. if(!importedData || !importedData.Make_Twitter_little_useful_script_SettingsData){
  1924. throw new Error("Invalid data format");
  1925. }
  1926. const importedSettings = importedData.Make_Twitter_little_useful_script_SettingsData;
  1927. for(let key in importedSettings){
  1928. localStorage.setItem(key, JSON.stringify(importedSettings[key]));
  1929. }
  1930. location.reload();
  1931. }catch(error){
  1932. console.error(error);
  1933. customAlert(textData.invalidJson);
  1934. }
  1935. };
  1936. reader.readAsText(file);
  1937. }
  1938. };
  1939. document.getElementById("exportButton").addEventListener("click", exportSettings);
  1940. document.getElementById("importFile").addEventListener("change", importSettings);
  1941. pages[pageName].saveFunction = save;
  1942. function save(){
  1943. }
  1944. }
  1945. function createStylePage(){
  1946. const pageName = "style";
  1947. const textData = env_Text[pageName];
  1948. const thisStoredSettings = storedSettings[pageName];
  1949. let thisScriptSettings = script_settings[pageName];
  1950. let contents = createPage(pageName,pageName,textData.functionName);
  1951. let mainContent = contents.main;
  1952. mainContent.innerHTML = `
  1953. <div class="${pageName}MainContents">
  1954. <span class="item_name">hogehoge</span>
  1955. </div>
  1956. `;
  1957. document.head.querySelectorAll(`style.settingCss.${pageName}`).forEach(e=>e.remove());
  1958. let style = document.createElement('style');
  1959. style.className = `settingCss ${pageName}`
  1960. style.textContent = `
  1961.  
  1962. `;
  1963. document.head.appendChild(style);
  1964. pages[pageName].saveFunction = save;
  1965. function save(){
  1966. console.log(pageName)
  1967. }
  1968. }
  1969. function createWebhook_brings_tweets_to_discordPage(){
  1970. const pageName = "webhook_brings_tweets_to_discord"
  1971. const textData = env_Text.webhook_brings_tweets_to_discord;
  1972. let contents = createPage(pageName,"webhook",textData.functionName);
  1973. let mainContent = contents.main;
  1974. const thisStoredSettings = storedSettings[pageName];
  1975. let thisScriptSettings = script_settings[pageName];
  1976. mainContent.innerHTML = `
  1977. <div class="${pageName}MainContents webhook-container" style="">
  1978. <span class="item_name">Webhooks</span>
  1979. <div id="webhooks" style="">
  1980. <!-- ここにWebhookの設定行が動的に追加されます -->
  1981. </div>
  1982. <div class="addWebhookButton">
  1983. <button id="addWebhook">+</button>
  1984. </div>
  1985. <div class="defaultWebhook">
  1986. <span class="item_name">${textData.default}Webhook</span><br>
  1987. <select id="defaultWebhook"></select>
  1988. </div>
  1989. <div class="displayMethod">
  1990. <span class="item_name">${textData.display_method}</span><br>
  1991. <select id="displayMethod">
  1992. <option value="method1">${textData.display_everywhere}</option>
  1993. <option value="method2">${textData.tweet_details_only}</option>
  1994. </select>
  1995. </div>
  1996. <div class="sendLangage">
  1997. <span class="item_name">${textData.language}</span><br>
  1998. <select id="sendLanguageSelect">
  1999. ${Object.keys(Text).map(lang_ => `<option value="${lang_}" ${lang_ === script_settings.webhook_brings_tweets_to_discord.lang ? 'selected' : ''}>${lang_}</option>`).join('')}
  2000. </select>
  2001. </div>
  2002. <div class="downloadVideo">
  2003. <span class="item_name">${textData.Video_Download_Option}</span><br>
  2004. <input type="radio" id="downloadYes" name="downloadOption" value="true" ${thisStoredSettings.downloadVideo === true ? 'checked' : ''}>
  2005. <label for="downloadYes">Yes</label>
  2006. <input type="radio" id="downloadNo" name="downloadOption" value="false" ${thisStoredSettings.downloadVideo === false ? 'checked' : ''}>
  2007. <label for="downloadNo">No</label>
  2008. </div>
  2009. <br>
  2010. </div>
  2011. `;
  2012. let style = document.createElement('style');
  2013. style.className = `settingCss ${pageName}`
  2014. style.textContent = `
  2015. .webhook-container {
  2016. display: flex;
  2017. flex-direction: column;
  2018. }
  2019. .webhook-row {
  2020. display: flex;
  2021. align-items: center; /* 子要素を中央揃えにする */
  2022. flex-wrap: wrap; /* 必要に応じて折り返す */
  2023. }
  2024. .webhook-name,.webhook-url{
  2025. margin-right: 5px; /* 余白を追加 */
  2026. }
  2027. `;
  2028. document.head.appendChild(style);
  2029. document.getElementById('addWebhook').addEventListener('click', () => {
  2030. addWebhookRow();
  2031. });
  2032. function addWebhookRow(name = '', url = ''){
  2033. let row = document.createElement('div');
  2034. row.className = 'webhook-row';
  2035. row.innerHTML = `
  2036. <div style="display:flex;border-bottom: 1px solid;flex-wrap: wrap;">
  2037. <div class="WebhookName">
  2038. <label>Name:</label>
  2039. <input type="text" class="webhook-name" value="${name}">
  2040. </div>
  2041. <div class=">WebhookUrl">
  2042. <label>URL:</label>
  2043. <input type="text" class="webhook-url" value="${url}">
  2044. </div>
  2045. <div class="removeWebhookButton">
  2046. <button class="removeWebhook">X</button>
  2047. </div>
  2048. </div>
  2049. `;
  2050. row.querySelector('.removeWebhook').addEventListener('click', () => {
  2051. row.remove();
  2052. updateDefaultWebhookOptions();
  2053. });
  2054. document.getElementById('webhooks').appendChild(row);
  2055. }
  2056. //console.log(thisStoredSettings.data)
  2057. function updateDefaultWebhookOptions(){
  2058. let select = document.getElementById('defaultWebhook');
  2059. select.innerHTML = '';
  2060. let webhookElements = document.getElementById('webhooks').children;
  2061. for(let elem of webhookElements){
  2062. let name = elem.querySelector('.webhook-name').value;
  2063. if(name){
  2064. let option = document.createElement('option');
  2065. option.value = name;
  2066. option.textContent = name;
  2067. select.appendChild(option);
  2068. }
  2069. }
  2070. }
  2071. for(let item of thisStoredSettings.data){
  2072. let decodedUrl = 'https://discord.com/api/webhooks/' + decodeBase64(item.value);
  2073. addWebhookRow(item.name, decodedUrl);
  2074. let option = document.createElement('option');
  2075. option.value = item.name;
  2076. option.textContent = item.name;
  2077. document.getElementById('defaultWebhook').appendChild(option);
  2078. }
  2079. pages[pageName].saveFunction = save;
  2080. function save(){
  2081. let data = [];
  2082. let names = [];
  2083. let hasDuplicate = false;
  2084. let hasInvalidWebhook = false;
  2085. let webhookElements = document.getElementById('webhooks').children;
  2086.  
  2087. for(let elem of webhookElements){
  2088. let name = elem.querySelector('.webhook-name').value;
  2089. let url = elem.querySelector('.webhook-url').value;
  2090. if(name && url){
  2091. if(!/^https:\/\/discord\.com\/api\/webhooks\/[\w-]+\/[\w-]+$/.test(url)){
  2092. elem.querySelector('.webhook-url').style.backgroundColor = 'red';
  2093. hasInvalidWebhook = true; // 無効なWebhookを検出
  2094. continue;
  2095. }else{
  2096. elem.querySelector('.webhook-url').style.backgroundColor = '';
  2097. }
  2098. // URLをBase64エンコード
  2099. let encodedUrl = encodeBase64(url.replace('https://discord.com/api/webhooks/', ''));
  2100. if(names.includes(name)){
  2101. hasDuplicate = true;
  2102. elem.querySelector('.webhook-name').style.backgroundColor = 'red';
  2103. }else{
  2104. names.push(name);
  2105. elem.querySelector('.webhook-name').style.backgroundColor = '';
  2106. data.push({ name, value: encodedUrl });
  2107. }
  2108. }
  2109. }
  2110. if(hasDuplicate){
  2111. customAlert(textData.when_webhook_name_duplicate);
  2112. return;
  2113. }
  2114. if(hasInvalidWebhook){
  2115. customAlert(textData.when_webhook_url_invalid);
  2116. return;
  2117. }
  2118. // 設定をJSON形式で保存
  2119. let selectedLanguage = document.getElementById('sendLanguageSelect').value;
  2120. let settings = {
  2121. data: data,
  2122. downloadVideo: getDownloadOption(),
  2123. defaultWebhook: document.getElementById('defaultWebhook').value,
  2124. displayMethod: document.getElementById('displayMethod').value,
  2125. lang: selectedLanguage
  2126. };
  2127. localStorage.setItem('webhook_brings_tweets_to_discord', JSON.stringify(settings));
  2128. // 保存した設定を再度読み込む
  2129. storedSettings[pageName] = JSON.parse(localStorage.getItem('webhook_brings_tweets_to_discord') || '{}');
  2130.  
  2131. script_settings[pageName] = {
  2132. "displayMethod": storedSettings[pageName].displayMethod || 'method1',
  2133. "lang": storedSettings[pageName].lang || GetCookie("lang") || 'en',
  2134. "defaultWebhook": storedSettings[pageName].defaultWebhook,
  2135. "webHooks": (function(data){
  2136. let webhooks = {};
  2137. if(data && Array.isArray(data)){
  2138. data.forEach(item => {
  2139. if(item.name && item.value){
  2140. webhooks[item.name] = item.value;
  2141. }
  2142. });
  2143. }
  2144. return webhooks;
  2145. })(storedSettings[pageName].data)
  2146. };
  2147. updateDefaultWebhookOptions();
  2148. document.querySelectorAll('.quickDimg').forEach((target)=>{target.remove()});
  2149. main(true);
  2150. function getDownloadOption(){
  2151. const radioButtons = document.querySelectorAll('.downloadVideo input[name="downloadOption"]');
  2152. for(let i = 0; i < radioButtons.length; i++){
  2153. if(radioButtons[i].checked){
  2154. return radioButtons[i].value === "true";
  2155. }
  2156. }
  2157. return null;
  2158. }
  2159. };
  2160. }
  2161.  
  2162. function createPage(name,buttonText = undefined,headerText = undefined){
  2163. let page = document.createElement('div');
  2164. page.style.position = 'relative';
  2165. page.style.display = 'flex';
  2166. page.style.flexDirection = 'column';
  2167. page.style.flexGrow = '0';
  2168. page.style.flexShrink = '1';
  2169. page.style.top = '0';
  2170. page.style.left = '0';
  2171. page.style.width = 'auto';
  2172. page.style.height = 'auto';
  2173. page.style.zIndex = '-1';
  2174. page.style.overflowY = 'auto';
  2175. page.style.border = '1px solid black';
  2176. page.style.padding = "0.5em";
  2177. page.className = `${name} settingChildWindow`;
  2178. page.setAttribute('settingName', name);
  2179. let header = document.createElement('div');
  2180. header.className = 'pageHeader';
  2181. header.innerHTML = `<span style="font-size: 2em; font-weight: 700">${headerText || name} ${env_Text.settings}</span>`;
  2182. header.style.color = 'black';
  2183. page.appendChild(header);
  2184. let contentContainer = document.createElement('div');
  2185. contentContainer.className = 'pageContent';
  2186. contentContainer.style.display = 'flex';
  2187. contentContainer.style.flexDirection = 'column';
  2188. contentContainer.style.overflowY = 'auto';
  2189. contentContainer.style.color = 'black';
  2190. page.appendChild(contentContainer);
  2191. let footer = document.createElement('div');
  2192. footer.className = 'pageFooter';
  2193. footer.style.marginTop = "1em";
  2194. let saveButton = document.createElement('button');
  2195. saveButton.textContent = 'Save';
  2196. saveButton.addEventListener('click', function(){
  2197. if(pages[name] && typeof pages[name].saveFunction === 'function'){
  2198. pages[name].saveFunction();
  2199. }
  2200. });
  2201. footer.appendChild(saveButton);
  2202. page.appendChild(footer);
  2203. pages[name] = {
  2204. "page": page,
  2205. "saveFunction": function(){}
  2206. };
  2207. contentWrapper.appendChild(page);
  2208. makeChangePageButton(name,buttonText)
  2209. return {"all": page,"header": header,"main": contentContainer,"footer": footer};
  2210. }
  2211. function showPage(name){
  2212. for(let key in pages){
  2213. if(key === name){
  2214. pages[key].page.style.zIndex = '1';
  2215. pages[key].page.style.display = 'flex';
  2216. }else{
  2217. pages[key].page.style.zIndex = '-1';
  2218. pages[key].page.style.display = 'none';
  2219. }
  2220. }
  2221. }
  2222. function makeChangePageButton(name,buttonText = undefined){
  2223. if(buttonText === undefined)buttonText = name;
  2224. let homeButton = document.createElement('button');
  2225. homeButton.textContent = buttonText;
  2226. homeButton.addEventListener('click', () => showPage(name));
  2227. nav.appendChild(homeButton);
  2228. }
  2229. function saveAll(){
  2230. for(let key in pages){
  2231. pages[key].saveFunction();
  2232. }
  2233. container.remove();
  2234. document.head.querySelectorAll('style.settingCss').forEach(s=>{
  2235. s.remove();
  2236. });
  2237. }
  2238. let style = document.createElement('style');
  2239. style.className = "settingCss"
  2240. style.textContent = `
  2241. .pageHeader, .pageFooter {
  2242. flex-shrink: 0;
  2243. }
  2244. .pageContent {
  2245. flex-grow: 1;
  2246. overflow-y: auto;
  2247. }
  2248. .item_name {
  2249. font-size: 1.5em;
  2250. }
  2251. `;
  2252. document.head.appendChild(style);
  2253. creategGeneralPage();
  2254. createWebhook_brings_tweets_to_discordPage();
  2255. createAdvancedPage();
  2256. showPage('general');
  2257. }
  2258. GM_registerMenuCommand('script settings', openSettings);
  2259. function customAlert(message, url){
  2260. let overlay = document.createElement('div');
  2261. overlay.style.position = 'fixed';
  2262. overlay.style.top = '0';
  2263. overlay.style.left = '0';
  2264. overlay.style.width = '100%';
  2265. overlay.style.height = '100%';
  2266. overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
  2267. overlay.style.zIndex = '10000';
  2268. let alertBox = document.createElement('div');
  2269. alertBox.style.position = 'absolute';
  2270. alertBox.style.top = '50%';
  2271. alertBox.style.left = '50%';
  2272. alertBox.style.transform = 'translate(-50%, -50%)';
  2273. alertBox.style.padding = '20px';
  2274. alertBox.style.backgroundColor = 'white';
  2275. alertBox.style.border = '1px solid black';
  2276. alertBox.style.zIndex = '10001';
  2277. alertBox.style.maxWidth = '80%'; // ボックスの最大幅を設定してテキストを折り返します
  2278. alertBox.style.whiteSpace = 'pre-wrap'; // 改行と空白を保持します
  2279.  
  2280. let alertMessage = document.createElement('p');
  2281. alertMessage.innerHTML = message.replace(/\n/g, '<br>'); // \nを<br>に置き換えて改行を表示します
  2282. alertMessage.style.color = 'black';
  2283.  
  2284. let closeButton = document.createElement('button');
  2285. closeButton.textContent = env_Text.close;
  2286. alertBox.addEventListener('click', (e) => {
  2287. e.stopPropagation(); // これにより、イベントがoverlayまで伝播しなくなります
  2288. });
  2289. overlay.addEventListener('click', () => {
  2290. document.body.removeChild(overlay);
  2291. });
  2292. closeButton.addEventListener('click', () => {
  2293. document.body.removeChild(overlay);
  2294. });
  2295.  
  2296. alertBox.appendChild(alertMessage);
  2297.  
  2298. // URLが提供された場合、それを表示するa要素を作成します
  2299. if(url){
  2300. let urlElement = document.createElement('a');
  2301. urlElement.href = url;
  2302. urlElement.textContent = url;
  2303. urlElement.style.display = 'block';
  2304. urlElement.style.marginTop = '10px';
  2305. urlElement.target = '_blank';
  2306. urlElement.rel = 'noopener';
  2307. alertBox.appendChild(urlElement);
  2308. }
  2309. alertBox.appendChild(closeButton);
  2310. overlay.appendChild(alertBox);
  2311. document.body.appendChild(overlay);
  2312. }
  2313. async function request(object, maxRetries = 0, timeout = 60000){
  2314. let retryCount = 0;
  2315. while(retryCount <= maxRetries){
  2316. try{
  2317. return await new Promise((resolve, reject) => {
  2318. GM_xmlhttpRequest({
  2319. method: object.method,
  2320. url: object.url,
  2321. headers: object.headers,
  2322. responseType: object.respType,
  2323. data: object.body,
  2324. anonymous: object.anonymous,
  2325. timeout: timeout,
  2326. onload: function(responseDetails){
  2327. return resolve(responseDetails);
  2328. },
  2329. ontimeout: function(responseDetails){
  2330. reject(`[request]time out:\nresponse ${responseDetails}`);
  2331. },
  2332. onerror: function(responseDetails){
  2333. reject(`[request]error:\nresponse ${responseDetails}`);
  2334. }
  2335. });
  2336. });
  2337. }catch(error){
  2338. retryCount++;
  2339. console.warn(`Retry ${retryCount}: Failed to fetch ${object.url}. Reason: ${error}`);
  2340. if(retryCount === maxRetries){
  2341. throw new Error(`Failed to fetch ${object.url} after ${maxRetries} retries.`);
  2342. }
  2343. }
  2344. }
  2345. }
  2346. class requestObject_twitter_api_v1_1{
  2347. constructor(ID,endpoints = "lookup"){
  2348. this.method = 'GET';
  2349. this.respType = 'json';
  2350. this.url = `https://api.twitter.com/1.1/statuses/${endpoints}.json?id=${ID}&tweet_mode=extended`;
  2351. this.body = null;
  2352. this.headers = {
  2353. "Content-Type": "application/json",
  2354. 'User-agent': userAgent,
  2355. 'accept': '*/*',
  2356. 'Accept-Encoding': 'br, gzip, deflate',
  2357. 'Referer': "https://twitter.com/",
  2358. 'Host': 'api.twitter.com',
  2359. 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAAIK1zgAAAAAA2tUWuhGZ2JceoId5GwYWU5GspY4%3DUq7gzFoCZs1QfwGoVdvSac3IniczZEYXIcDyumCauIXpcAPorE',
  2360. 'x-csrf-token': GetCookie("ct0"),
  2361. };
  2362. this.package = null;
  2363. this.anonymous = false;
  2364. }
  2365. }
  2366. class requestObject_twitter_api_graphql{
  2367. constructor(ID){
  2368. this.method = 'GET';
  2369. this.respType = 'json';
  2370. this.url = `https://twitter.com/i/api/graphql/TuC3CinYecrqAyqccUyFhw/TweetDetail?variables=%7B%22focalTweetId%22%3A%22${ID}%22%2C%22referrer%22%3A%22home%22%2C%22with_rux_injections%22%3Afalse%2C%22includePromotedContent%22%3Atrue%2C%22withCommunity%22%3Atrue%2C%22withQuickPromoteEligibilityTweetFields%22%3Atrue%2C%22withArticleRichContent%22%3Atrue%2C%22withBirdwatchNotes%22%3Atrue%2C%22withVoice%22%3Atrue%2C%22withV2Timeline%22%3Atrue%7D&features=%7B%22rweb_lists_timeline_redesign_enabled%22%3Atrue%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22tweetypie_unmention_optimization_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22view_counts_everywhere_api_enabled%22%3Atrue%2C%22longform_notetweets_consumption_enabled%22%3Atrue%2C%22responsive_web_twitter_article_tweet_consumption_enabled%22%3Atrue%2C%22tweet_awards_web_tipping_enabled%22%3Afalse%2C%22freedom_of_speech_not_reach_fetch_enabled%22%3Atrue%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Atrue%2C%22longform_notetweets_rich_text_read_enabled%22%3Atrue%2C%22longform_notetweets_inline_media_enabled%22%3Atrue%2C%22responsive_web_media_download_video_enabled%22%3Atrue%2C%22responsive_web_enhance_cards_enabled%22%3Afalse%7D&fieldToggles=%7B%22withArticleRichContentState%22%3Atrue%7D`;
  2371. this.body = null;
  2372. this.headers = {
  2373. "Content-Type": "application/json",
  2374. 'User-agent': userAgent,
  2375. 'accept': '*/*',
  2376. 'Accept-Encoding': 'br, gzip, deflate',
  2377. 'authorization': `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`,
  2378. 'x-csrf-token': GetCookie("ct0"),
  2379. };
  2380. this.package = null;
  2381. this.anonymous = false;
  2382. }
  2383. }
  2384. class requestObject_twitter_get_user_by_screenname{
  2385. constructor(ID){
  2386. this.method = 'GET';
  2387. this.respType = 'json';
  2388. this.url = `https://api.twitter.com/graphql/rePnxwe9LZ51nQ7Sn_xN_A/UserByScreenName?variables=%7B%22screen_name%22%3A%22${ID}%22%2C%22withSafetyModeUserFields%22%3Afalse%2C%22withSuperFollowsUserFields%22%3Afalse%7D&features=%7B%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Afalse%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Afalse%2C%22verified_phone_label_enabled%22%3Afalse%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Afalse%7D`;
  2389. this.body = null;
  2390. this.headers = {
  2391. "Content-Type": "application/json",
  2392. 'User-agent': userAgent,
  2393. 'accept': '*/*',
  2394. 'Referer': "https://twitter.com/",
  2395. 'Host': 'api.twitter.com',
  2396. 'Accept-Encoding': 'br, gzip, deflate',
  2397. 'authorization': `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`,
  2398. 'x-csrf-token': GetCookie("ct0"),
  2399. };
  2400. this.package = null;
  2401. this.anonymous = false;
  2402. this.screen_name = ID;
  2403. }
  2404. }
  2405. class requestObject_twitter_get_user_by_screenname_1_1{
  2406. constructor(ID) {
  2407. this.method = 'GET';
  2408. this.respType = 'json';
  2409. this.url = `https://api.twitter.com/1.1/users/show.json?screen_name=${ID}`;
  2410. this.body = null;
  2411. this.headers = {
  2412. "Content-Type": "application/json",
  2413. 'User-agent': userAgent,
  2414. 'accept': '*/*',
  2415. 'Referer': "https://twitter.com/",
  2416. 'Host': 'api.twitter.com',
  2417. 'Accept-Encoding': 'br, gzip, deflate',
  2418. 'authorization': `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`,
  2419. 'x-csrf-token': GetCookie("ct0"),
  2420. };
  2421. this.package = null;
  2422. this.anonymous = false;
  2423. this.screen_name = ID;
  2424. }
  2425. }
  2426. class requestObject_twitter_time_line{
  2427. constructor(){
  2428. this.method = 'POST';
  2429. this.respType = 'json';
  2430. this.url = `https://twitter.com/i/api/graphql/HnVOsy-Rh_0Cuq06_z9lGA/HomeLatestTimeline`;
  2431. this.body = JSON.stringify({
  2432. "variables": {
  2433. "count": 100,
  2434. "includePromotedContent": false,
  2435. "latestControlAvailable": true,
  2436. "requestContext": "launch"
  2437. },
  2438. "features": {
  2439. "responsive_web_graphql_exclude_directive_enabled": true,
  2440. "verified_phone_label_enabled": false,
  2441. "responsive_web_home_pinned_timelines_enabled": true,
  2442. "creator_subscriptions_tweet_preview_api_enabled": true,
  2443. "responsive_web_graphql_timeline_navigation_enabled": true,
  2444. "responsive_web_graphql_skip_user_profile_image_extensions_enabled": false,
  2445. "tweetypie_unmention_optimization_enabled": true,
  2446. "responsive_web_edit_tweet_api_enabled": true,
  2447. "graphql_is_translatable_rweb_tweet_is_translatable_enabled": true,
  2448. "view_counts_everywhere_api_enabled": true,
  2449. "longform_notetweets_consumption_enabled": true,
  2450. "responsive_web_twitter_article_tweet_consumption_enabled": false,
  2451. "tweet_awards_web_tipping_enabled": false,
  2452. "freedom_of_speech_not_reach_fetch_enabled": true,
  2453. "standardized_nudges_misinfo": true,
  2454. "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": true,
  2455. "longform_notetweets_rich_text_read_enabled": true,
  2456. "longform_notetweets_inline_media_enabled": true,
  2457. "responsive_web_media_download_video_enabled": true,
  2458. "responsive_web_enhance_cards_enabled": false
  2459. },
  2460. "queryId": "HnVOsy-Rh_0Cuq06_z9lGA"
  2461. });
  2462. this.headers = {
  2463. "Content-Type": "application/json",
  2464. 'User-agent': userAgent,
  2465. 'accept': '*/*',
  2466. 'Referer': "https://twitter.com/",
  2467. 'Host': 'twitter.com',
  2468. 'Accept-Encoding': 'br, gzip, deflate',
  2469. 'authorization': `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`,
  2470. 'x-csrf-token': GetCookie("ct0"),
  2471. };
  2472. this.package = null;
  2473. this.anonymous = false;
  2474. }
  2475. }
  2476. class requestObject_twitter_time_line_forYou{
  2477. constructor(){
  2478. this.method = 'POST';
  2479. this.respType = 'json';
  2480. this.url = `https://twitter.com/i/api/graphql/-8TWbLqVU1ROq-eeErVc2w/HomeTimeline`;
  2481. this.body = JSON.stringify({
  2482. "variables": {
  2483. "count": 100,
  2484. "includePromotedContent": false,
  2485. "latestControlAvailable": true,
  2486. "requestContext": "launch",
  2487. "withCommunity": true,
  2488. },
  2489. "features": {
  2490. "responsive_web_graphql_exclude_directive_enabled": true,
  2491. "verified_phone_label_enabled": false,
  2492. "responsive_web_home_pinned_timelines_enabled": true,
  2493. "creator_subscriptions_tweet_preview_api_enabled": true,
  2494. "responsive_web_graphql_timeline_navigation_enabled": true,
  2495. "responsive_web_graphql_skip_user_profile_image_extensions_enabled": false,
  2496. "tweetypie_unmention_optimization_enabled": true,
  2497. "responsive_web_edit_tweet_api_enabled": true,
  2498. "graphql_is_translatable_rweb_tweet_is_translatable_enabled": true,
  2499. "view_counts_everywhere_api_enabled": true,
  2500. "longform_notetweets_consumption_enabled": true,
  2501. "responsive_web_twitter_article_tweet_consumption_enabled": false,
  2502. "tweet_awards_web_tipping_enabled": false,
  2503. "freedom_of_speech_not_reach_fetch_enabled": true,
  2504. "standardized_nudges_misinfo": true,
  2505. "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": true,
  2506. "longform_notetweets_rich_text_read_enabled": true,
  2507. "longform_notetweets_inline_media_enabled": true,
  2508. "responsive_web_media_download_video_enabled": false,
  2509. "responsive_web_enhance_cards_enabled": false
  2510. },
  2511. "queryId": "-8TWbLqVU1ROq-eeErVc2w"
  2512. });
  2513. this.headers = {
  2514. "Content-Type": "application/json",
  2515. 'User-agent': userAgent,
  2516. 'accept': '*/*',
  2517. 'Referer': "https://twitter.com/",
  2518. 'Host': 'twitter.com',
  2519. 'Accept-Encoding': 'br, gzip, deflate',
  2520. 'authorization': `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`,
  2521. 'x-csrf-token': GetCookie("ct0"),
  2522. };
  2523. this.package = null;
  2524. this.anonymous = false;
  2525. }
  2526. }
  2527. class requestObject_twitter_FavoriteTweet{
  2528. constructor(URL){
  2529. this.method = 'POST';
  2530. this.respType = 'json';
  2531. this.url = 'https://twitter.com/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet';
  2532. this.body = `{"variables":{"tweet_id":"${URL.split('/').pop()}"},"queryId":"lI07N6Otwv1PhnEgXILM7A"}`;
  2533. this.headers = {
  2534. 'Content-Type': 'application/json',
  2535. 'User-agent': userAgent,
  2536. 'accept': '*/*',
  2537. 'Referer': URL,
  2538. 'Accept-Encoding': 'br, gzip, deflate',
  2539. 'Origin': 'https://twitter.com',
  2540. 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
  2541. "x-csrf-token": GetCookie("ct0"),
  2542. 'Sec-Fetch-Site': 'same-origin',
  2543. 'Sec-Fetch-Mode': 'navigate',
  2544. };
  2545. this.package = null;
  2546. this.anonymous = false;
  2547. }
  2548. }
  2549. class requestObject_url_expand{
  2550. constructor(URL){
  2551. this.method = 'POST';
  2552. this.respType = 'text';
  2553. this.url = `https://geek-website.com/tool/shortlink_open/request.php`;
  2554. this.body = `shortlink=${encodeURIComponent(URL)}`;
  2555. this.headers = {
  2556. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  2557. 'User-agent': userAgent,
  2558. "Accept": "text/plain, */*; q=0.01",
  2559. 'Accept-Encoding': 'br, gzip, deflate',
  2560. 'Referer': "https://geek-website.com/tool/shortlink_open/",
  2561. 'Host': 'geek-website.com',
  2562. "Sec-Fetch-Dest": "empty",
  2563. "Sec-Fetch-Mode": "cors",
  2564. "Sec-Fetch-Site": "same-origin"
  2565. };
  2566. this.package = null;
  2567. this.anonymous = true;
  2568. }
  2569. }
  2570. class requestObject_fanbox{
  2571. constructor(URL,fanbox_URL){
  2572. this.method = 'GET';
  2573. this.respType = 'json';
  2574. this.url = `${URL}`;
  2575. this.body = null;
  2576. this.headers = {
  2577. 'User-agent': userAgent,
  2578. 'origin': fanbox_URL,
  2579. 'Accept-Encoding': 'br, gzip, deflate',
  2580. 'Host': 'api.fanbox.cc',
  2581. };
  2582. }
  2583. }
  2584. class sendObject_to_discord_webhook{
  2585. constructor(webhook,sendEmbeds){
  2586. this.method = 'POST';
  2587. this.respType = 'json';
  2588. this.url = "https://discord.com/api/webhooks/" + atob(webhook);
  2589. this.headers = {
  2590. };
  2591. this.package = null;
  2592. this.anonymous = true;
  2593. this.body = sendEmbeds;
  2594. }
  2595. }
  2596. class requestObject_binary_data{
  2597. constructor(URL,addtional_cookie = undefined){
  2598. this.method = 'GET';
  2599. this.respType = 'blob';
  2600. this.url = `${URL}`;
  2601. this.body = null;
  2602. this.encoding = null;
  2603. this.headers = {
  2604. "Content-Type": "*/*",
  2605. 'User-agent': navigator.userAgent || navigator.vendor || window.opera,
  2606. 'accept': '*/*',
  2607. 'Accept-Encoding': 'br, gzip, deflate',
  2608. 'Referer': URL,
  2609. "Sec-Fetch-Mode": "navigate",
  2610. 'cookie': `${addtional_cookie}`
  2611. };
  2612. this.package = null;
  2613. }
  2614. }
  2615. class requestObject_binary_head{
  2616. constructor(URL, addtional_cookie = undefined) {
  2617. this.method = 'HEAD'; // ここを 'HEAD' に変更
  2618. this.url = `${URL}`;
  2619. this.body = null;
  2620. this.encoding = null;
  2621. this.headers = {
  2622. "Content-Type": "*/*",
  2623. 'User-agent': navigator.userAgent || navigator.vendor || window.opera,
  2624. 'accept': '*/*',
  2625. 'Accept-Encoding': 'br, gzip, deflate',
  2626. 'Referer': URL,
  2627. "Sec-Fetch-Mode": "navigate",
  2628. 'cookie': `${addtional_cookie}`
  2629. };
  2630. this.package = null;
  2631. }
  2632. }
  2633. class requestObject{
  2634. constructor(URL){
  2635. this.method = 'GET';
  2636. this.respType = '';
  2637. this.url = `${URL}`;
  2638. this.headers = {
  2639. "Content-Type": "*/*",
  2640. 'Accept-Encoding': 'br, gzip, deflate',
  2641. 'User-agent': userAgent,
  2642. 'accept': '*/*',
  2643. 'Referer': URL,
  2644. "Sec-Fetch-Mode": "navigate",
  2645. "Sec-Fetch-Dest": "empty",
  2646. "Sec-Fetch-Mode": "cors",
  2647. "Sec-Fetch-Site": "same-origin"
  2648. };
  2649. this.package = null;
  2650. }
  2651. }
  2652. async function test(){
  2653. }
  2654. main();
  2655. //fetchAndProcessTwitterApi("TL");
  2656. if(currentUrl.match(/https?:\/\/twitter\.com\/[\w]*\/status\/[0-9]*/))fetchAndProcessTwitterApi("graphQL",extractTweetId(currentUrl));
  2657. window.addEventListener("scroll", update);
  2658. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址