diff --git a/IPA-Installer/IPA-Installer-JSBox.js b/IPA-Installer/IPA-Installer-JSBox.js new file mode 100644 index 0000000..1284aaa --- /dev/null +++ b/IPA-Installer/IPA-Installer-JSBox.js @@ -0,0 +1,144 @@ +/* + * IPA-installer JSBox script. This script is not available stand alone, checkout the demo from TG channel @NobyDa + * + * Modified from https://github.com/axelburks/JSBox/blob/master/IPA%20Installer.js by @NobyDa + */ + +var port_number = 8070 +var plist_url = `itms-services://?action=download-manifest&url=https://nobyda.app/install%3Fclient%3Djsbox%26url%3Dhttp%253A%252F%252F127.0.0.1%253A${port_number}%252Fdownload%253Fpath%253D%25252Fapp.ipa` + +$app.strings = { + "en": { + "starterror": "Not support running in this way", + "ftypeerror": " is not ipa file", + "installtitle": "Installing...", + "installmsg": "\n\nYou can check on Homescreen.\nPlease tap \"Done\" button after finished", + "inerrtitle": "IPA file import error", + "inerrmsg": "Please rerun the script" + }, + "zh-Hans": { + "starterror": "不支持此方式运行!", + "ftypeerror": " 非 ipa 文件!", + "installtitle": "正在安装…", + "installmsg": "\n\n可前往桌面查看安装进度\n完成后请点击\"Done\"按钮", + "inerrtitle": "IPA文件导入失败", + "inerrmsg": "请重新运行此脚本" + } +} + +// 从应用内启动 +if ($app.env == $env.app) { + $drive.open({ + handler: function(data) { + fileCheck(data) + } + }) +} +// 从 Action Entension 启动 +else if ($app.env == $env.action) { + fileCheck($context.data) +} + +else { + $ui.error($l10n("starterror")) + delayClose(2) +} + + +function startServer(port) { + $http.startServer({ + port: port, + path: "", + handler: function(result) { + console.info(result.url) + } + }) +} + +function fileCheck(data) { + if (data && data.fileName) { + var fileName = data.fileName; + if (fileName.indexOf(".ipa") == -1) { + $ui.error(fileName + $l10n("ftypeerror")) + delayClose(2) + } else { + install(fileName, data); + } + } +} + +function install(fileName, file) { + var result = $file.write({ + data: file, + path: "app.ipa" + }) + if (result) { + startServer(port_number) + $location.startUpdates({ + handler: function(resp) { + console.info(resp.lat + " " + resp.lng + " " + resp.alt) + } + }) + var preResult = $app.openURL(plist_url); + if (preResult) { + $ui.alert({ + title: $l10n("installtitle"), + message: "\n" + fileName + $l10n("installmsg"), + actions: [{ + title: "Cancel", + style: "Cancel", + handler: function() { + $http.stopServer() + $file.delete("app.ipa") + delayClose(0.2) + } + }, + { + title: "Done", + handler: function() { + $http.stopServer() + $file.delete("app.ipa") + delayClose(0.2) + } + }] + }) + } else { + $ui.alert({ + title: "Open itms-services scheme failed", + message: "Please rerun the script or restart device", + actions: [ + { + title: "OK", + handler: function() { + delayClose(0.2) + } + }] + }) + } + } else { + $ui.alert({ + title: $l10n("inerrtitle"), + message: $l10n("inerrmsg"), + actions: [{ + title: "OK", + style: "Cancel", + handler: function() { + delayClose(0.2) + } + }] + }) + } +} + +function delayClose(time) { + $location.stopUpdates() + $thread.main({ + delay: time, + handler: function() { + if ($app.env == $env.action || $app.env == $env.safari) { + $context.close() + } + $app.close() + } + }) +} diff --git a/IPA-Installer/IPA-Installer-Pythonista.py b/IPA-Installer/IPA-Installer-Pythonista.py new file mode 100644 index 0000000..a929058 --- /dev/null +++ b/IPA-Installer/IPA-Installer-Pythonista.py @@ -0,0 +1,74 @@ +# IPA-installer pythonista script. This script is not available stand alone, checkout the demo from TG channel @NobyDa +# +# Modified from https://github.com/axelburks/Pythonista/blob/master/IPA%20Installer.py by @NobyDa + +import os, appex, console, shutil, http.server, webbrowser, time +from os import path +from threading import Thread + +port_number = 8090 +plist_url = f'itms-services://?action=download-manifest&url=https://nobyda.app/install%3Fclient%3Dpythonista%26url%3Dhttp%253A%252F%252F127.0.0.1%253A{port_number}%252Fipa%252Fapp.ipa' +save_dir = path.expanduser('./ipa') +if not path.exists(save_dir): + os.makedirs(save_dir) + +httpd = None +def startServer(port): + Handler = http.server.SimpleHTTPRequestHandler + + global httpd + httpd = http.server.HTTPServer(("", port), Handler) + + print("Start server at port", port) + httpd.serve_forever() + +def start(port): + thread = Thread(target=startServer, args=[port]) + thread.start() + + startTime = int(time.time()) + while not httpd: + if int(time.time()) > startTime + 60: + print("Time out") + break + return httpd + +def stop(): + if httpd: + httpd.shutdown() + +def main(): + if appex.is_running_extension(): + get_path = appex.get_file_path() + file_name = path.basename(get_path) + file_ext = path.splitext(file_name)[-1] + if file_ext == '.ipa': + dstpath = path.join(save_dir, 'app.ipa') + try: + shutil.copy(get_path, dstpath) + + except Exception as eer: + print(eer) + console.hud_alert('导入失败!','error',1) + start(port_number) + if httpd: + webbrowser.open(plist_url) + try: + finish = console.alert(file_name, '\n正在安装...请返回桌面查看进度...\n\n安装完成后请返回点击已完成','已完成', hide_cancel_button=False) + if finish == 1: + stop() + shutil.rmtree('./ipa') + print("Server stopped") + except: + stop() + shutil.rmtree('./ipa') + print("Cancelled") + appex.finish() + else: + console.hud_alert('非 ipa 文件无法导入安装', 'error', 2) + appex.finish() + else: + console.hud_alert('请在分享扩展中打开本脚本','error',2) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/IPA-Installer/IPA-Installer.js b/IPA-Installer/IPA-Installer.js new file mode 100644 index 0000000..38d47c7 --- /dev/null +++ b/IPA-Installer/IPA-Installer.js @@ -0,0 +1,146 @@ +/* + * iOS IPA应用辅助安装脚本. + * + * 兼容: QuantumultX、Surge5,Loon、Shadowrocket、Stash + * 作者: @NobyDa + * + * 快捷指令 + Shu配合安装: + * 导入IPA文件至Shu -> Shu长按IPA文件 -> 导出文件 -> WiFi传输 -> 本机 -> 系统共享 -> 分享至IPA-Installer快捷指令 + * + * 快捷指令 + JSBox/Pythonista配合安装: + * IPA文件长按分享至IPA-Installer快捷指令,完成后再分享至Jsbox/pythonista分享扩展. + * + * + * QuanX重写: https://raw.githubusercontent.com/NobyDa/Script/master/QuantumultX/IPA-Installer.snippet + * + * Surge模块: https://raw.githubusercontent.com/NobyDa/Script/master/Surge/Module/IPA_install.sgmodule + * + * loon插件: https://raw.githubusercontent.com/NobyDa/Script/master/Loon/Loon_IPA_Installer.plugin + * + * Stash覆写: https://raw.githubusercontent.com/NobyDa/Script/master/Stash/IPA-Installer.stoverride + * + * 快捷指令: https://www.icloud.com/shortcuts/a89979261837465aa3b4cae3527cf5f9 + * + * JSBox脚本: https://xteko.com/redir?url=https%3A%2F%2Fraw.githubusercontent.com%2FNobyDa%2FScript%2Fmaster%2FIPA-Installer%2FIPA-Installer-JSBox.js&name=IPA%20Installer%20%28NobyDa%29 + * + * Pythonista脚本: https://github.com/NobyDa/Script/blob/master/IPA-Installer/IPA-Installer-Pythonista.py + */ + +const $ = new compatible_tool(); + +(async function () { + const args = urlArgs($request.url); + const plist = ` + + + + items + + + assets + + + kind + software-package + url + https://nobyda.app/download?url=${encodeURIComponent(args.url)} + + + metadata + + bundle-identifier + ${args.bundleId || $.read("nobyda_ipa_bundle_id") || "*"} + bundle-version + 1.0 + kind + software + title + IPA + + + + +`; + if ($request.url.includes("/install?")) { + if (args.bundleId) { + $.write(args.bundleId, "nobyda_ipa_bundle_id"); + }; + $.resp = { response: { status: 200, body: args.client && plist || "{}" } }; + } else { + if ($request.method == "GET") { + const size = await ipaSize(args.url); + $.notify(`IPA Installer`, ``, size && `Installing IPA, Size: ${size} MB` || `HTTP local server read failed!`); + } + $.resp = { response: { status: 307, headers: { Location: args.url }, body: "{}" } }; + } +})() + .catch((e) => $.notify(`IPA Installer`, ``, `ERROR: ${e.message || e}\nPATH: ${e.stack}`)) + .finally(() => $.done($.resp)) + + +function ipaSize(url) { + return new Promise((r, e) => { + $.http({ method: "head", url: url, policy: "DIRECT", }, (e, h, d) => { + r(h && h.status == 200 && `${((h.headers["Content-Length"] || 0) / 1000 / 1000).toFixed(2)}`) + }); + setTimeout(() => r(), 1000) + }); +} + +function urlArgs(str) { + return Object.fromEntries( + (str.startsWith("http") && str.split("?")[1] || str).split("&") + .map((item) => item.split("=")) + .map(([k, v]) => [k, decodeURIComponent(v)]) + ); +} + +function compatible_tool() { + const isSurge = typeof $httpClient != "undefined"; + const isQuanX = typeof $task != "undefined"; + const isStash = typeof $environment == "object" && $environment["stash-version"]; + const adapterStatus = (response) => { + if (response && response.statusCode) { + response.status = response.statusCode; + } + return response + }; + this.read = (key) => { + if (isQuanX) return $prefs.valueForKey(key); + if (isSurge) return $persistentStore.read(key); + }; + this.write = (value, key) => { + if (isQuanX) return $prefs.setValueForKey(value, key); + if (isSurge) return $persistentStore.write(value, key); + }; + this.notify = (title, subtitle, message) => { + if (isQuanX) $notify(title, subtitle, message); + if (isSurge) $notification.post(title, subtitle, message); + }; + this.http = (options, callback) => { + if (options.policy) { + options.node = options.policy; + options.opts = { policy: options.policy }; + if (isStash) options.headers = { + ...options.headers, + ...{ "X-Stash-Selected-Proxy": encodeURIComponent(options.policy) } + }; + } + if (isQuanX) { + $task.fetch(options).then(response => { + callback(null, adapterStatus(response), response.body) + }, reason => callback(reason.error, null, null)) + } + if (isSurge) { + $httpClient[options.method](options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + } + }; + this.done = (value = {}) => { + if (value.response && isQuanX) { + value.response.status = `HTTP/1.1 ${value.response.status}`; + } + $done((value.response && isQuanX) ? value.response : value) + } +}; \ No newline at end of file diff --git a/Loon/Loon_IPA_Installer.plugin b/Loon/Loon_IPA_Installer.plugin new file mode 100644 index 0000000..47c5531 --- /dev/null +++ b/Loon/Loon_IPA_Installer.plugin @@ -0,0 +1,12 @@ +#!name=IPA应用辅助安装器 +#!desc=该模块可在iOS端辅助安装商店版或已签名IPA(需使用快捷指令 + Shu/Jsbox/pythonista), 查看脚本注释以了解具体方法; 安装演示可查看TG频道 @NobyDa +#!author=NobyDa +#!homepage=https://github.com/NobyDa/Script/tree/master +#!icon=https://cdn.jsdelivr.net/gh/NobyDa/mini@master/Color/Apple.png + + +[Script] +http-request ^https:\/\/nobyda.app/(install|download) requires-body=true, script-path=https://raw.githubusercontent.com/NobyDa/Script/master/IPA-Installer/IPA-Installer.js, timeout=10, tag=IPA-Installer + +[MITM] +hostname = nobyda.app \ No newline at end of file diff --git a/QuantumultX/IPA-Installer.snippet b/QuantumultX/IPA-Installer.snippet new file mode 100644 index 0000000..d8a3b12 --- /dev/null +++ b/QuantumultX/IPA-Installer.snippet @@ -0,0 +1,6 @@ +# 该文件为 "IPA应用辅助安装脚本" QuantumultX远端重写资源. +# 该资源可在iOS端辅助安装商店版或已签名IPA(需使用快捷指令 + Shu/Jsbox/pythonista), 查看脚本注释以了解具体方法; 安装演示可查看TG频道 @NobyDa + +^https:\/\/nobyda.app/(install|download) url script-analyze-echo-response https://raw.githubusercontent.com/NobyDa/Script/master/IPA-Installer/IPA-Installer.js + +hostname = nobyda.app \ No newline at end of file diff --git a/Stash/IPA-Installer.stoverride b/Stash/IPA-Installer.stoverride new file mode 100644 index 0000000..29dd322 --- /dev/null +++ b/Stash/IPA-Installer.stoverride @@ -0,0 +1,16 @@ +name: IPA应用辅助安装器 +desc: 该模块可在iOS端辅助安装商店版或已签名IPA(需使用快捷指令 + Shu/Jsbox/pythonista), 查看脚本注释以了解具体方法; 安装演示可查看TG频道 @NobyDa + +http: + mitm: + - nobyda.app + script: + - match: ^https:\/\/nobyda.app/(install|download) + name: IPA-Installer + type: request + require-body: true + timeout: 10 +script-providers: + IPA-Installer: + url: https://raw.githubusercontent.com/NobyDa/Script/master/IPA-Installer/IPA-Installer.js + interval: 86400 \ No newline at end of file diff --git a/Surge/Module/IPA_install.sgmodule b/Surge/Module/IPA_install.sgmodule index b801d39..a9a707e 100644 --- a/Surge/Module/IPA_install.sgmodule +++ b/Surge/Module/IPA_install.sgmodule @@ -1,8 +1,9 @@ -#!name=iOS14 IPA应用安装 -#!desc=该模块可解决iOS14或IPadOS14无法在移动端安装IPA的问题. 注: 需要使用Shu+捷径或Jsbox辅助安装. +#!name=IPA应用辅助安装器 +#!desc=该模块可在iOS端辅助安装商店版或已签名IPA(需使用快捷指令 + Shu/Jsbox/pythonista), 查看脚本注释以了解具体方法; 安装演示可查看TG频道 @NobyDa + [Script] -IPA应用安装 = type=http-request,pattern=^https:\/\/nobyda/(install|download)(\/jsbox)?$,requires-body=1,max-size=0,script-path=https://raw.githubusercontent.com/NobyDa/Script/master/Surge/JS/IPA_install.js +IPA Installer = type=http-request,pattern=^https:\/\/nobyda.app/(install|download),requires-body=1,script-path=https://raw.githubusercontent.com/NobyDa/Script/master/IPA-Installer/IPA-Installer.js [MITM] -hostname = %APPEND% nobyda \ No newline at end of file +hostname = %APPEND% nobyda.app \ No newline at end of file