iOS App Download Link Extractor

Extracts the IPA download link from itms-services URLs or displays an error message, shown next to the original button.

  1. // ==UserScript==
  2. // @name iOS App Download Link Extractor
  3. // @namespace https://github.com/WangONC/ios-app-download-link-extractor
  4. // @version 0.9.0
  5. // @description Extracts the IPA download link from itms-services URLs or displays an error message, shown next to the original button.
  6. // @author WangONC
  7. // @source https://github.com/WangONC/ios-app-download-link-extractor
  8. // @match *://*/*
  9. // @grant GM.xmlHttpRequest
  10. // @license MIT
  11.  
  12. // @name:en iOS App Download Link Extractor
  13. // @description:en Extracts the IPA download link from itms-services URLs or displays an error message, shown next to the original button.
  14. // @name:zh-CN IPA提取助手
  15. // @description:zh-CN 从 itms-services 链接中提取 IPA 下载直链或显示错误提示,并显示在原始按钮旁边
  16. // @name:zh-TW IPA提取助手
  17. // @description:zh-TW 從 itms-services 連結中提取 IPA 下載直鍊或顯示錯誤提示,並顯示在原始按鈕旁邊
  18. // @name:ar مُستخرج رابط تنزيل تطبيقات iOS
  19. // @description:ar يستخرج رابط تنزيل IPA من عناوين itms-services أو يعرض رسالة خطأ، تُظهر بجانب الزر الأصلي
  20. // @name:bg Екстрактор на връзки за изтегляне на iOS приложения
  21. // @description:bg Извлича връзката за изтегляне на IPA от itms-services URL-и или показва съобщение за грешка до оригиналния бутон
  22. // @name:ckb دەرهێنەری بەستەری داگرتنی ئەپی iOS
  23. // @description:ckb بەستەری داگرتنی IPA لە itms-services URLەکان دەرهێنەرە یان پەیامی هەڵە نیشان دەدات، کە لە تەنیشتی دوگمەی سەرەکی دەردەکەوێت
  24. // @name:cs Extraktor odkazů ke stažení aplikací pro iOS
  25. // @description:cs Extrahuje odkaz na stažení IPA z adres itms-services nebo zobrazí chybovou zprávu vedle původního tlačítka
  26. // @name:da iOS App Download Link Extractor
  27. // @description:da Uddrager IPA-downloadlinket fra itms-services URL'er eller viser en fejlmeddelelse ved siden af den originale knap
  28. // @name:de iOS App Download Link Extractor
  29. // @description:de Extrahiert den IPA-Download-Link aus itms-services URLs oder zeigt eine Fehlermeldung neben dem ursprünglichen Button an
  30. // @name:el Εξαγωγέας Συνδέσμων Λήψης Εφαρμογών iOS
  31. // @description:el Εξάγει τον σύνδεσμο λήψης IPA από URLs itms-services ή εμφανίζει ένα μήνυμα σφάλματος δίπλα στο αρχικό κουμπί
  32. // @name:eo Eltiraĵo de Elŝuta Ligilo por iOS-Aplikoj
  33. // @description:eo Eltiras la IPA-elŝutan ligilon el itms-services URL-oj aŭ montras erarmesaĝon apud la originala butono
  34. // @name:es Extractor de Enlaces de Descarga de Aplicaciones iOS
  35. // @description:es Extrae el enlace de descarga de IPA de las URLs de itms-services o muestra un mensaje de error junto al botón original
  36. // @name:es-419 Extractor de Enlaces de Descarga de Aplicaciones iOS
  37. // @description:es-419 Extrae el enlace de descarga de IPA de las URLs de itms-services o muestra un mensaje de error junto al botón original
  38. // @name:fi iOS-sovelluksen latauslinkin poimija
  39. // @description:fi Puraisee IPA-latauslinkin itms-services-URL-osoitteista tai näyttää virheilmoituksen alkuperäisen painikkeen vieressä
  40. // @name:fr Extracteur de liens de téléchargement d'applications iOS
  41. // @description:fr Extrait le lien de téléchargement IPA des URL itms-services ou affiche un message d'erreur à côté du bouton d'origine
  42. // @name:fr-CA Extracteur de liens de téléchargement d'applications iOS
  43. // @description:fr-CA Extrait le lien de téléchargement IPA des URL itms-services ou affiche un message d'erreur à côté du bouton d'origine
  44. // @name:he מחלץ קישורי הורדה לאפליקציות iOS
  45. // @description:he מחלץ את קישור ההורדה של IPA מכתובות itms-services או מציג הודעת שגיאה ליד הכפתור המקורי
  46. // @name:hr Izvlači poveznice za preuzimanje iOS aplikacija
  47. // @description:hr Izvlači poveznicu za preuzimanje IPA iz itms-services URL-ova ili prikazuje poruku o grešci pored originalnog gumba
  48. // @name:hu iOS App Letöltési Link Kivonó
  49. // @description:hu Kinyeri az IPA letöltési linket az itms-services URL-ekből vagy hibaüzenetet jelenít meg az eredeti gomb mellett
  50. // @name:id Pengekstrak Tautan Unduhan Aplikasi iOS
  51. // @description:id Mengekstrak tautan unduhan IPA dari URL itms-services atau menampilkan pesan kesalahan di samping tombol asli
  52. // @name:it Estrattore di Link di Download per App iOS
  53. // @description:it Estrae il link di download IPA dagli URL itms-services o visualizza un messaggio di errore accanto al pulsante originale
  54. // @name:ja iOSアプリダウンロードリンク抽出ツール
  55. // @description:ja itms-services URLからIPAダウンロードリンクを抽出するか、エラーメッセージを元のボタンの横に表示します
  56. // @name:ka iOS აპლიკაციის ჩამოტვირთვის ბმულის ამომღები
  57. // @description:ka ამოიღებს IPA-ს ჩამოტვირთვის ბმულს itms-services URL-ებიდან ან აჩვენებს შეცდომის შეტყობინებას ორიგინალური ღილაკის გვერდით
  58. // @name:ko iOS 앱 다운로드 링크 추출기
  59. // @description:ko itms-services URL에서 IPA 다운로드 링크를 추출하거나 오류 메시지를 원래 버튼 옆에 표시합니다
  60. // @name:mr iOS अॅप डाउनलोड लिंक एक्स्ट्रॅक्टर
  61. // @description:mr itms-services URL मधून IPA डाउनलोड लिंक काढून किंवा त्रुटी संदेश दाखवून, मूळ बटणाजवळ दर्शविलेला
  62. // @name:nb iOS App Nedlastingslenke Ekstraktor
  63. // @description:nb Trekker ut IPA-nedlastingslenken fra itms-services URL-er eller viser en feilmelding ved siden av den opprinnelige knappen
  64. // @name:nl iOS App Download Link Extractor
  65. // @description:nl Extraheert de IPA-downloadlink van itms-services URL's of toont een foutmelding naast de originele knop
  66. // @name:pl Ekstraktor linków do pobierania aplikacji iOS
  67. // @description:pl Wyciąga link do pobrania IPA z adresów URL itms-services lub wyświetla komunikat o błędzie obok oryginalnego przycisku
  68. // @name:pt Extrator de Link de Download de Aplicativos iOS
  69. // @description:pt Extrai o link de download do IPA de URLs itms-services ou exibe uma mensagem de erro ao lado do botão original
  70. // @name:pt-BR Extrator de Link de Download de Aplicativos iOS
  71. // @description:pt-BR Extrai o link de download do IPA de URLs itms-services ou exibe uma mensagem de erro ao lado do botão original
  72. // @name:ro Extragător de Linkuri de Descărcare pentru Aplicații iOS
  73. // @description:ro Extrage linkul de descărcare IPA din URL-urile itms-services sau afișează un mesaj de eroare lângă butonul original
  74. // @name:ru Экстрактор ссылок на скачивание приложений iOS
  75. // @description:ru Извлекает ссылку на скачивание IPA из URL-адресов itms-services или отображает сообщение об ошибке рядом с оригинальной кнопкой
  76. // @name:sk Extraktor odkazov na stiahnutie aplikácií pre iOS
  77. // @description:sk Extrahuje odkaz na stiahnutie IPA z adries itms-services alebo zobrazí chybovú správu vedľa pôvodného tlačidla
  78. // @name:sr Екстрактор веза за преузимање iOS апликација
  79. // @description:sr Екстрахује везу за преузимање IPA из itms-services URL-ова или приказује поруку о грешци поред оригиналног дугмета
  80. // @name:sv iOS App Nedladdningslänk Extraktor
  81. // @description:sv Extraherar IPA-nedladdningslänken från itms-services URL:er eller visar ett felmeddelande bredvid den ursprungliga knappen
  82. // @name:th ตัวดึงลิงค์ดาวน์โหลดแอป iOS
  83. // @description:th ดึงลิงค์ดาวน์โหลด IPA จาก URL itms-services หรือแสดงข้อความผิดพลาดข้างปุ่มต้นฉบับ
  84. // @name:tr iOS Uygulama İndirme Bağlantısı Çıkarıcı
  85. // @description:tr itms-services URL'lerinden IPA indirme bağlantısını çıkarır veya orijinal butonun yanında bir hata mesajı gösterir
  86. // @name:uk Екстрактор посилань для завантаження додатків iOS
  87. // @description:uk Витягує посилання на завантаження IPA з URL-адрес itms-services або відображає повідомлення про помилку поруч з оригінальною кнопкою
  88. // @name:ug iOS ئەپ تۆۋەنلىك ئۇلىنىشىنى چىقىرىش
  89. // @description:ug itms-services URL لىرىدىن IPA چۈشۈرۈش ئۇلىنىشىنى چىقىرىپ ياكى ئەسلى كۇنۇپكا يېنىدا خاتالىق ئۇچۇرىنى كۆرسىتىدۇ
  90. // @name:vi Trích xuất liên kết tải xuống ứng dụng iOS
  91. // @description:vi Trích xuất liên kết tải IPA từ URL itms-services hoặc hiển thị thông báo lỗi bên cạnh nút gốc
  92. // ==/UserScript==
  93.  
  94. (function() {
  95. 'use strict';
  96.  
  97. const MAX_RETRIES = 3; // 最大重试次数
  98. const RETRY_INTERVAL = 1000; // 重试间隔
  99. const TIMEOUT = 500; // 超时时间
  100.  
  101. function fetchPlist(plistUrl, retryCount, link) {
  102. GM.xmlHttpRequest({
  103. method: 'GET',
  104. url: plistUrl,
  105. timeout: TIMEOUT, // 设置超时时间
  106. onload: function(response) {
  107. if (response.status === 200) {
  108. let xmlText = response.responseText;
  109. let parser = new DOMParser();
  110. let xmlDoc = parser.parseFromString(xmlText, 'text/xml');
  111. let downloadUrl = extractDownloadUrl(xmlDoc);
  112. if (downloadUrl) {
  113. let downloadLink = document.createElement('a');
  114. downloadLink.href = downloadUrl;
  115. downloadLink.target = '_blank'; // 新窗口打开,方便下载
  116. downloadLink.textContent = 'Download IPA';
  117. downloadLink.style.marginLeft = '13px';
  118. downloadLink.classList.add('ipa-download-link'); // 添加类名以便识别
  119. link.insertAdjacentElement('afterend', downloadLink);
  120. } else {
  121. showError(link, 'Unable to parse plist file');
  122. }
  123. } else {
  124. showError(link, `Request failed: ${response.status}`);
  125. }
  126. },
  127. onerror: function() {
  128. if (retryCount > 0) {
  129. setTimeout(() => {
  130. fetchPlist(plistUrl, retryCount - 1, link); // 递归重试
  131. console.log(`当前重试 ${retryCount - 1}`);
  132. }, RETRY_INTERVAL);
  133. } else {
  134. showError(link, 'Network Error');
  135. }
  136. },
  137. ontimeout: function() {
  138. if (retryCount > 0) {
  139. setTimeout(() => {
  140. fetchPlist(plistUrl, retryCount - 1, link);
  141. console.log(`当前重试 ${retryCount - 1}`);
  142. }, RETRY_INTERVAL);
  143. } else {
  144. showError(link, 'Request timed out');
  145. }
  146. }
  147. });
  148. }
  149.  
  150. // 处理页面中的链接
  151. function processLinks() {
  152. let links = document.querySelectorAll('a[href^="itms-services://?action=download-manifest&url="]');
  153. if (links.length === 0) {
  154. // console.log('No matching links found');
  155. return;
  156. }
  157.  
  158. for (let link of links) {
  159. // 检查新添加的链接
  160. if (link.nextElementSibling && (link.nextElementSibling.classList.contains('ipa-download-link') || link.nextElementSibling.classList.contains('ipa-error-link'))) {
  161. continue;
  162. }
  163.  
  164. // 检查是否是已经处理过的链接
  165. if (link.getAttribute('ipa-data-processed') === 'true') {
  166. continue;
  167. }
  168.  
  169. // 标记该链接为已处理
  170. link.setAttribute('ipa-data-processed', 'true');
  171.  
  172. try {
  173. let href = link.href;
  174. let query = href.split('?')[1];
  175. if (!query) throw new Error('Invalid link format');
  176. let params = new URLSearchParams(query);
  177. let plistUrl = params.get('url');
  178. if (!plistUrl) throw new Error('No "url" parameter found');
  179.  
  180. // 解码 plistUrl 以处理 URL 编码
  181. plistUrl = decodeURIComponent(plistUrl);
  182.  
  183. fetchPlist(plistUrl, MAX_RETRIES, link);
  184.  
  185. } catch (e) {
  186. showError(link, e.message);
  187. }
  188. }
  189. }
  190.  
  191. // 提取下载链接的核心函数
  192. function extractDownloadUrl(xmlDoc) {
  193. let dict = xmlDoc.querySelector('plist > dict');
  194. if (!dict) return null;
  195.  
  196. let keys = Array.from(dict.children).filter(el => el.tagName === 'key');
  197. let itemsKey = keys.find(key => key.textContent === 'items');
  198. if (!itemsKey) return null;
  199.  
  200. let itemsArray = itemsKey.nextElementSibling;
  201. if (!itemsArray || itemsArray.tagName !== 'array') return null;
  202.  
  203. let firstItem = itemsArray.querySelector('dict');
  204. if (!firstItem) return null;
  205.  
  206. let assetsKey = Array.from(firstItem.children).find(el => el.tagName === 'key' && el.textContent === 'assets');
  207. if (!assetsKey) return null;
  208.  
  209. let assetsArray = assetsKey.nextElementSibling;
  210. if (!assetsArray || assetsArray.tagName !== 'array') return null;
  211.  
  212. let softwarePackageDict = Array.from(assetsArray.children).find(dict => {
  213. let kindKey = Array.from(dict.children).find(key => key.tagName === 'key' && key.textContent === 'kind');
  214. if (kindKey && kindKey.nextElementSibling.textContent === 'software-package') {
  215. return true;
  216. }
  217. return false;
  218. });
  219. if (!softwarePackageDict) return null;
  220.  
  221. let urlKey = Array.from(softwarePackageDict.children).find(el => el.tagName === 'key' && el.textContent === 'url');
  222. if (!urlKey) return null;
  223.  
  224. return urlKey.nextElementSibling.textContent;
  225. }
  226.  
  227. // 显示错误提示的函数
  228. function showError(link, message) {
  229. let errorLink = document.createElement('a');
  230. errorLink.textContent = message;
  231. errorLink.style.color = 'red';
  232. errorLink.style.marginLeft = '13px';
  233. errorLink.style.pointerEvents = 'none'; // 防止点击
  234. errorLink.classList.add('ipa-error-link'); // 添加类名以便识别
  235. link.insertAdjacentElement('afterend', errorLink);
  236. }
  237.  
  238.  
  239. document.addEventListener('DOMContentLoaded', function() {
  240. processLinks();
  241. });
  242.  
  243. window.addEventListener('load', function() {
  244. processLinks();
  245. });
  246.  
  247. // 监听动态内容加载
  248. const observer = new MutationObserver(() => {
  249. processLinks();
  250. });
  251. observer.observe(document.body, { childList: true, subtree: true, attributes: true });
  252. })();