XEnhancer

XEnhancer는 Twitter/X 탐색을 강화합니다 — 미디어를 한 번의 클릭으로 저장하고, 타임스탬프를 명확하게 포맷하며, 소셜 피드를 쉽게 정리할 수 있습니다.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name        XEnhancer
// @name:ar     XEnhancer
// @name:bg     XEnhancer
// @name:ckb    XEnhancer
// @name:cs     XEnhancer
// @name:da     XEnhancer
// @name:de     XEnhancer
// @name:el     XEnhancer
// @name:en     XEnhancer
// @name:eo     XEnhancer
// @name:es     XEnhancer
// @name:es-419 XEnhancer
// @name:fi     XEnhancer
// @name:fr     XEnhancer
// @name:fr-CA  XEnhancer
// @name:he     XEnhancer
// @name:hr     XEnhancer
// @name:hu     XEnhancer
// @name:id     XEnhancer
// @name:it     XEnhancer
// @name:ja     XEnhancer
// @name:ka     XEnhancer
// @name:ko     XEnhancer
// @name:nb     XEnhancer
// @name:nl     XEnhancer
// @name:pl     XEnhancer
// @name:pt-BR  XEnhancer
// @name:ro     XEnhancer
// @name:ru     XEnhancer
// @name:sk     XEnhancer
// @name:sr     XEnhancer
// @name:sv     XEnhancer
// @name:th     XEnhancer
// @name:tr     XEnhancer
// @name:uk     XEnhancer
// @name:ug     XEnhancer
// @name:vi     XEnhancer
// @description        XEnhancer empowers your Twitter/X browsing — save media in one click, format timestamps for clarity, and streamline your social feed with ease.
// @description:ar     XEnhancer يعزز تجربتك على تويتر (X) — احفظ الوسائط بنقرة واحدة، نسق الطوابع الزمنية لتوضيح أفضل، ونظّم تغذيتك الاجتماعية بسهولة.
// @description:bg     XEnhancer подобрява разглеждането на Twitter (X) — запазвайте медии с едно кликване, форматирайте времевите отметки за яснота и оптимизирайте социалния си поток лесно.
// @description:ckb    XEnhancer بەرز دەکاتەوە بەرەوپێشگای تیوتر (X) — وێنە و ڤیدیۆکان بە یەک کرتە پاشکەوت بکە، کاتی ڕووداوەکان ڕوون بکەرەوە، و فیدەکانی کۆمەڵایەتی بە ئاسانیدا ڕێکبخە.
// @description:cs     XEnhancer vylepšuje prohlížení Twitteru (X) — ukládejte média jedním kliknutím, formátujte časová razítka pro přehlednost a snadno optimalizujte svůj sociální feed.
// @description:da     XEnhancer forbedrer din oplevelse på Twitter (X) — gem medier med et enkelt klik, formater tidsstempler for klarhed, og strømlin din sociale feed med lethed.
// @description:de     XEnhancer verbessert Ihr Twitter/X-Erlebnis — speichern Sie Medien mit einem Klick, formatieren Sie Zeitstempel zur besseren Übersicht und optimieren Sie Ihren Social-Feed mühelos.
// @description:el     Το XEnhancer ενισχύει την περιήγησή σας στο Twitter (X) — αποθηκεύστε πολυμέσα με ένα κλικ, μορφοποιήστε χρονικές σημάνσεις για μεγαλύτερη σαφήνεια και οργανώστε εύκολα το κοινωνικό σας feed.
// @description:en     XEnhancer empowers your Twitter/X browsing — save media in one click, format timestamps for clarity, and streamline your social feed with ease.
// @description:eo     XEnhancer plibonigas vian retumadon de Twitter (X) — konservu amaskomunikilojn per unu klako, formatu tempstampon por pli granda klareco, kaj faciligu vian socian fluon.
// @description:es     XEnhancer mejora tu experiencia en Twitter (X): guarda medios con un clic, formatea las marcas de tiempo para mayor claridad y optimiza tu feed social con facilidad.
// @description:es-419 XEnhancer mejora tu navegación en Twitter (X) — guarda medios con un clic, formatea las marcas de tiempo para mayor claridad y agiliza tu feed social fácilmente.
// @description:fi     XEnhancer parantaa Twitter (X) -selailuasi — tallenna media yhdellä napsautuksella, muotoile aikaleimat selkeyden lisäämiseksi ja tehosta sosiaalista syötettäsi vaivattomasti.
// @description:fr     XEnhancer améliore votre navigation sur Twitter (X) — enregistrez des médias en un clic, formatez les horodatages pour plus de clarté et optimisez facilement votre flux social.
// @description:fr-CA  XEnhancer améliore votre expérience sur Twitter (X) — sauvegardez les médias en un clic, formatez les horodatages pour plus de clarté et simplifiez votre flux social facilement.
// @description:he     XEnhancer משדרג את הגלישה שלך ב-Twitter (X) — שמור מדיה בלחיצה אחת, עצב חותמות זמן להבהרה וייעל את הפיד החברתי שלך בקלות.
// @description:hr     XEnhancer poboljšava pregledavanje Twittera (X) — spremite medije jednim klikom, formatirajte vremenske oznake radi preglednosti i pojednostavite svoj društveni feed.
// @description:hu     Az XEnhancer fokozza a Twitter/X böngészést — egy kattintással mentheted a médiát, formázhatod az időbélyegeket az átláthatóság érdekében, és könnyedén optimalizálhatod a közösségi hírcsatornát.
// @description:id     XEnhancer meningkatkan pengalaman menjelajahi Twitter/X — simpan media dengan satu klik, format timestamp untuk kejelasan, dan permudah feed sosial Anda.
// @description:it     XEnhancer potenzia la navigazione su Twitter/X — salva i media con un clic, formatta i timestamp per maggiore chiarezza e ottimizza il tuo feed sociale con facilità.
// @description:ja     XEnhancer は Twitter (X) の閲覧を強化します — メディアをワンクリックで保存し、タイムスタンプを見やすくフォーマットし、ソーシャルフィードを簡単に整理できます。
// @description:ka     XEnhancer აძლიერებს Twitter/X-ს — დაარეგისტრირე მედია ერთ ক্লიკზე, დააწყობ დროის ნიშანებს გასაგებად და გამარტივე სოციალური ფიდი.
// @description:ko     XEnhancer는 Twitter/X 탐색을 강화합니다 — 미디어를 한 번의 클릭으로 저장하고, 타임스탬프를 명확하게 포맷하며, 소셜 피드를 쉽게 정리할 수 있습니다.
// @description:nb     XEnhancer forbedrer din Twitter/X-opplevelse — lagre medier med ett klikk, formater tidsstempler for klarhet, og strømlin feeden din enkelt.
// @description:nl     XEnhancer verbetert je Twitter/X-ervaring — sla media op met één klik, formatteer tijdstempels voor duidelijkheid en stroomlijn je sociale feed eenvoudig.
// @description:pl     XEnhancer usprawnia przeglądanie Twittera (X) — zapisuj media jednym kliknięciem, formatuj znaczniki czasu dla przejrzystości i usprawnij swój feed społecznościowy.
// @description:pt-BR  XEnhancer potencializa sua navegação no Twitter/X — salve mídias com um clique, formate os timestamps para maior clareza e organize seu feed social com facilidade.
// @description:ro     XEnhancer îmbunătățește navigarea pe Twitter/X — salvează media cu un singur clic, formatează timestamp-urile pentru claritate și optimizează-ți feedul social cu ușurință.
// @description:ru     XEnhancer улучшает просмотр Twitter/X — сохраняйте медиа в один клик, форматируйте временные метки для наглядности и упрощайте вашу социальную ленту.
// @description:sk     XEnhancer zlepšuje prehliadanie Twitteru (X) — ukladajte médiá jedným kliknutím, formátujte časové značky pre prehľadnosť a zjednodušte svoj sociálny feed.
// @description:sr     XEnhancer unapređuje pregledanje Twittera (X) — sačuvajte medije jednim klikom, formatirajte vremenske oznake radi preglednosti i olakšajte svoj društveni feed.
// @description:sv     XEnhancer förbättrar din Twitter/X-upplevelse — spara media med ett klick, formatera tidsstämplar för tydlighet och effektivisera ditt sociala flöde enkelt.
// @description:th     XEnhancer ช่วยเพิ่มประสิทธิภาพการใช้งาน Twitter/X — บันทึกสื่อด้วยคลิกเดียว, จัดรูปแบบเวลาสำหรับความชัดเจน, และปรับปรุงฟีดโซเชียลของคุณอย่างง่ายดาย
// @description:tr     XEnhancer, Twitter/X deneyiminizi güçlendirir — medyaları tek tıkla kaydedin, zaman damgalarını netlik için biçimlendirin ve sosyal akışınızı kolayca düzenleyin.
// @description:uk     XEnhancer покращує перегляд Twitter/X — зберігайте медіа одним кліком, форматування часових міток для наочності та спрощення вашої соціальної стрічки.
// @description:ug     XEnhancer Twitter/X تەجرىبىسىڭىزنى كۈچەيتىدۇ — ۋىدىئولارنى ۋە رەسىملەرنى بىر قېتىملىق چېكىش بىلەن ساقلاڭ، ۋاقىت بەلگىلىرىنى ئاچچىق-ئاشكارا قىلىپ بەلگىلەڭ، ۋە ئىجتىمائىي فېدڭىزنى ئاسانلاشتۇرۇڭ.
// @description:vi     XEnhancer nâng cao trải nghiệm duyệt Twitter/X — lưu phương tiện chỉ với một cú nhấp, định dạng dấu thời gian rõ ràng và tối ưu hóa luồng xã hội của bạn dễ dàng.
// @namespace levivi_myself
// @version   1.0.1
// @author    PeterParker, Levivi
// @icon      data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAEBklEQVR4AeyZXVbjMAyFy2wJ3ikro6wMeIc1de7nOh7LlmM7yRwOHDhR4h9ZuleSnbb8OX3zv18CX53An5WBh4eHywY5H5kF+T9LXByeH5OB6/X6eL1en6+T4hneMgZw+X6VVBhk701SXYbA5+fnkzRcRY23rvP9/f1ra3J0PIJ/9vTv7u5ePj4+XFyGAItR5jkpIe2Ta5K6wJ+JehrIGuAR+Es2ZJoVASm/scho3TqMP2lukZfb8O0OAIDcenN3rXUzKF9EvgkeLxUBBkWCRWXKwmbV3FuUSkdA3BLAZkta5TcCHpsuASa8/VACxAm6mUztB2WMIITAZDZoEiTmaK9KkwCregDJhKcTgWGiKeiUAVmUY/CW7upzlcAIQOlcShIAE0AvsgGM5i7ohE5xky1OwmK03V0lwLIRgOhI1+yZFsAeeNkydmR39eoSYLWMUo/GsACak0ORM6eS1lX7oQOeE8f4kI3uNUQAKw7AU36CiCTHbEUC0KzXc/NZz/qWDBOIAMv6NC8w6TT3Q5mxBRCBYd3Sn30OE8CwHFVRFrBnoss8Ip1uuaGH7AWPjSkCLGgBzEkADN2ODJ/1a3amCWDMO6fJBHOISJKpstyYSuLZSJMTjU0EsK8olwDL/QCJclOz9OSsDeNbbhMErPkYZQOQLKiU2ANBWTq0q6NR49VYWLDhtpkAvgSkAhhJpLewom1Isi4/funvkV0EcBxr2UQUEswhIkkprZYbeltlNwEcO1Hu7gdIqtxSprCzRQ4hEKNsSiUCpMQCLunQLjP1upfEIQRAKMCPPHPRmHnJOZk6oZOvmW0fQkBRJLpuOeQAlYXD98NuAoDPQToRNJ9KIwmv3NwAOPbM0C4CA+AXZ+WmJmOH7IfNBNbAU+vIgp4nWdKaFOV4/DKVBJ3UGWxsIgCQljOAq0wuiDBUUdZYuqS7+/2wiYDAm29jCyIB4lsV5RGG6IdGdsvfwiLJpt61H6YJ5AAyXHxAM+CZiwBXoywdCFeZIsvY6MkUgQg+1XFmvPnZXgC7Ud6zH4YJKCJEygN/8gBk5E4iwdrVKKvcVjOV28vbQwQAr7p3fzZ0HOf2U9sjmdsUyVamIJ/slI0ugR54HJdGW32H7Mj7wXwcKW2vEuiAZ9OasiiNl33IioR36qQox0wZu3mmSptNAgL/X37HEQnAVgDlL/1bqQSpvsmU+ulqEhDrobM+WZpotKIsn+FfSzJVHRbMQVJz5nIJxOPSKNIh/TGCdHcJtmYNRBKGXEUggjdK0VHzrI/z/x4DLQWiOnUGllXfHwwBpeisyLxLXkqJaR/xMawjEuGnyNJXp/+eOzAEZDBEWc/wYSx/5ouObOc+Rtu5f0Mgn/gu7V8CX52pvwAAAP//pQZ+UwAAAAZJREFUAwBCEOZ/U8dgbwAAAABJRU5ErkJggg==
// @include   https://x.com/*
// @include   https://twitter.com/*
// @exclude   *://x.com/i/flow/*
// @license   MIT
// @run-at    document-start
// @noframes
// @grant     GM_registerMenuCommand
// @grant     GM_openInTab
// @grant     GM.openInTab
// @grant     GM_addStyle
// @grant     GM_setValue
// @grant     GM_getValue
// @grant     GM_deleteValue
// @grant     GM_xmlhttpRequest
// @grant     GM_download
// ==/UserScript==
(function () {
  'use strict';

  
  /*!
  * Copyright (c) 2026 - 2026, Levivi. All rights reserved.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  *
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
  */


  var css_248z$1 = "li[role=listitem]>div>div>div>div:not(:last-child){filter:none}li[role=listitem]>div>div>div>div+div:last-child{display:none}";

  var css_248z = ".x-master-dl{margin-left:12px;order:99}.x-master-dl:hover>div>div>div>div{color:#1da1f2}.x-master-dl:hover>div>div>div>div>div{background-color:rgba(29,161,242,.1)}.x-master-dl:active>div>div>div>div>div{background-color:rgba(29,161,242,.2)}.x-master-dl:hover svg{color:#1da1f2}.x-master-dl:hover div:first-child:not(:last-child){background-color:rgba(29,161,242,.1)}.x-master-dl:active div:first-child:not(:last-child){background-color:rgba(29,161,242,.2)}.x-master-dl.tmd-media{position:absolute;right:0}.x-master-dl.tmd-media>div{border-radius:99px;display:flex;margin:2px}.x-master-dl.tmd-media>div>div{color:#fff;display:flex;margin:6px}.x-master-dl.tmd-media:hover>div{background-color:hsla(0,0%,100%,.6)}.x-master-dl.tmd-media:hover>div>div{color:#1da1f2}.x-master-dl.tmd-media:not(:hover)>div>div{filter:drop-shadow(0 0 1px #000)}.x-master-dl g{display:none}.x-master-dl.completed g.completed,.x-master-dl.download g.download,.x-master-dl.failed g.failed,.x-master-dl.loading g.loading{display:unset}.x-master-dl.loading svg{animation:spin 1s linear infinite}.x-master-dl.download g.download{color:#1da1f2}.tmd-btn{background-color:#1da1f2;border-radius:99px;color:#fff;padding:0 20px}.tmd-btn,.tmd-tag{display:inline-block}.tmd-tag{background-color:#fff;border:1px solid #1da1f2;border-radius:10px;color:#1da1f2;font-weight:700;margin:5px;padding:0 10px}.tmd-btn:hover{background-color:rgba(29,161,242,.9)}.tmd-tag:hover{background-color:rgba(29,161,242,.1)}.tmd-notifier{background:#fff;border:1px solid #ccc;border-radius:8px;bottom:16px;color:#000;display:none;left:16px;padding:4px;position:fixed}.tmd-notifier.running{align-items:center;display:flex}.tmd-notifier label{align-items:center;display:inline-flex;margin:0 8px}.tmd-notifier label:before{background-position:50%;background-repeat:no-repeat;content:\" \";height:16px;width:32px}.tmd-notifier label:first-child:before{background-image:url(\"data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2216%22 height=%2216%22 viewBox=%220 0 24 24%22><path d=%22M3,14 v5 q0,2 2,2 h14 q2,0 2,-2 v-5 M7,10 l4,4 q1,1 2,0 l4,-4 M12,3 v11%22 fill=%22none%22 stroke=%22%23666%22 stroke-width=%222%22 stroke-linecap=%22round%22 /></svg>\")}.tmd-notifier label:nth-child(2):before{background-image:url(\"data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2216%22 height=%2216%22 viewBox=%220 0 24 24%22><path d=%22M12,2 a1,1 0 0 1 0,20 a1,1 0 0 1 0,-20 M12,5 v7 h6%22 fill=%22none%22 stroke=%22%23999%22 stroke-width=%222%22 stroke-linejoin=%22round%22 stroke-linecap=%22round%22 /></svg>\")}.tmd-notifier label:nth-child(3):before{background-image:url(\"data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2216%22 height=%2216%22 viewBox=%220 0 24 24%22><path d=%22M12,0 a2,2 0 0 0 0,24 a2,2 0 0 0 0,-24%22 fill=%22%23f66%22 stroke=%22none%22 /><path d=%22M14.5,5 a1,1 0 0 0 -5,0 l0.5,9 a1,1 0 0 0 4,0 z M12,17 a2,2 0 0 0 0,5 a2,2 0 0 0 0,-5%22 fill=%22%23fff%22 stroke=%22none%22 /></svg>\")}.x-master-dl.tmd-img{bottom:0;display:none!important;position:absolute;right:0}.x-master-dl.tmd-img>div{background-color:hsla(0,0%,100%,.6);border-radius:99px;display:flex;margin:2px}.x-master-dl.tmd-img>div>div{color:#fff!important;display:flex;margin:6px}.x-master-dl.tmd-img:not(:hover)>div>div{filter:drop-shadow(0 0 1px #000)}.x-master-dl.tmd-img:hover>div>div{color:#1da1f2}.tmd-img.completed,.tmd-img.failed,.tmd-img.loading,:hover>.x-master-dl.tmd-img{display:block!important}.tweet-detail-action-item{width:20%!important}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}";

  const language = {
    "zh": {
      "dateFormat": {
        "week": ["日", "一", "二", "三", "四", "五", "六"]
      },
      "download": {
        "download": "下载",
        "completed": "下载完成",
        "tip": "点击下载视频",
        "preparing": "正在准备下载(如果失败,请手动操作)"
      },
      "menuCommand": {
        "settings": "设置",
        "titleDateFormat": "时间格式设置:",
        "buttonClose": "关闭",
        "simplifyMode": "简化模式",
        "turnOn": "打开",
        "turnOff": "关闭"
      }
    },
    "en": {
      "dateFormat": {
        "week": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
      },
      "download": {
        "download": "Download",
        "completed": "Download Completed",
        "tip": "Click to download video",
        "preparing": "Preparing to download (if failed, please do it manually)"
      },
      "menuCommand": {
        "settings": "Settings",
        "titleDateFormat": "Time format settings:",
        "buttonClose": "Close",
        "simplifyMode": "Simplify Mode",
        "turnOn": "Turn on",
        "turnOff": "Turn off"
      }
    },
    "ja": {
      "dateFormat": {
        "week": ["日", "月", "火", "水", "木", "金", "土"]
      },
      "download": {
        "download": "ダウンロード",
        "completed": "ダウンロード完了",
        "tip": "クリックしてビデオをダウンロード",
        "preparing": "ダウンロードの準備中(失敗する場合は手動で行ってください)"
      },
      "menuCommand": {
        "settings": "設定",
        "titleDateFormat": "時刻形式の設定:",
        "buttonClose": "閉鎖",
        "simplifyMode": "簡易モード",
        "turnOn": "オン",
        "turnOff": "オフ"
      }
    },
    "fr": {
      "dateFormat": {
        "week": ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"]
      },
      "download": {
        "download": "télécharger",
        "completed": "éléchargement terminé",
        "tip": "Cliquez pour télécharger la vidéo",
        "preparing": "Préparation du téléchargement (en cas d'échec, veuillez le faire manuellement)"
      },
      "menuCommand": {
        "settings": "installation",
        "titleDateFormat": "Paramètres du format de l'heure :",
        "buttonClose": "fermeture",
        "simplifyMode": "Mode simplifié",
        "turnOn": "Activer",
        "turnOff": "Désactiver"
      }
    },
    "de": {
      "dateFormat": {
        "week": ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"]
      },
      "download": {
        "download": "herunterladen",
        "completed": "Download abgeschlossen",
        "tip": "Klicken Sie hier, um das Video herunterzuladen",
        "preparing": "Vorbereitung für den Download (falls der Download fehlschlägt, führen Sie ihn bitte manuell durch)"
      },
      "menuCommand": {
        "settings": "aufstellen",
        "titleDateFormat": "Einstellungen für das Zeitformat:",
        "buttonClose": "Schließung",
        "simplifyMode": "Vereinfachter Modus",
        "turnOn": "Einschalten",
        "turnOff": "Ausschalten"
      }
    },
    "it": {
      "dateFormat": {
        "week": ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"]
      },
      "download": {
        "download": "scaricamento",
        "completed": "Download completato",
        "tip": "Fare clic per scaricare il video",
        "preparing": "Preparazione per il download (se fallisce, eseguilo manualmente)"
      },
      "menuCommand": {
        "settings": "impostare",
        "titleDateFormat": "Impostazioni del formato dell'ora:",
        "buttonClose": "chiusura",
        "simplifyMode": "Modalità semplificata",
        "turnOn": "Attiva",
        "turnOff": "Disattiva"
      }
    },
    "ko": {
      "dateFormat": {
        "week": ["일", "월", "화", "수", "목", "금", "토"]
      },
      "download": {
        "download": "다운로드",
        "completed": "다운로드 완료",
        "tip": "비디오를 다운로드하려면 클릭하세요",
        "preparing": "다운로드 준비 중 (실패할 경우 수동으로 진행해주세요)"
      },
      "menuCommand": {
        "settings": "설정",
        "titleDateFormat": "시간 형식 설정:",
        "buttonClose": "폐쇄",
        "simplifyMode": "간소화 모드",
        "turnOn": "켜기",
        "turnOff": "끄기"
      }
    },
    "ru": {
      "dateFormat": {
        "week": ["ВС", "ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ"]
      },
      "download": {
        "download": "скачать",
        "completed": "Загрузка завершена",
        "tip": "Нажмите, чтобы скачать видео",
        "preparing": "Подготовка к загрузке (если не получается, сделайте это вручную)"
      },
      "menuCommand": {
        "settings": "настраивать",
        "titleDateFormat": "Настройки формата времени:",
        "buttonClose": "закрытие",
        "simplifyMode": "Упрощенный режим",
        "turnOn": "Включить",
        "turnOff": "Выключить"
      }
    },
    "pt": {
      "dateFormat": {
        "week": ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"]
      },
      "download": {
        "download": "descargar",
        "completed": "Descarga completa",
        "tip": "Clique para baixar o vídeo",
        "preparing": "Preparação para download (se falhar, faça-o manualmente)"
      },
      "menuCommand": {
        "settings": "configuración",
        "titleDateFormat": "Configuración de formato de hora:",
        "buttonClose": "cierre",
        "simplifyMode": "Modo simplificado",
        "turnOn": "Ativar",
        "turnOff": "Desativar"
      }
    },
    "es": {
      "dateFormat": {
        "week": ["DOM", "LUN", "MAR", "MIER", "JUE", "VIE", "SÁB"]
      },
      "download": {
        "download": "descargar",
        "completed": "Descarga completa",
        "tip": "Haga clic para descargar el vídeo",
        "preparing": "Preparándose para la descarga (si falla, hágalo manualmente)"
      },
      "menuCommand": {
        "settings": "configuración",
        "titleDateFormat": "Configuración de formato de hora:",
        "buttonClose": "cierre",
        "simplifyMode": "Modo simplificado",
        "turnOn": "Activar",
        "turnOff": "Desactivar"
      }
    },
    "th": {
      "dateFormat": {
        "week": ["วันอาทิตย์", "วันจันทร์", "วันอังคาร", "วันพุธ", " วันพฤหัสบดี", "วันศุกร์ ", "วันเสาร์ "]
      },
      "download": {
        "download": "ดาวน์โหลด",
        "completed": "ดาวน์โหลดเสร็จสมบูรณ์",
        "tip": "คลิกเพื่อดาวน์โหลดวิดีโอ",
        "preparing": "กำลังเตรียมการดาวน์โหลด (หากล้มเหลว กรุณาดำเนินการด้วยตนเอง)"
      },
      "menuCommand": {
        "settings": "ตั้งค่า",
        "titleDateFormat": "การตั้งค่ารูปแบบเวลา:",
        "buttonClose": "ปิด",
        "simplifyMode": "โหมดแบบย่อ",
        "turnOn": "เปิด",
        "turnOff": "ปิด"
      }
    },
    "tr": {
      "dateFormat": {
        "week": ["Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"]
      },
      "download": {
        "download": "indirmek",
        "completed": "İndirme tamamlandı",
        "tip": "Videoyu indirmek için tıklayın",
        "preparing": "İndirmeye hazırlanıyor (başarısız olursa lütfen manuel olarak yapın)"
      },
      "menuCommand": {
        "settings": "kurmak",
        "titleDateFormat": "Saat formatı ayarları:",
        "buttonClose": "kapatma",
        "simplifyMode": "Basitleştirilmiş mod",
        "turnOn": "Aç",
        "turnOff": "Kapat"
      }
    },
    "nl": {
      "dateFormat": {
        "week": ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"]
      },
      "download": {
        "download": "downloaden",
        "completed": "Downloaden voltooid",
        "tip": "Klik om video te downloaden",
        "preparing": "Voorbereiden voor downloaden (als dit mislukt, doe dit dan handmatig)"
      },
      "menuCommand": {
        "settings": "opgezet",
        "titleDateFormat": "Instellingen tijdformaat:",
        "buttonClose": "sluiting",
        "simplifyMode": "Vereenvoudigde modus",
        "turnOn": "Inschakelen",
        "turnOff": "Uitschakelen"
      }
    }
  };
  const lang = (navigator.language || navigator.userLanguage || "").slice(0, 2).toLowerCase() || "en";
  const Commonlanguage = language[lang] ?? language["en"];

  const FMT = 16;
  const XSettingsDialog = {
    number: Math.ceil(Math.random() * 1e8),
    formats: [
      { format: "Do nothing", example: "N/A" },
      { format: "ISO 8601 T", example: "2025-07-09T22:57:30" },
      { format: "ISO 8601 (space + s)", example: "2025-07-09 22:57:30" },
      { format: "ISO 8601 (space, no s)", example: "2025-07-09 22:57" },
      { format: "US: MMM d, yyyy h:mm A", example: "Jul 9, 2025, 10:57 PM" },
      { format: "US: EEE, MMM d, yyyy h:mm A", example: "Wed, Jul 9, 2025, 10:57 PM" },
      { format: "US: MM/dd/yyyy h:mm A", example: "07/09/2025 10:57 PM" },
      { format: "US: MM/dd/yyyy HH:mm", example: "07/09/2025 22:57" },
      { format: "EU/UK: dd/MM/yyyy HH:mm", example: "09/07/2025 22:57" },
      { format: "DE: dd.MM.yyyy, HH:mm", example: "09.07.2025, 22:57" },
      { format: "EU long: d MMMM yyyy, HH:mm", example: "9 July 2025, 22:57" },
      { format: "CN: yyyy年M月d日 HH:mm", example: "2025年7月9日 22:57" },
      { format: "East Asia: yyyy/MM/dd HH:mm", example: "2025/07/09 22:57" },
      { format: "UK short: EEE d MMM yyyy HH:mm", example: "Wed 9 Jul 2025 22:57" },
      { format: "Unix ctime (en)", example: "Wed Jul  9 22:57:30 2025" },
      { format: "US full: EEEE, MMMM d, yyyy h:mm:ss A", example: "Wednesday, July 9, 2025, 10:57:30 PM" },
      { format: "Compact: hh.mm A·mmm d,yy", example: "10.57 PM·Jul 9,25" },
      { format: "TW ROC: Myyy-MM-dd HH:mm", example: "M114-07-09 22:57" }
    ],
    make() {
      const dialog = document.createElement("div");
      dialog.className = "dialog_u_" + this.number;
      dialog.style.all = "initial";
      dialog.style.backgroundColor = "#fff";
      dialog.style.border = "1px solid #e1e8ed";
      dialog.style.borderRadius = "10px";
      dialog.style.boxShadow = "0 16px 48px rgba(15, 20, 25, 0.14), 0 4px 16px rgba(15, 20, 25, 0.08)";
      dialog.style.fontFamily = "monospace";
      dialog.style.fontSize = "12px";
      dialog.style.width = "640px";
      dialog.style.maxWidth = "calc(100vw - 24px)";
      dialog.style.boxSizing = "border-box";
      dialog.style.paddingLeft = "8px";
      dialog.style.paddingRight = "8px";
      dialog.style.paddingTop = "8px";
      dialog.style.paddingBottom = "8px";
      dialog.style.position = "fixed";
      dialog.style.right = "8px";
      dialog.style.top = "8px";
      dialog.style.zIndex = "2147483647";
      dialog.style.overflow = "auto";
      const escHtml = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
      let formatsHtml = `<table style="width:100%;border: 1px solid #c0bfbf;border-collapse: collapse;">`;
      for (let i = 1; i <= this.formats.length; i++) {
        if (i % 2 !== 0) {
          formatsHtml += `<tr style="width:100%;border: 1px solid #c0bfbf;">`;
        }
        const item = this.formats[i - 1];
        const exTitle = String(item.example).replace(/"/g, "&quot;");
        const fmtLine = escHtml(item.format);
        const exLine = escHtml(item.example);
        formatsHtml += `<td width="50%" style="border: 1px solid #c0bfbf;padding: 5px;vertical-align:top;" title="${exTitle}"><div><div style="color:#000;font-size:13px;"><input type="radio" name="fmt" value="${i - 1}" class="top_r" /><b>【${i}】${fmtLine}</b></div><div style="color:#555;font-size:11px;margin-top:4px;padding-left:20px;">${exLine}</div></div></td>`;
        if (i % 2 === 0) {
          formatsHtml += `</tr>`;
        }
      }
      if (this.formats.length % 2 !== 0) {
        formatsHtml += `</tr>`;
      }
      formatsHtml += `</table>`;
      const btnLabel = escHtml(Commonlanguage.menuCommand.buttonClose);
      const titleText = escHtml(Commonlanguage.menuCommand.titleDateFormat);
      dialog.innerHTML = `<div style="font-size:17px;font-weight:700;margin:15px auto;padding:0 4px;text-align:center;font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;color:#0f1419;">` + titleText + `</div><div>` + formatsHtml + `</div><div style="margin-top:15px;text-align:center;"><button type="button" name="closex" style="appearance:none;-webkit-appearance:none;margin:0;border:1px solid #1d9bf0;border-radius:999px;padding:5px 18px;font-size:14px;font-weight:600;font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;background:linear-gradient(180deg,#1d9bf0 0%,#1a8cd8 100%);color:#fff;cursor:pointer;box-shadow:0 2px 10px rgba(29,155,240,0.35);">` + btnLabel + `</button></div>`;
      dialog.style.display = "none";
      return dialog;
    },
    addEvent(dialog) {
      dialog.querySelector("button[name='closex']").addEventListener(
        "click",
        () => {
          for (const e of dialog.querySelectorAll('input[name="fmt"]')) {
            if (e.checked) {
              fmt = e.value;
              break;
            }
          }
          GM_setValue("fmt", fmt);
          dialog.style.display = "none";
        },
        false
      );
    },
    init() {
      const dialog = this.make();
      this.addEvent(dialog);
      document.body.appendChild(dialog);
      GM_registerMenuCommand(Commonlanguage.menuCommand.settings, () => {
        if (dialog.style.display !== "none")
          return;
        const input = dialog.querySelector(
          `input[name="fmt"][value="${String(fmt)}"]`
        );
        if (input)
          input.checked = true;
        dialog.style.display = "block";
      });
      const isSimplifyMode = GM_getValue("x_simplify_mode", "") === "true";
      GM_registerMenuCommand(Commonlanguage.menuCommand.simplifyMode + `(${isSimplifyMode ? Commonlanguage.menuCommand.turnOff : Commonlanguage.menuCommand.turnOn})`, () => {
        if (isSimplifyMode) {
          GM_deleteValue("x_simplify_mode");
        } else {
          GM_setValue("x_simplify_mode", "true");
        }
        location.reload(true);
      });
    }
  };
  let fmt = GM_getValue("fmt", FMT);
  (function syncFmtIndex() {
    const max = XSettingsDialog.formats.length - 1;
    const v = parseInt(String(fmt), 10);
    if (Number.isNaN(v) || v < 0 || v > max) {
      const next = Math.min(Math.max(parseInt(String(FMT), 10), 0), max);
      fmt = String(next);
      GM_setValue("fmt", fmt);
    } else {
      fmt = String(v);
    }
  })();
  const XDateFormat = {
    df: function(date, f) {
      const WEEK_FULL = [
        "Sunday",
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday"
      ];
      const MONTH_SHORT = [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec"
      ];
      const MONTH_FULL = [
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December"
      ];
      const pad = (num) => ("0" + num).slice(-2);
      const YE = date.getFullYear();
      const YE2 = YE.toString().slice(-2);
      const YM = YE - 1911;
      const MO = pad(date.getMonth() + 1);
      const MO_IDX = date.getMonth();
      const MO_NAME = MONTH_SHORT[MO_IDX];
      const MO_NAME_FULL = MONTH_FULL[MO_IDX];
      const DA = pad(date.getDate());
      const dNum = parseInt(DA, 10);
      const weekAbbr = () => WEEK_FULL[date.getDay()].slice(0, 3);
      const HO = pad(date.getHours());
      const MI = pad(date.getMinutes());
      const SE = pad(date.getSeconds());
      const h12 = date.getHours() % 12 || 12;
      const HO12 = pad(h12);
      const AMPM = date.getHours() >= 12 ? "PM" : "AM";
      const F = [
        `${YE}-${MO}-${DA}T${HO}:${MI}:${SE}`,
        `${YE}-${MO}-${DA} ${HO}:${MI}:${SE}`,
        `${YE}-${MO}-${DA} ${HO}:${MI}`,
        `${MO_NAME} ${dNum}, ${YE}, ${HO12}:${MI} ${AMPM}`,
        `${weekAbbr()}, ${MO_NAME} ${dNum}, ${YE}, ${HO12}:${MI} ${AMPM}`,
        `${MO}/${DA}/${YE} ${HO12}:${MI} ${AMPM}`,
        `${MO}/${DA}/${YE} ${HO}:${MI}`,
        `${DA}/${MO}/${YE} ${HO}:${MI}`,
        `${DA}.${MO}.${YE}, ${HO}:${MI}`,
        `${dNum} ${MO_NAME_FULL} ${YE}, ${HO}:${MI}`,
        `${YE}年${MO_IDX + 1}月${dNum}日 ${HO}:${MI}`,
        `${YE}/${MO}/${DA} ${HO}:${MI}`,
        `${weekAbbr()} ${dNum} ${MO_NAME} ${YE} ${HO}:${MI}`,
        `${weekAbbr()} ${MO_NAME} ${String(dNum).padStart(2, " ")} ${HO}:${MI}:${SE} ${YE}`,
        `${WEEK_FULL[date.getDay()]}, ${MO_NAME_FULL} ${dNum}, ${YE}, ${HO12}:${MI}:${SE} ${AMPM}`,
        `${HO12}.${MI} ${AMPM}·${MO_NAME} ${dNum},${YE2}`,
        `M${YM}-${MO}-${DA} ${HO}:${MI}`
      ];
      return F[f] ?? F[0];
    },
    repldatetime: function() {
      const MYNAME = "peter_parker_x1190";
      const SEL = 'main div[data-testid="primaryColumn"] section article time[datetime*=":"]';
      const SEL_2 = 'div[aria-labelledby="modal-header"] div[data-testid^="User-Name"] time[datetime]';
      const SEL_3 = 'div[aria-labelledby="modal-header"] div[aria-label] time[datetime]';
      const SEL_4 = 'main section[aria-labelledby="detail-header"] article div[data-testid^="User-Name"] time[datetime]';
      const SEL_5 = 'main section div[data-testid="conversation"] div[aria-label] time[datetime]';
      document.querySelectorAll(
        SEL + ", " + SEL_2 + ", " + SEL_3 + ", " + SEL_4 + ", " + SEL_5
      ).forEach((e) => {
        if (fmt != 0) {
          const SEL_ADD = "span.us-" + MYNAME;
          let d = e.getAttribute("datetime");
          let df = this.df(new Date(d), fmt - 1);
          let pe = e.parentNode;
          let old = pe.querySelectorAll(SEL_ADD);
          if (!old.length) {
            let span = document.createElement("span");
            span.className = "us-" + MYNAME;
            span.setAttribute("datetime", d);
            span.setAttribute("local-datetime", df);
            span.textContent = df;
            span.style = e.style;
            e.style.setProperty("display", "none");
            pe.appendChild(span);
          } else if (old[0].getAttribute("local-datetime") != df) {
            old[0].setAttribute("local-datetime", df);
            old[0].textContent = df;
            old[0].style = e.style;
          }
        }
      });
    }
  };
  const XDownloadUI = {
    showSensitive: true,
    svg: `
        <g class="download"><path d="M11.99 16l-5.7-5.7L7.7 8.88l3.29 3.3V2.59h2v9.59l3.3-3.3 1.41 1.42-5.71 5.7zM21 15l-.02 3.51c0 1.38-1.12 2.49-2.5 2.49H5.5C4.11 21 3 19.88 3 18.5V15h2v3.5c0 .28.22.5.5.5h12.98c.28 0 .5-.22.5-.5L19 15h2z" /></g>
        <g class="completed"><path d="M3,14 v5 q0,2 2,2 h14 q2,0 2,-2 v-5 M7,10 l3,4 q1,1 2,0 l8,-11" fill="none" stroke="#1DA1F2" stroke-width="2" stroke-linecap="round" /></g>
        <g class="loading"><circle cx="12" cy="12" r="10" fill="none" stroke="#1DA1F2" stroke-width="4" opacity="0.4" /><path d="M12,2 a10,10 0 0 1 10,10" fill="none" stroke="#1DA1F2" stroke-width="4" stroke-linecap="round" /></g>
        <g class="failed"><circle cx="12" cy="12" r="11" fill="#f33" stroke="currentColor" stroke-width="2" opacity="0.8" /><path d="M14,5 a1,1 0 0 0 -4,0 l0.5,9.5 a1.5,1.5 0 0 0 3,0 z M12,17 a2,2 0 0 0 0,4 a2,2 0 0 0 0,-4" fill="#fff" stroke="none" /></g>
    `,
    isTweetdeck: () => window.location.host.includes("tweetdeck"),
    extractStatusId: (url) => url ? (url.match(/\/status\/(\d+)/) || [null, null])[1] : null,
    uniqueArray: (arr) => Array.from(new Set(arr)),
    getExtension: (url) => new URL(url).pathname.split(".").pop() || null,
    sanitizeFilename: (filename) => filename.replace(/[\/\\\?\%\*\:\|\\"<>\r\n]/g, "_"),
    setStatus: function(btn, classnames, title, style) {
      if (classnames) {
        btn.classList.remove("download", "completed", "loading", "failed");
        btn.classList.add(...classnames);
      }
      if (title)
        btn.title = title;
      if (style)
        btn.style.cssText = style;
    },
    clickDownloadEvent: function(btn, statusIds) {
      const filenameTemplate = "{name}";
      const uniqueStatusIds = this.uniqueArray(statusIds);
      const handleDownload = (url, filename, defaultExt) => {
        return new Promise((resolve, reject) => {
          const finalFilename = filename + "." + (this.getExtension(url) ?? defaultExt);
          GM_download({
            url,
            name: finalFilename,
            onload: () => {
              XDownloadUI.setStatus(btn, ["completed"], "下载完成");
              resolve();
            },
            onerror: (error) => {
              reject(error);
            }
          });
        });
      };
      this.setStatus(btn, ["loading"], "正在准备下载...");
      const downloadPromises = uniqueStatusIds.map((statusId) => {
        const media = XDownload.mediaMap.get(statusId);
        if (!media) {
          return Promise.reject(
            `Media data not found for status ID: ${statusId}`
          );
        }
        const { entityId, video, photo, text } = media;
        const filename = filenameTemplate.replace(
          "{name}",
          this.sanitizeFilename(text) || entityId
        );
        if (video) {
          return handleDownload(video, filename, "mp4");
        } else if (photo) {
          return handleDownload(photo, filename, "jpg");
        }
        return Promise.reject("No media to download");
      });
      Promise.allSettled(downloadPromises).then((results) => {
        const anySuccess = results.some(
          (result) => result.status === "fulfilled"
        );
        if (anySuccess) {
          this.setStatus(btn, ["completed"], "下载成功");
        } else {
          this.setStatus(btn, ["failed"], "未找到媒体资源");
        }
      }).catch((error) => {
        this.setStatus(btn, ["failed"], "下载失败");
      });
    },
    addButtonTo: function(article) {
      if (article.dataset.detected)
        return;
      article.dataset.detected = "true";
      const statusIds = Array.from(
        article.querySelectorAll('a[href*="/status/"]')
      ).map((el) => this.extractStatusId(el.href)).filter((id) => id);
      if (statusIds.length === 0)
        return;
      const mediaSelector = [
        'a[href*="/photo/1"]',
        'div[role="progressbar"]',
        'button[data-testid="playButton"]',
        'div[data-testid="videoComponent"]',
        'a[href="/settings/content_you_see"]',
        "div.media-image-container",
        "div.media-preview-container",
        'div[aria-labelledby]>div:first-child>div[role="button"][tabindex="0"]'
      ];
      const hasMedia = article.querySelector(mediaSelector.join(","));
      if (hasMedia) {
        const btnGroup = article.querySelector(
          'div[role="group"]:last-of-type, ul.tweet-actions, ul.tweet-detail-actions'
        );
        if (btnGroup) {
          const btnShare = Array.from(
            btnGroup.querySelectorAll(
              ":scope>div>div, li.tweet-action-item>a, li.tweet-detail-action-item>a"
            )
          ).pop().parentNode;
          const btnDownload = btnShare.cloneNode(true);
          btnDownload.style.marginLeft = "10px";
          btnDownload.querySelector("button")?.removeAttribute("disabled");
          const svgContainer = this.isTweetdeck() ? btnDownload.firstElementChild : btnDownload.querySelector("svg");
          if (svgContainer) {
            if (this.isTweetdeck()) {
              svgContainer.innerHTML = `<svg viewBox="0 0 20 20" width="15" height="15">${this.svg}</svg>`;
              svgContainer.removeAttribute("rel");
              btnDownload.classList.replace("pull-left", "pull-right");
            } else {
              svgContainer.innerHTML = this.svg;
            }
          }
          this.setStatus(btnDownload, ["x-master-dl", "download"], "下载媒体");
          btnGroup.insertBefore(btnDownload, btnShare.nextSibling);
          btnDownload.onclick = () => this.clickDownloadEvent(btnDownload, statusIds);
          if (this.showSensitive) {
            article.querySelector(
              'div[aria-labelledby] div[role="button"][tabindex="0"]:not([data-testid]) > div[dir] > span > span'
            )?.click();
          }
        }
      }
      const imgs = article.querySelectorAll('a[href*="/photo/"]');
      if (imgs.length > 1) {
        imgs.forEach((img) => {
          const imgStatusId = this.extractStatusId(img.href);
          if (!imgStatusId || img.parentNode.querySelector(".x-master-dl.tmd-img"))
            return;
          const btnDownload = document.createElement("div");
          btnDownload.style.cssText = "position: absolute; top: 0; right: 0; z-index: 10; margin: 5px;";
          btnDownload.innerHTML = `<div><div><svg viewBox="0 0 20 20" width="15" height="15">${this.svg}</svg></div></div>`;
          this.setStatus(
            btnDownload,
            ["x-master-dl", "tmd-img", "download"],
            "下载图片"
          );
          img.parentNode.appendChild(btnDownload);
          btnDownload.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            this.clickDownloadEvent(btnDownload, [imgStatusId]);
          };
        });
      }
    },
    addButtonToMedia: function(listitems) {
      listitems.forEach((li) => {
        if (li.dataset.detected)
          return;
        li.dataset.detected = "true";
        const statusElement = li.querySelector('a[href*="/status/"]');
        const statusId = statusElement ? this.extractStatusId(statusElement.href) : null;
        if (!statusId)
          return;
        const btnDownload = document.createElement("div");
        btnDownload.innerHTML = `<div><div><svg viewBox="0 0 20 20" width="15" height="15">${this.svg}</svg></div></div>`;
        this.setStatus(
          btnDownload,
          ["x-master-dl", "tmd-media", "download"],
          "下载媒体"
        );
        li.appendChild(btnDownload);
        btnDownload.onclick = () => this.clickDownloadEvent(btnDownload, [statusId]);
      });
    },
    detect: function(node) {
      const article = node.tagName === "ARTICLE" && node || node.tagName === "DIV" && (node.querySelector("article") || node.closest("article"));
      if (article) {
        this.addButtonTo(article);
      }
      const listitems = node.tagName === "LI" && node.getAttribute("role") === "listitem" ? [node] : node.tagName === "DIV" && node.querySelectorAll('li[role="listitem"]');
      if (listitems) {
        this.addButtonToMedia(listitems);
      }
    }
  };
  const XDownload = {
    mediaMap: /* @__PURE__ */ new Map(),
    findParent: function(obj, targetKey) {
      let result = [];
      if (typeof obj === "object" && obj !== null) {
        for (let key in obj) {
          if (obj.hasOwnProperty(key)) {
            if (key === targetKey) {
              result.push(obj);
            }
            result = result.concat(this.findParent(obj[key], targetKey));
          }
        }
      } else if (Array.isArray(obj)) {
        for (let item of obj) {
          result = result.concat(this.findParent(item, targetKey));
        }
      }
      return result;
    },
    extractMediaFromResponse: function(url, responseText) {
      try {
        const data = JSON.parse(responseText);
        const entities = this.findParent(data, "extended_entities");
        if (entities.length === 0) {
          return;
        }
        for (let entity of entities) {
          if (!entity.extended_entities)
            continue;
          const entityId = entity.id_str || entity.conversation_id_str;
          (entity.extended_entities.media || []).filter((m) => ["video", "animated_gif", "photo"].includes(m.type)).forEach((m) => {
            const bestVideo = m.video_info?.variants?.filter((v) => v.content_type === "video/mp4").sort((a, b) => b.bitrate - a.bitrate)[0];
            const text = (entity.full_text || "").split("https://t.co")[0]?.trim()?.slice(0, 50) || entityId;
            const mediaItem = {
              entityId,
              id: m.id_str,
              thumbnail: m.media_url_https?.split(".jpg")[0],
              video: bestVideo?.url,
              photo: m.media_url_https,
              text
            };
            this.mediaMap.set(entityId, mediaItem);
          });
        }
      } catch (e) {
      }
    },
    hookXMLHttpRequest: function() {
      const self = this;
      const originalOpen = XMLHttpRequest.prototype.open;
      const originalSend = XMLHttpRequest.prototype.send;
      XMLHttpRequest.prototype.open = function(method, url) {
        this._url = url;
        return originalOpen.apply(this, arguments);
      };
      XMLHttpRequest.prototype.send = function() {
        this.addEventListener("load", function() {
          if (this._url && this.response) {
            try {
              if (this.responseType === "" || this.responseType === "text") {
                self.extractMediaFromResponse(this._url, this.responseText);
              }
            } catch (e) {
            }
          }
        });
        return originalSend.apply(this, arguments);
      };
    },
    init: function() {
      this.hookXMLHttpRequest();
      GM_addStyle(
        css_248z + (this.showSensitive ? css_248z$1 : "")
      );
    }
  };
  const X = {
    start: function() {
      XDownload.init();
      XSettingsDialog.init();
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === 1) {
              XDownloadUI.detect(node);
              XDateFormat.repldatetime();
            }
          });
        });
      });
      observer.observe(document.body, {
        childList: true,
        subtree: true
      });
    }
  };

  (function() {
    const enabled = GM_getValue("x_simplify_mode", "") === "true";
    if (!enabled) {
      return;
    }
    function update() {
      const width = Math.min(document.documentElement.offsetWidth || 800, 800);
      if (window.innerWidth === width && document.documentElement.clientWidth === width) {
        return;
      }
      window.__defineGetter__("innerWidth", () => width);
      document.documentElement.__defineGetter__("clientWidth", () => width);
      if (window.visualViewport) {
        window.visualViewport.__defineGetter__("width", () => width);
      }
      window.dispatchEvent(new Event("resize"));
      if (window.visualViewport) {
        window.visualViewport.dispatchEvent(new Event("resize"));
      }
    }
    window.addEventListener("load", update);
    window.addEventListener("resize", update);
    if (window.visualViewport) {
      window.visualViewport.addEventListener("resize", update);
    }
    document.addEventListener("visibilitychange", update);
    GM_addStyle(`
    #react-root main {
      -webkit-flex-grow: 1 !important;
      flex-grow: 1 !important;
    }

    @media (min-width: 800px) {
      [role='listbox'] {
        max-width: 500px !important;
      }
    }
  `);
    update();
  })();

  const Init = {
    async start() {
      X.start();
    }
  };
  Init.start();

}());