├── 10010 └── 10010.js ├── 10086 └── 10086.js ├── Apple └── AppleRepair.js ├── Bilibili └── BilibiliMonitor.js ├── Checkin ├── Checkin.js ├── Dler Cloud.js └── checkin_example.json ├── Config.js ├── Config.scriptable ├── CountDown └── CountDown.js ├── Douban └── DoubanMonitor.js ├── Env.js ├── Env.scriptable ├── EnvExample.js ├── EnvExample.scriptable ├── Install Scripts.js ├── NodeQuery └── NodeQuery.js ├── README.md ├── READMEEN.md ├── RRShare └── RRShareMonitor.js ├── RSS └── RSSMonitor.js ├── SNKRS └── SNKRS.js ├── Weibo └── WeiboMonitor.js ├── Zhihu └── ZhihuMonitor.js ├── crypto-js.min.js └── v2ex ├── hot.js └── latest.js /10010/10010.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: cyan; icon-glyph: mobile-alt; 4 | /** 5 | * Author: GideonSenku evilbutcher 6 | * Github: https://github.com/GideonSenku 7 | */ 8 | 9 | const $ = importModule("Env"); 10 | 11 | const prefix = "boxjs.net"; //修改成你用的域名 12 | 13 | const title = '联通5G' 14 | const preview = 'small' // 预览大小 可选:small,medium,large 15 | const spacing = 5 // 间隙大小 16 | // option1 manual 17 | 18 | const tel = `` // 填入你的电话号码 19 | 20 | const VAL_loginheader = `` // 填入来自BoxJs的数据 21 | 22 | // option2 auto getdata from BoxJS 23 | $.KEY_signheader = "chavy_signheader_10010"; 24 | $.KEY_loginheader = "chavy_tokenheader_10010"; 25 | 26 | $.Val_signheader = await getdata($.KEY_signheader); 27 | $.Val_loginheader = await getdata($.KEY_loginheader); 28 | 29 | const res = await getinfo() 30 | await render() 31 | 32 | async function render() { 33 | // create and show widget 34 | if (config.runsInWidget) { 35 | let widget = await createWidget(res) 36 | Script.setWidget(widget) 37 | Script.complete() 38 | } else { 39 | await createWidget(res) 40 | } 41 | } 42 | 43 | async function createWidget(res) { 44 | const signinfo = res; 45 | if (signinfo.code == "Y") { 46 | // 基本信息 47 | const traffic = signinfo.data.dataList[0]; 48 | const flow = signinfo.data.dataList[1]; 49 | const voice = signinfo.data.dataList[2]; 50 | const credit = signinfo.data.dataList[3]; 51 | const back = signinfo.data.dataList[4]; 52 | const money = signinfo.data.dataList[5]; 53 | 54 | 55 | $.traffic = `[${traffic.remainTitle}]${traffic.number}${traffic.unit}` 56 | $.flow = `[${flow.remainTitle}]${flow.number}${flow.unit}` 57 | $.voice = `[${voice.remainTitle}]${voice.number}${voice.unit}` 58 | $.credit = `[${credit.remainTitle}]${credit.number}${credit.unit}` 59 | $.back = `[${back.remainTitle}]${back.number}${back.unit}` 60 | const opts = { 61 | title, 62 | texts: { 63 | traffic: $.traffic, 64 | flow: $.flow, 65 | voice: $.voice, 66 | credit: $.credit, 67 | back: $.back, 68 | updateTime: 'true', 69 | battery: 'true' 70 | }, 71 | preview, 72 | spacing 73 | } 74 | let widget = await $.createWidget(opts); 75 | return widget 76 | 77 | } 78 | } 79 | 80 | async function getinfo() { 81 | const telNum = $.VAL_signheader ? gettel() : tel; 82 | const loginheader = $.Val_loginheader ? $.Val_loginheader : VAL_loginheader; 83 | const url = { 84 | url: `https://m.client.10010.com/mobileService/home/queryUserInfoSeven.htm?version=iphone_c@7.0403&desmobiel=${telNum}&showType=3`, 85 | headers: { 86 | Cookie: JSON.parse(loginheader)["Cookie"], 87 | }, 88 | }; 89 | const res = await $.get(url); 90 | return res; 91 | } 92 | 93 | function gettel() { 94 | const reqheaders = JSON.parse($.VAL_signheader); 95 | const reqreferer = reqheaders.Referer; 96 | const reqCookie = reqheaders.Cookie; 97 | let tel = ""; 98 | if (reqreferer.indexOf(`desmobile=`) >= 0) 99 | tel = reqreferer.match(/desmobile=(.*?)(&|$)/)[1]; 100 | if (tel == "" && reqCookie.indexOf(`u_account=`) >= 0) 101 | tel = reqCookie.match(/u_account=(.*?);/)[1]; 102 | return tel; 103 | } 104 | 105 | async function getdata(key) { 106 | const url = `http://${prefix}/query/boxdata`; 107 | const boxdata = await $.get({ url }); 108 | if (boxdata.datas[key]) { 109 | return boxdata.datas[key]; 110 | } else { 111 | return undefined; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /10086/10086.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-green icon-glyph: mobile 4 | 5 | /** 6 | * Author: GideonSenku,Chavyleung,wangfei021325 7 | * Github: https://github.com/GideonSenku 8 | */ 9 | 10 | const prefix = "boxjs.net" //修改成你用的域名 11 | const title = '移动5G' 12 | const preview = 'small' // 预览大小 可选:small,medium,large 13 | const spacing = 5 14 | const $ = importModule("Env") 15 | 16 | // 修改为BoxJS中的chavy_autologin_cmcc数据,或者抓包填入一个request对象 17 | const chavy_autologin_cmcc = `` 18 | 19 | // 修改为BoxJS中的chavy_getfee_cmcc数据,或者抓包填入一个request对象 20 | const chavy_getfee_cmcc = `` 21 | 22 | $.KEY_autologin = "chavy_autologin_cmcc" 23 | 24 | $.KEY_getfee = "chavy_getfee_cmcc" 25 | 26 | async function getdata(key) { 27 | const url = `http://${prefix}/query/boxdata` 28 | const boxdata = await $.get({ url }) 29 | if (boxdata.datas[key]) { 30 | return boxdata.datas[key] 31 | } else { 32 | return undefined 33 | } 34 | } 35 | 36 | const crypto = { 37 | moduleName: "crypto-js", 38 | url: 39 | "https://raw.githubusercontent.com/GideonSenku/Scriptable/master/crypto-js.min.js", 40 | } 41 | 42 | !(async () => { 43 | $.CryptoJS = $.require(crypto) 44 | $.autologin = await getdata($.KEY_autologin) 45 | $.getfee = await getdata($.KEY_getfee) 46 | await loginapp() 47 | await queryfee() 48 | await querymeal() 49 | await showmsg() 50 | await render() 51 | })().catch((e) => $.logErr(e)) 52 | 53 | function loginapp() { 54 | return new Promise((resolve) => { 55 | const url = $.autologin 56 | ? JSON.parse($.autologin) 57 | : JSON.parse(chavy_autologin_cmcc) 58 | $.post(url, (resp, data) => { 59 | try { 60 | $.setck = resp.headers["Set-Cookie"] 61 | console.warn("login") 62 | } catch (e) { 63 | $.logErr(e, resp) 64 | } finally { 65 | resolve() 66 | } 67 | }) 68 | }) 69 | } 70 | 71 | function queryfee() { 72 | return new Promise((resolve) => { 73 | const url = $.getfee ? JSON.parse($.getfee) : JSON.parse(chavy_getfee_cmcc) 74 | const body = JSON.parse(decrypt(url.body, "bAIgvwAuA4tbDr9d")) 75 | const cellNum = body.reqBody.cellNum 76 | const bodystr = `{"t":"${$.CryptoJS.MD5( 77 | $.setck 78 | ).toString()}","cv":"9.9.9","reqBody":{"cellNum":"${cellNum}"}}` 79 | url.body = encrypt(bodystr, "bAIgvwAuA4tbDr9d") 80 | url.headers["Cookie"] = $.setck 81 | url.headers["xs"] = $.CryptoJS.MD5( 82 | url.url + "_" + bodystr + "_Leadeon/SecurityOrganization" 83 | ).toString() 84 | 85 | $.post(url, (resp, data) => { 86 | try { 87 | $.fee = JSON.parse(decrypt(data, "GS7VelkJl5IT1uwQ")) 88 | console.warn("fee") 89 | } catch (e) { 90 | $.logErr(e, resp) 91 | } finally { 92 | resolve() 93 | } 94 | }) 95 | }) 96 | } 97 | 98 | function querymeal() { 99 | return new Promise((resolve) => { 100 | const url = $.getfee ? JSON.parse($.getfee) : JSON.parse(chavy_getfee_cmcc) 101 | url.url = 102 | "https://clientaccess.10086.cn/biz-orange/BN/newComboMealResouceUnite/getNewComboMealResource" 103 | const body = JSON.parse(decrypt(url.body, "bAIgvwAuA4tbDr9d")) 104 | const cellNum = body.reqBody.cellNum 105 | const bodystr = `{"t":"${$.CryptoJS.MD5( 106 | $.setck 107 | ).toString()}","cv":"9.9.9","reqBody":{"cellNum":"${cellNum}","tag":"3"}}` 108 | url.body = encrypt(bodystr, "bAIgvwAuA4tbDr9d") 109 | url.headers["Cookie"] = $.setck 110 | url.headers["xs"] = $.CryptoJS.MD5( 111 | url.url + "_" + bodystr + "_Leadeon/SecurityOrganization" 112 | ).toString() 113 | $.post(url, (resp, data) => { 114 | try { 115 | $.meal = JSON.parse(decrypt(data, "GS7VelkJl5IT1uwQ")) 116 | console.warn('meal') 117 | } catch (e) { 118 | $.logErr(e, resp) 119 | } finally { 120 | resolve() 121 | } 122 | }) 123 | }) 124 | } 125 | 126 | function encrypt(str, key) { 127 | return $.CryptoJS.AES.encrypt( 128 | $.CryptoJS.enc.Utf8.parse(str), 129 | $.CryptoJS.enc.Utf8.parse(key), 130 | { 131 | iv: $.CryptoJS.enc.Utf8.parse("9791027341711819"), 132 | mode: $.CryptoJS.mode.CBC, 133 | padding: $.CryptoJS.pad.Pkcs7, 134 | } 135 | ).toString() 136 | } 137 | 138 | function decrypt(str, key) { 139 | return $.CryptoJS.AES.decrypt(str, $.CryptoJS.enc.Utf8.parse(key), { 140 | iv: $.CryptoJS.enc.Utf8.parse("9791027341711819"), 141 | mode: $.CryptoJS.mode.CBC, 142 | padding: $.CryptoJS.pad.Pkcs7, 143 | }).toString($.CryptoJS.enc.Utf8) 144 | } 145 | 146 | function showmsg() { 147 | return new Promise((resolve) => { 148 | $.subt = `[话费] ${$.fee.rspBody.curFee}元` 149 | const res = $.meal.rspBody.qryInfoRsp[0].resourcesTotal 150 | const flowRes = res.find((r) => r.resourcesCode === "04") 151 | const voiceRes = res.find((r) => r.resourcesCode === "01") 152 | if (flowRes) { 153 | const remUnit = flowRes.remUnit === "05" ? "GB" : "MB" 154 | const usedUnit = flowRes.usedUnit === "05" ? "GB" : "MB" 155 | const unit = flowRes.allUnit === "05" ? "GB" : "MB" 156 | $.flowRes = `[流量] ${flowRes.allRemainRes}${remUnit}` 157 | } 158 | if (voiceRes) { 159 | const remUnit = flowRes.remUnit === "01" ? "分钟" : "" 160 | const usedUnit = flowRes.usedUnit === "01" ? "分钟" : "" 161 | const allUnit = "分钟" 162 | $.voiceRes = `[语音] ${voiceRes.allRemainRes}${allUnit}` 163 | } 164 | resolve() 165 | }) 166 | } 167 | 168 | async function render() { 169 | // create and show widget 170 | if (config.runsInWidget) { 171 | let widget = await getWidget() 172 | Script.setWidget(widget) 173 | Script.complete() 174 | } else { 175 | await getWidget() 176 | } 177 | } 178 | 179 | async function getWidget() { 180 | const texts = { 181 | subt: $.subt, 182 | flowRes: $.flowRes, 183 | voiceRes: $.voiceRes, 184 | updateTime: 'true', 185 | battery: 'true' 186 | } 187 | const opts = { 188 | title, 189 | texts, 190 | preview, 191 | spacing 192 | } 193 | let widget = await $.createWidget(opts) 194 | return widget 195 | } -------------------------------------------------------------------------------- /Apple/AppleRepair.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-blue; icon-glyph: wrench; 4 | /** 5 | * Author: GideonSenku 6 | * Github: https://github.com/GideonSenku 7 | */ 8 | 9 | const $ = importModule("Env") 10 | 11 | const title = 'AppleRepair' 12 | const preview = 'medium' // 预览大小 可选:small,medium,large 13 | const spacing = 5 // 间隙大小 14 | 15 | const repairId = 'xxxx' 16 | const serialNumber = 'xxxxx' 17 | 18 | 19 | const repairMetaData = await getinfo() 20 | await render() 21 | 22 | async function render() { 23 | // create and show widget 24 | if (config.runsInWidget) { 25 | let widget = await createWidget(repairMetaData) 26 | Script.setWidget(widget) 27 | Script.complete() 28 | } else { 29 | await createWidget(repairMetaData) 30 | } 31 | } 32 | 33 | async function createWidget(repairMetaData) { 34 | const texts = {} 35 | 36 | repairMetaData.repairDetails.forEach((detail, idx) => { 37 | const date = $.time('MM-dd HH:mm', detail?.time) 38 | const labelName = detail?.status 39 | const Line = `• ${date} ${labelName}` 40 | texts[`text${idx}`] = Line 41 | }) 42 | 43 | texts.battery = 'true' 44 | 45 | const opts = { 46 | title, 47 | texts, 48 | preview, 49 | spacing 50 | } 51 | let widget = await $.createWidget(opts) 52 | return widget 53 | } 54 | 55 | async function getinfo() { 56 | const url = { 57 | url: `https://mysupport.apple.com/api/v1/supportaccount/getRepairStatus?repairId=${repairId}&serialNumber=${serialNumber}`, 58 | headers: { 59 | 'Cookie': 'SA-Locale=zh_CN;' 60 | }, 61 | } 62 | const { data } = await $.get(url) 63 | return data?.repairMetaData 64 | } -------------------------------------------------------------------------------- /Bilibili/BilibiliMonitor.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: pink; icon-glyph: heartbeat; 4 | /* 5 | * Author: evilbutcher 6 | * Github: https://github.com/evilbutcher 7 | * 本脚本使用了@Gideon_Senku的Env.scriptable,感谢! 8 | */ 9 | const goupdate = true; 10 | const $ = importModule("Env"); 11 | var rid = 0; //rid对应不同的B站榜单:0全站,1动画,3音乐,4游戏,5娱乐,36科技,119鬼畜,129舞蹈。 12 | var num = 6; //自定义显示数量 13 | var rancolor = true; //true为开启随机颜色 14 | 15 | try { 16 | var { bilibili, blnum, blrancolor } = importModule("Config"); 17 | rid = bilibili(); 18 | num = blnum(); 19 | rancolor = blrancolor(); 20 | console.log("将使用配置文件内B站配置"); 21 | } catch (e) { 22 | console.log("将使用脚本内B站配置"); 23 | } 24 | 25 | const res = await getinfo(); 26 | 27 | let widget = createWidget(res); 28 | Script.setWidget(widget); 29 | Script.complete(); 30 | 31 | function createWidget(res) { 32 | var group = res.data; 33 | items = []; 34 | for (var i = 0; i < num; i++) { 35 | var item = group[i].title; 36 | items.push(item); 37 | } 38 | console.log(items); 39 | 40 | const w = new ListWidget(); 41 | const bgColor = new LinearGradient(); 42 | bgColor.colors = [new Color("#1c1c1c"), new Color("#29323c")]; 43 | bgColor.locations = [0.0, 1.0]; 44 | w.backgroundGradient = bgColor; 45 | w.addSpacer(); 46 | w.spacing = 5; 47 | 48 | const firstLine = w.addText(`💗B站榜单`); 49 | firstLine.textSize = 15; 50 | firstLine.textColor = Color.white(); 51 | firstLine.textOpacity = 0.7; 52 | 53 | for (var i = 0; i < items.length; i++) { 54 | addTextToListWidget(`• ${items[i]}`, w); 55 | } 56 | 57 | w.addSpacer(); 58 | w.spacing = 5; 59 | w.presentSmall(); 60 | return w; 61 | } 62 | 63 | async function getinfo() { 64 | const blRequest = { 65 | url: `https://app.bilibili.com/x/v2/rank/region?rid=${rid}`, 66 | }; 67 | const res = await $.get(blRequest); 68 | log(res); 69 | return res; 70 | } 71 | 72 | function addTextToListWidget(text, listWidget) { 73 | let item = listWidget.addText(text); 74 | if (rancolor == true) { 75 | item.textColor = new Color(color16()); 76 | } else { 77 | item.textColor = Color.white(); 78 | } 79 | item.textSize = 12; 80 | } 81 | 82 | function color16() { 83 | var r = Math.floor(Math.random() * 256); 84 | if (r + 50 < 255) { 85 | r = r + 50; 86 | } 87 | if (r > 230 && r < 255) { 88 | r = r - 50; 89 | } 90 | var g = Math.floor(Math.random() * 256); 91 | if (g + 50 < 255) { 92 | g = g + 50; 93 | } 94 | if (g > 230 && g < 255) { 95 | g = g - 50; 96 | } 97 | var b = Math.floor(Math.random() * 256); 98 | if (b + 50 < 255) { 99 | b = b + 50; 100 | } 101 | if (b > 230 && b < 255) { 102 | b = b - 50; 103 | } 104 | var color = "#" + r.toString(16) + g.toString(16) + b.toString(16); 105 | return color; 106 | } 107 | 108 | //更新代码 109 | function update() { 110 | log("🔔更新脚本开始!"); 111 | scripts.forEach(async (script) => { 112 | await $.getFile(script); 113 | }); 114 | log("🔔更新脚本结束!"); 115 | } 116 | 117 | const scripts = [ 118 | { 119 | moduleName: "BilibiliMonitor", 120 | url: 121 | "https://raw.githubusercontent.com/evilbutcher/Scriptables/master/BilibiliMonitor.js", 122 | }, 123 | ]; 124 | if (goupdate == true) update(); 125 | -------------------------------------------------------------------------------- /Checkin/Checkin.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: purple; icon-glyph: plane-departure; 4 | /* 5 | * Author: evilbutcher Neurogram 6 | * Github: https://github.com/evilbutcher 7 | * 本脚本使用了@Gideon_Senku的Env.scriptable,感谢! 8 | * 感谢@MuTu88帮忙测试! 9 | * 自动更新打开后会运行覆盖脚本内已有修改,多种解决方案: 10 | * 一、配置Config文件,请参考https://github.com/evilbutcher/Scriptables/blob/master/Config.js,下载后导入Scriptable,脚本运行会❗️优先❗️调取Config文件中信息,此方法只能显示❗️一个❗️机场。 11 | * 二、【推荐】Scriptable的iCloud文件夹内,配置checkin.json文件(注意文件名),具体格式参考https://github.com/evilbutcher/Scriptables/blob/master/checkin_example.json,可以通过创建桌面小组件时填入不同参数如“c1”、“c2”等实现读取多个机场信息。 12 | * 三、脚本内配置,在下方注释写有填写签到标题的引号内,填写对应的签到信息,注意,此方法一旦更新脚本,所做的更改就会被远程文件覆盖。 13 | * 脚本运行后,会在iCloud/Scriptable文件夹内写入一个recordcheckintime.txt,用于记录签到时间,脚本逻辑每天签到一次。 14 | */ 15 | const goupdate = false; //默认关闭,需要时打开,更新后会覆盖脚本已有的签到信息,建议使用Config或Scriptable的iCloud文件夹存入checkin.json文件的方式 16 | const $ = importModule("Env"); 17 | $.autoLogout = false; //退出登录后再签到 18 | try { 19 | const para = args.widgetParameter || "c1"; 20 | const fileName = "checkin.json"; 21 | const res = JSON.parse($.read(fileName)); 22 | var checkintitle = res[para].title || ""; //填写签到标题 23 | var checkinloginurl = res[para].url || ""; //填写签到登陆链接 24 | var checkinemail = res[para].email || ""; //填写签到邮箱 25 | var checkinpwd = res[para].password || ""; //填写签到密码 26 | } catch (e) { 27 | log("获取JSON文件失败"); 28 | } 29 | const size = 12; //字体大小 30 | const isDark = Device.isUsingDarkAppearance(); 31 | const bgColor = new LinearGradient(); 32 | bgColor.colors = isDark 33 | ? [new Color("#030079"), new Color("#000000")] 34 | : [new Color("#a18cd1"), new Color("#fbc2eb")]; 35 | bgColor.locations = [0.0, 1.0]; 36 | function addTextToListWidget(text, listWidget) { 37 | let item = listWidget.addText(text); 38 | item.textColor = isDark ? Color.white() : Color.black(); 39 | item.textSize = size; 40 | } 41 | function addTitleTextToListWidget(text, listWidget) { 42 | let item = listWidget.addText(text); 43 | item.textColor = isDark ? Color.white() : Color.black(); 44 | item.applyHeadlineTextStyling(); 45 | } 46 | 47 | const scripts = [ 48 | { 49 | moduleName: "Checkin", 50 | url: 51 | "https://raw.githubusercontent.com/evilbutcher/Scriptables/master/Checkin.js", 52 | }, 53 | ]; 54 | 55 | !(async () => { 56 | init(); 57 | getinfo(); 58 | await launch(); 59 | log($.checkintitle); 60 | log($.checkinMsg); 61 | log($.todayUsed); 62 | log($.usedData); 63 | log($.restData); 64 | let widget = createWidget( 65 | $.checkintitle, 66 | $.checkinMsg, 67 | $.todayUsed, 68 | $.usedData, 69 | $.restData 70 | ); 71 | Script.setWidget(widget); 72 | Script.complete(); 73 | })() 74 | .catch((err) => { 75 | $.msg("Checkin运行出现错误❌\n" + err); 76 | }) 77 | .finally(update()); 78 | 79 | function getinfo() { 80 | try { 81 | const con = importModule("Config"); 82 | $.checkintitle = con.checkintitle(); 83 | $.checkinloginurl = con.checkinloginurl(); 84 | $.checkinemail = con.checkinemail(); 85 | $.checkinpwd = con.checkinpwd(); 86 | if ( 87 | $.checkintitle == "" || 88 | $.checkinloginurl == "" || 89 | $.checkinemail == "" || 90 | $.checkinpwd == "" 91 | ) { 92 | log("配置文件内签到信息不完整"); 93 | throw new Error(err); 94 | } 95 | log("将使用配置文件内签到信息"); 96 | } catch (err) { 97 | $.checkintitle = checkintitle; 98 | $.checkinloginurl = checkinloginurl; 99 | $.checkinemail = checkinemail; 100 | $.checkinpwd = checkinpwd; 101 | log("将使用脚本内签到信息"); 102 | if ( 103 | $.checkintitle == "" || 104 | $.checkinloginurl == "" || 105 | $.checkinemail == "" || 106 | $.checkinpwd == "" 107 | ) { 108 | $.msg("请检查填入的签到信息是否完整"); 109 | } 110 | } 111 | } 112 | 113 | function init() { 114 | $.nowtime = new Date().getTime(); 115 | log($.nowtime); 116 | if ($.isFileExists("recordcheckintime.txt") == true) { 117 | var recordtime = $.read("recordcheckintime.txt"); 118 | log(recordtime); 119 | if ($.nowtime - recordtime > 86400000) { 120 | $.cancheckin = true; 121 | $.write("recordcheckintime.txt", JSON.stringify($.nowtime)); 122 | } else { 123 | $.cancheckin = false; 124 | } 125 | } else { 126 | $.write("recordcheckintime.txt", JSON.stringify($.nowtime)); 127 | log("初始时间已写入"); 128 | $.cancheckin = true; 129 | } 130 | } 131 | 132 | async function launch() { 133 | let title = $.checkintitle; 134 | let url = $.checkinloginurl; 135 | let email = $.checkinemail; 136 | let password = $.checkinpwd; 137 | if ($.autoLogout == true) { 138 | let logoutPath = 139 | url.indexOf("auth/login") != -1 ? "user/logout" : "user/logout.php"; 140 | var logouturl = { 141 | url: url.replace(/(auth|user)\/login(.php)*/g, "") + logoutPath, 142 | }; 143 | log(logouturl); 144 | await $.getStr(logouturl); 145 | await login(url, email, password, title); 146 | if ($.loginok == true) { 147 | if ($.cancheckin == true) { 148 | await checkin(url, email, password, title); 149 | if ($.checkinok == true) { 150 | await dataResults(url, $.checkindatamsg, title); 151 | } 152 | } else { 153 | await dataResults(url, "签到完成🎉", title); 154 | } 155 | } 156 | } else { 157 | if ($.cancheckin == true) { 158 | await checkin(url, email, password, title); 159 | if ($.checkinok == true) { 160 | await dataResults(url, $.checkindatamsg, title); 161 | } else { 162 | await login(url, email, password, title); 163 | if ($.loginok == true) { 164 | await checkin(url, email, password, title); 165 | await dataResults(url, "签到完成🎉", title); 166 | } 167 | } 168 | } else { 169 | await dataResults(url, "签到完成🎉", title); 170 | if ($.getdata == false) { 171 | await login(url, email, password, title); 172 | if ($.loginok == true) { 173 | await dataResults(url, "签到完成🎉", title); 174 | } 175 | } 176 | } 177 | } 178 | } 179 | 180 | async function login(url, email, password, title) { 181 | let loginPath = 182 | url.indexOf("auth/login") != -1 ? "auth/login" : "user/_login.php"; 183 | let table = { 184 | url: 185 | url.replace(/(auth|user)\/login(.php)*/g, "") + 186 | loginPath + 187 | `?email=${email}&passwd=${password}&rumber-me=week`, 188 | }; 189 | log(table); 190 | await $.post(table, async (response, data) => { 191 | if ( 192 | JSON.parse(data).msg.match( 193 | /邮箱不存在|邮箱或者密码错误|Mail or password is incorrect/ 194 | ) 195 | ) { 196 | $.msg(title + "邮箱或者密码错误"); 197 | $.loginok = false; 198 | log("登陆失败"); 199 | } else { 200 | $.loginok = true; 201 | log("登陆成功"); 202 | } 203 | }); 204 | } 205 | 206 | async function checkin(url, email, password, title) { 207 | let checkinPath = 208 | url.indexOf("auth/login") != -1 ? "user/checkin" : "user/_checkin.php"; 209 | var checkinreqest = { 210 | url: url.replace(/(auth|user)\/login(.php)*/g, "") + checkinPath, 211 | }; 212 | log(checkinreqest); 213 | await $.post(checkinreqest, async (response, data) => { 214 | if (data.match(/\"msg\"\:/)) { 215 | $.checkinok = true; 216 | $.checkindatamsg = JSON.parse(data).msg; 217 | log("签到成功"); 218 | } else { 219 | $.checkinok = false; 220 | log("签到失败"); 221 | } 222 | }); 223 | } 224 | 225 | async function dataResults(url, checkinMsg, title) { 226 | let userPath = url.indexOf("auth/login") != -1 ? "user" : "user/index.php"; 227 | var datarequest = { 228 | url: url.replace(/(auth|user)\/login(.php)*/g, "") + userPath, 229 | }; 230 | log(datarequest); 231 | await $.getStr(datarequest, async (response, data) => { 232 | if (data.match(/login|请填写邮箱|登陆/)) { 233 | $.getdata = false; 234 | } else { 235 | let resultData = ""; 236 | let result = []; 237 | if (data.match(/theme\/malio/)) { 238 | let flowInfo = data.match(/trafficDountChat\s*\(([^\)]+)/); 239 | if (flowInfo) { 240 | let flowData = flowInfo[1].match(/\d[^\']+/g); 241 | let usedData = flowData[0]; 242 | let todatUsed = flowData[1]; 243 | let restData = flowData[2]; 244 | $.todayUsed = `今日已用:${flowData[1]}`; 245 | $.usedData = `累计使用:${flowData[0]}`; 246 | $.restData = `剩余流量:${flowData[2]}`; 247 | result.push( 248 | `今日:${todatUsed}\n已用:${usedData}\n剩余:${restData}` 249 | ); 250 | } 251 | let userInfo = data.match(/ChatraIntegration\s*=\s*({[^}]+)/); 252 | if (userInfo) { 253 | let user_name = userInfo[1].match(/name.+'(.+)'/)[1]; 254 | let user_class = userInfo[1].match(/Class.+'(.+)'/)[1]; 255 | let class_expire = userInfo[1].match(/Class_Expire.+'(.+)'/)[1]; 256 | let money = userInfo[1].match(/Money.+'(.+)'/)[1]; 257 | result.push( 258 | `用户名:${user_name}\n用户等级:lv${user_class}\n余额:${money}\n到期时间:${class_expire}` 259 | ); 260 | } 261 | if (result.length != 0) { 262 | resultData = result.join("\n\n"); 263 | } 264 | } else { 265 | let todayUsed = data.match(/>*\s*今日(已用|使用)*[^B]+/); 266 | if (todayUsed) { 267 | todayUsed = flowFormat(todayUsed[0]); 268 | result.push(`今日:${todayUsed}`); 269 | $.todayUsed = `今日已用:${todayUsed}`; 270 | } else { 271 | $.todayUsed = `今日已用获取失败`; 272 | result.push(`今日已用获取失败`); 273 | } 274 | let usedData = data.match( 275 | /(Used Transfer|>过去已用|>已用|>总已用|\"已用)[^B]+/ 276 | ); 277 | if (usedData) { 278 | usedData = flowFormat(usedData[0]); 279 | result.push(`已用:${usedData}`); 280 | $.usedData = `累计使用:${usedData}`; 281 | } else { 282 | $.usedData = `累计使用获取失败`; 283 | result.push(`累计使用获取失败`); 284 | } 285 | let restData = data.match( 286 | /(Remaining Transfer|>剩余流量|>流量剩余|>可用|\"剩余)[^B]+/ 287 | ); 288 | if (restData) { 289 | restData = flowFormat(restData[0]); 290 | result.push(`剩余:${restData}`); 291 | $.restData = `剩余流量:${restData}`; 292 | } else { 293 | $.restData = `剩余流量获取失败`; 294 | result.push(`剩余流量获取失败`); 295 | } 296 | resultData = result.join("\n"); 297 | } 298 | $.checkinMsg = checkinMsg; 299 | log(title + "\n" + checkinMsg + "\n" + resultData); 300 | } 301 | }); 302 | } 303 | 304 | function flowFormat(data) { 305 | data = data.replace(/\d+(\.\d+)*%/, ""); 306 | let flow = data.match(/\d+(\.\d+)*\w*/); 307 | return flow[0] + "B"; 308 | } 309 | 310 | function createWidget(checkintitle, checkinMsg, todayUsed, usedData, restData) { 311 | const w = new ListWidget(); 312 | w.backgroundGradient = bgColor; 313 | w.addSpacer(); 314 | w.spacing = 5; 315 | 316 | const emoji = w.addText(`🪐`); 317 | emoji.textSize = 30; 318 | 319 | addTitleTextToListWidget(checkintitle, w); 320 | addTextToListWidget(checkinMsg, w); 321 | addTextToListWidget(todayUsed, w); 322 | addTextToListWidget(usedData, w); 323 | addTextToListWidget(restData, w); 324 | 325 | w.addSpacer(); 326 | w.spacing = 5; 327 | w.presentSmall(); 328 | return w; 329 | } 330 | 331 | //更新代码 332 | function update() { 333 | if (goupdate == true) { 334 | log("🔔更新脚本开始!"); 335 | scripts.forEach(async (script) => { 336 | await $.getFile(script); 337 | }); 338 | log("🔔更新脚本结束!"); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /Checkin/Dler Cloud.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: purple; icon-glyph: plane-departure; 4 | /* 5 | * Author: evilbutcher Neurogram 6 | * Github: https://github.com/evilbutcher 7 | * 本脚本使用了@Gideon_Senku的Env.scriptable,感谢! 8 | * 感谢@MuTu88帮忙测试! 9 | * 自动更新打开后会运行覆盖脚本内已有修改,多种解决方案: 10 | * 一、配置Config文件,请参考https://github.com/evilbutcher/Scriptables/blob/master/Config.js,下载后导入Scriptable,脚本运行会❗️优先❗️调取Config文件中信息,此方法只能显示❗️一个❗️机场。 11 | * 二、【推荐】Scriptable的iCloud文件夹内,配置checkin.json文件(注意文件名),具体格式参考https://github.com/evilbutcher/Scriptables/blob/master/checkin_example.json,可以通过创建桌面小组件时填入不同参数如“c1”、“c2”等实现读取多个机场信息。 12 | * 三、脚本内配置,在下方注释写有填写签到标题的引号内,填写对应的签到信息,注意,此方法一旦更新脚本,所做的更改就会被远程文件覆盖。 13 | * 脚本运行后,会在iCloud/Scriptable文件夹内写入一个recordcheckintime.txt,用于记录签到时间,脚本逻辑每天签到一次。 14 | */ 15 | const goupdate = false; //默认关闭,需要时打开,更新后会覆盖脚本已有的签到信息,建议使用Config或Scriptable的iCloud文件夹存入checkin.json文件的方式 16 | const $ = importModule("Env"); 17 | $.autoLogout = false; //退出登录后再签到 18 | try { 19 | const para = args.widgetParameter || "c1"; 20 | const fileName = "checkin.json"; 21 | const res = JSON.parse($.read(fileName)); 22 | var checkintitle = res[para].title || ""; //填写签到标题 23 | var checkinloginurl = res[para].url || ""; //填写签到登陆链接 24 | var checkinemail = res[para].email || ""; //填写签到邮箱 25 | var checkinpwd = res[para].password || ""; //填写签到密码 26 | } catch (e) { 27 | log("获取JSON文件失败"); 28 | } 29 | const size = 12; //字体大小 30 | const isDark = Device.isUsingDarkAppearance(); 31 | const bgColor = new LinearGradient(); 32 | bgColor.colors = isDark 33 | ? [new Color("#030079"), new Color("#000000")] 34 | : [new Color("#a18cd1"), new Color("#fbc2eb")]; 35 | bgColor.locations = [0.0, 1.0]; 36 | function addTextToListWidget(text, listWidget) { 37 | let item = listWidget.addText(text); 38 | item.textColor = isDark ? Color.white() : Color.black(); 39 | item.textSize = size; 40 | } 41 | function addTitleTextToListWidget(text, listWidget) { 42 | let item = listWidget.addText(text); 43 | item.textColor = isDark ? Color.white() : Color.black(); 44 | item.applyHeadlineTextStyling(); 45 | } 46 | 47 | const scripts = [ 48 | { 49 | moduleName: "Checkin", 50 | url: 51 | "https://raw.githubusercontent.com/evilbutcher/Scriptables/master/Dler%20Cloud.js", 52 | }, 53 | ]; 54 | 55 | !(async () => { 56 | init(); 57 | getinfo(); 58 | await launch(); 59 | log($.checkintitle); 60 | log($.checkinMsg); 61 | log($.todayUsed); 62 | log($.usedData); 63 | log($.restData); 64 | let widget = createWidget( 65 | $.checkintitle, 66 | $.checkinMsg, 67 | $.todayUsed, 68 | $.usedData, 69 | $.restData 70 | ); 71 | Script.setWidget(widget); 72 | Script.complete(); 73 | })() 74 | .catch((err) => { 75 | $.msg("Checkin运行出现错误❌\n" + err); 76 | }) 77 | .finally(update()); 78 | 79 | function getinfo() { 80 | try { 81 | const con = importModule("Config"); 82 | $.checkintitle = con.checkintitle(); 83 | $.checkinloginurl = con.checkinloginurl(); 84 | $.checkinemail = con.checkinemail(); 85 | $.checkinpwd = con.checkinpwd(); 86 | if ( 87 | $.checkintitle == "" || 88 | $.checkinloginurl == "" || 89 | $.checkinemail == "" || 90 | $.checkinpwd == "" 91 | ) { 92 | log("配置文件内签到信息不完整"); 93 | throw new Error(err); 94 | } 95 | log("将使用配置文件内签到信息"); 96 | } catch (err) { 97 | $.checkintitle = checkintitle; 98 | $.checkinloginurl = checkinloginurl; 99 | $.checkinemail = checkinemail; 100 | $.checkinpwd = checkinpwd; 101 | log("将使用脚本内签到信息"); 102 | if ( 103 | $.checkintitle == "" || 104 | $.checkinloginurl == "" || 105 | $.checkinemail == "" || 106 | $.checkinpwd == "" 107 | ) { 108 | $.msg("请检查填入的签到信息是否完整"); 109 | } 110 | } 111 | } 112 | 113 | function init() { 114 | $.nowtime = new Date().getTime(); 115 | log($.nowtime); 116 | if ($.isFileExists("recordcheckintime.txt") == true) { 117 | var recordtime = $.read("recordcheckintime.txt"); 118 | log(recordtime); 119 | if ($.nowtime - recordtime > 86400000) { 120 | $.cancheckin = true; 121 | $.write("recordcheckintime.txt", JSON.stringify($.nowtime)); 122 | } else { 123 | $.cancheckin = false; 124 | } 125 | } else { 126 | $.write("recordcheckintime.txt", JSON.stringify($.nowtime)); 127 | log("初始时间已写入"); 128 | $.cancheckin = true; 129 | } 130 | } 131 | 132 | async function launch() { 133 | let title = $.checkintitle; 134 | let url = $.checkinloginurl; 135 | let email = $.checkinemail; 136 | let password = $.checkinpwd; 137 | if ($.autoLogout == true) { 138 | let logoutPath = 139 | url.indexOf("auth/login") != -1 ? "user/logout" : "user/logout.php"; 140 | var logouturl = { 141 | url: url.replace(/(auth|user)\/login(.php)*/g, "") + logoutPath, 142 | }; 143 | log(logouturl); 144 | await $.getStr(logouturl); 145 | await login(url, email, password, title); 146 | if ($.loginok == true) { 147 | if ($.cancheckin == true) { 148 | await checkin(url, email, password, title); 149 | if ($.checkinok == true) { 150 | await dataResults(url, $.checkindatamsg, title); 151 | } 152 | } else { 153 | await dataResults(url, "签到完成🎉", title); 154 | } 155 | } 156 | } else { 157 | if ($.cancheckin == true) { 158 | await checkin(url, email, password, title); 159 | if ($.checkinok == true) { 160 | await dataResults(url, $.checkindatamsg, title); 161 | } else { 162 | await login(url, email, password, title); 163 | if ($.loginok == true) { 164 | await checkin(url, email, password, title); 165 | await dataResults(url, "签到完成🎉", title); 166 | } 167 | } 168 | } else { 169 | await dataResults(url, "签到完成🎉", title); 170 | if ($.getdata == false) { 171 | await login(url, email, password, title); 172 | if ($.loginok == true) { 173 | await dataResults(url, "签到完成🎉", title); 174 | } 175 | } 176 | } 177 | } 178 | } 179 | 180 | async function login(url, email, password, title) { 181 | let loginPath = 182 | url.indexOf("auth/login") != -1 ? "auth/login" : "user/_login.php"; 183 | let table = { 184 | url: 185 | url.replace(/(auth|user)\/login(.php)*/g, "") + 186 | loginPath + 187 | `?email=${email}&passwd=${password}&rumber-me=week`, 188 | }; 189 | log(table); 190 | await $.post(table, async (response, data) => { 191 | if ( 192 | JSON.parse(data).msg.match( 193 | /邮箱不存在|邮箱或者密码错误|Mail or password is incorrect/ 194 | ) 195 | ) { 196 | $.msg(title + "邮箱或者密码错误"); 197 | $.loginok = false; 198 | log("登陆失败"); 199 | } else { 200 | $.loginok = true; 201 | log("登陆成功"); 202 | } 203 | }); 204 | } 205 | 206 | async function checkin(url, email, password, title) { 207 | let checkinPath = 208 | url.indexOf("auth/login") != -1 ? "user/checkin" : "user/_checkin.php"; 209 | var checkinreqest = { 210 | url: url.replace(/(auth|user)\/login(.php)*/g, "") + checkinPath, 211 | }; 212 | log(checkinreqest); 213 | await $.post(checkinreqest, async (response, data) => { 214 | if (data.match(/\"msg\"\:/)) { 215 | $.checkinok = true; 216 | $.checkindatamsg = JSON.parse(data).msg; 217 | log("签到成功"); 218 | } else { 219 | $.checkinok = false; 220 | log("签到失败"); 221 | } 222 | }); 223 | } 224 | 225 | async function dataResults(url, checkinMsg, title) { 226 | let userPath = url.indexOf("auth/login") != -1 ? "user" : "user/index.php"; 227 | var datarequest = { 228 | url: url.replace(/(auth|user)\/login(.php)*/g, "") + userPath, 229 | }; 230 | log(datarequest); 231 | await $.getStr(datarequest, async (response, data) => { 232 | if (data.match(/login|请填写邮箱|登陆/)) { 233 | $.getdata = false; 234 | } else { 235 | let resultData = ""; 236 | let result = []; 237 | if (data.match(/theme\/malio/)) { 238 | let flowInfo = data.match(/trafficDountChat\s*\(([^\)]+)/); 239 | if (flowInfo) { 240 | let flowData = flowInfo[1].match(/\d[^\']+/g); 241 | let usedData = flowData[0]; 242 | let todatUsed = flowData[1]; 243 | let restData = flowData[2]; 244 | $.todayUsed = `今日已用:${flowData[1]}`; 245 | $.usedData = `累计使用:${flowData[0]}`; 246 | $.restData = `剩余流量:${flowData[2]}`; 247 | result.push( 248 | `今日:${todatUsed}\n已用:${usedData}\n剩余:${restData}` 249 | ); 250 | } 251 | let userInfo = data.match(/ChatraIntegration\s*=\s*({[^}]+)/); 252 | if (userInfo) { 253 | let user_name = userInfo[1].match(/name.+'(.+)'/)[1]; 254 | let user_class = userInfo[1].match(/Class.+'(.+)'/)[1]; 255 | let class_expire = userInfo[1].match(/Class_Expire.+'(.+)'/)[1]; 256 | let money = userInfo[1].match(/Money.+'(.+)'/)[1]; 257 | result.push( 258 | `用户名:${user_name}\n用户等级:lv${user_class}\n余额:${money}\n到期时间:${class_expire}` 259 | ); 260 | } 261 | if (result.length != 0) { 262 | resultData = result.join("\n\n"); 263 | } 264 | } else { 265 | let todayUsed = data.match(/expire\"\,\s\".*?\"/); 266 | if (todayUsed) { 267 | var day = JSON.stringify(todayUsed).slice(14, -4); 268 | var time = day.replace(/-/g, "/"); 269 | var expire = new Date(time).getTime(); 270 | var left = ((expire - $.nowtime) / 86400000).toFixed(0); 271 | result.push(`剩余天数:${left}天`); 272 | $.todayUsed = `剩余天数:${left}天`; 273 | } else { 274 | $.todayUsed = `剩余天数获取失败`; 275 | result.push(`剩余天数获取失败`); 276 | } 277 | let usedData = data.match( 278 | /(Used Transfer|>过去已用|>已用|>总已用|\"已用)[^B]+/ 279 | ); 280 | if (usedData) { 281 | usedData = flowFormat(usedData[0]); 282 | result.push(`已用:${usedData}`); 283 | $.usedData = `累计使用:${usedData}`; 284 | } else { 285 | $.usedData = `累计使用获取失败`; 286 | result.push(`累计使用获取失败`); 287 | } 288 | let restData = data.match( 289 | /(Remaining Transfer|>剩余流量|>流量剩余|>可用|\"剩余)[^B]+/ 290 | ); 291 | if (restData) { 292 | restData = flowFormat(restData[0]); 293 | result.push(`剩余:${restData}`); 294 | $.restData = `剩余流量:${restData}`; 295 | } else { 296 | $.restData = `剩余流量获取失败`; 297 | result.push(`剩余流量获取失败`); 298 | } 299 | resultData = result.join("\n"); 300 | } 301 | $.checkinMsg = checkinMsg; 302 | log(title + "\n" + checkinMsg + "\n" + resultData); 303 | } 304 | }); 305 | } 306 | 307 | function flowFormat(data) { 308 | data = data.replace(/\d+(\.\d+)*%/, ""); 309 | let flow = data.match(/\d+(\.\d+)*\w*/); 310 | return flow[0] + "B"; 311 | } 312 | 313 | function createWidget(checkintitle, checkinMsg, todayUsed, usedData, restData) { 314 | const w = new ListWidget(); 315 | w.backgroundGradient = bgColor; 316 | w.addSpacer(); 317 | w.spacing = 5; 318 | 319 | const emoji = w.addText(`🪐`); 320 | emoji.textSize = 30; 321 | 322 | addTitleTextToListWidget(checkintitle, w); 323 | addTextToListWidget(checkinMsg, w); 324 | addTextToListWidget(todayUsed, w); 325 | addTextToListWidget(usedData, w); 326 | addTextToListWidget(restData, w); 327 | 328 | w.addSpacer(); 329 | w.spacing = 5; 330 | w.presentSmall(); 331 | return w; 332 | } 333 | 334 | //更新代码 335 | function update() { 336 | if (goupdate == true) { 337 | log("🔔更新脚本开始!"); 338 | scripts.forEach(async (script) => { 339 | await $.getFile(script); 340 | }); 341 | log("🔔更新脚本结束!"); 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /Checkin/checkin_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "c1": { 3 | "title": "name", 4 | "url": "https://your.loginurl.com/auth/login", 5 | "email": "example@email.com", 6 | "password": "password" 7 | }, 8 | "c2": { 9 | "title": "name", 10 | "url": "https://your.loginurl.com/auth/login", 11 | "email": "example@email.com", 12 | "password": "password" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Config.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-blue; icon-glyph: plus; 4 | /* 5 | * Author: evilbutcher 6 | * Github: https://github.com/evilbutcher 7 | */ 8 | 9 | module.exports = { 10 | bilibili: () => 0, //👈就改这个数字。B站榜单对应关系:0全站,1动画,3音乐,4游戏,5娱乐,36科技,119鬼畜,129舞蹈。 11 | blnum: () => 6, //自定义B站显示数量 12 | blrancolor: () => true, //是否开启B站随机颜色 13 | dbnum: () => 6, //自定义豆瓣显示数量 14 | dbrancolor: () => true, //是否开启豆瓣随机颜色 15 | rrnum: () => 6, //自定义人人影视显示数量 16 | rrrancolor: () => true, //是否开启人人影视随机颜色 17 | rsslink: () => "https://github.com/GideonSenku/Scriptable/commits/master.atom", //👈就改引号里的链接。 18 | rssnum: () => 6, //自定义RSS显示数量 19 | rssrancolor: () => true, //是否开启RSS随机颜色 20 | wbnum: () => 6, //自定义微博显示数量 21 | wbrancolor: () => true, //是否开启微博随机颜色 22 | zhnum: () => 6, //自定义知乎显示数量 23 | zhrancolor: () => true, //是否开启知乎随机颜色 24 | nasaapi: () => "", //填写NASA API Key 25 | imglink: () => "http://api.btstu.cn/sjbz/zsy.php", //NASA备用图片地址,可自定义 26 | checkintitle: () => "", //填写签到标题 27 | checkinloginurl: () => "", //填写签到登陆链接 28 | checkinemail: () => "", //填写签到邮箱 29 | checkinpwd: () => "", //填写签到密码 30 | }; 31 | -------------------------------------------------------------------------------- /Config.scriptable: -------------------------------------------------------------------------------- 1 | { 2 | "always_run_in_app" : false, 3 | "icon" : { 4 | "color" : "deep-blue", 5 | "glyph" : "plus" 6 | }, 7 | "name" : "Config", 8 | "script" : "\/*\n * Author: evilbutcher\n * Github: https:\/\/github.com\/evilbutcher\n *\/\nmodule.exports = () => {\n return new(class {\n bilibili() {\n return 4 \/\/👈就改这个数字。B站榜单对应关系:0全站,1动画,3音乐,4游戏,5娱乐,36科技,119鬼畜,129舞蹈。\n }\n rsslink() {\n return \"https:\/\/github.com\/GideonSenku\/Scriptable\/commits\/master.atom\" \/\/👈就改引号里的链接。\n }\n })\n}", 9 | "share_sheet_inputs" : [ 10 | 11 | ] 12 | } -------------------------------------------------------------------------------- /CountDown/CountDown.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: light-gray; icon-glyph: magic; 4 | const $ = importModule("Env") 5 | 6 | // 更改inputValue会重新写入目标时间 7 | // 目标时间 = '年 月 日 时 分 秒' 用空格隔开, 8 | let inputValue = "0 0 0 0 0 0" 9 | 10 | const leftTime = await leftTimer(inputValue) 11 | 12 | if (config.runsInWidget) { 13 | let widget = createWidget(leftTime) 14 | Script.setWidget(widget) 15 | Script.complete() 16 | } 17 | 18 | // 倒计时到天 19 | function createWidget(leftTime) { 20 | let w = new ListWidget() 21 | const bgColor = new LinearGradient() 22 | bgColor.colors = [new Color("#1c1c1c"), new Color("#29323c")] 23 | bgColor.locations = [0.0, 1.0] 24 | w.backgroundGradient = bgColor 25 | w.presentSmall() 26 | w.spacing = 5 27 | 28 | let line0 = w.addText(`距离`) 29 | line0.textColor = Color.white() 30 | line0.textSize = 12 31 | line0.textOpacity = 0.7 32 | 33 | let line1 = w.addText(`重要的日子`) 34 | line1.textColor = Color.white() 35 | line1.textSize = 15 36 | 37 | let line2 = w.addText(`还有`) 38 | line2.textColor = Color.white() 39 | line2.textSize = 12 40 | line2.textOpacity = 0.7 41 | 42 | let line3 = w.addText(`${leftTime[0]}天`) 43 | line3.textColor = Color.white() 44 | line3.textSize = 28 45 | 46 | return w 47 | } 48 | 49 | 50 | async function leftTimer(inputValue){ 51 | if ( inputValue != "0 0 0 0 0 0" ) { 52 | if( $.setdata('Time', inputValue) ){ 53 | log('clear:' + inputValue) 54 | } 55 | } else if ( $.hasdata('Time') ) { 56 | inputValue = $.getdata('Time') 57 | log('get:' + inputValue) 58 | } else { 59 | inputValue = await $.input('目标时间', '年 月 日 时 分 秒,用空格隔开', '例如:2020 12 31 0 0 0') 60 | if($.setdata('Time', inputValue)) { 61 | log('success:' + inputValue) 62 | } 63 | } 64 | 65 | let targetTime = inputValue.split(" "); 66 | var leftTime = (new Date(targetTime[0], targetTime[1]-1, targetTime[2], targetTime[3], targetTime[4], targetTime[5])) - (new Date()); //计算剩余的毫秒数 67 | var days = parseInt(leftTime / 1000 / 60 / 60 / 24 , 10); //计算剩余的天数 68 | var hours = parseInt(leftTime / 1000 / 60 / 60 % 24 , 10); //计算剩余的小时 69 | var minutes = parseInt(leftTime / 1000 / 60 % 60, 10);//计算剩余的分钟 70 | var seconds = parseInt(leftTime / 1000 % 60, 10);//计算剩余的秒数 71 | 72 | var leftTime=new Array(); 73 | leftTime[0] = checkTime(days); 74 | leftTime[1] = checkTime(hours); 75 | leftTime[2] = checkTime(minutes); 76 | leftTime[3] = checkTime(seconds); 77 | 78 | log(leftTime) 79 | return leftTime 80 | } 81 | 82 | function checkTime(i) { //将0-9的数字前面加上0,例1变为01 83 | if(i<10) { 84 | i = "0" + i; 85 | } 86 | return i; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /Douban/DoubanMonitor.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: green; icon-glyph: film; 4 | /* 5 | * Author: evilbutcher 6 | * Github: https://github.com/evilbutcher 7 | * 本脚本使用了@Gideon_Senku的Env.scriptable,感谢! 8 | */ 9 | const goupdate = true; 10 | const $ = importModule("Env"); 11 | var num = 6; //自定义显示数量 12 | var rancolor = true; //true为开启随机颜色 13 | 14 | try { 15 | var { dbnum, dbrancolor } = importModule("Config"); 16 | num = dbnum(); 17 | rancolor = dbrancolor(); 18 | console.log("将使用配置文件内B站配置"); 19 | } catch (e) { 20 | console.log("将使用脚本内B站配置"); 21 | } 22 | 23 | const res = await getinfo(); 24 | 25 | let widget = createWidget(res); 26 | Script.setWidget(widget); 27 | Script.complete(); 28 | 29 | function createWidget(res) { 30 | var group = res["subject_collection_items"]; 31 | items = []; 32 | for (var i = 0; i < num; i++) { 33 | var title = group[i].title; 34 | var rating = group[i].rating; 35 | if (rating == null) { 36 | var star = "暂无"; 37 | } else { 38 | star = rating["star_count"]; 39 | } 40 | var item = title + " " + star + "✨"; 41 | items.push(item); 42 | } 43 | console.log(items); 44 | 45 | const w = new ListWidget(); 46 | const bgColor = new LinearGradient(); 47 | bgColor.colors = [new Color("#1c1c1c"), new Color("#29323c")]; 48 | bgColor.locations = [0.0, 1.0]; 49 | w.backgroundGradient = bgColor; 50 | w.addSpacer(); 51 | w.spacing = 5; 52 | 53 | const firstLine = w.addText(`🎞豆瓣电影`); 54 | firstLine.textSize = 15; 55 | firstLine.textColor = Color.white(); 56 | firstLine.textOpacity = 0.7; 57 | 58 | for (var i = 0; i < items.length; i++) { 59 | addTextToListWidget(`• ${items[i]}`, w); 60 | } 61 | 62 | w.addSpacer(); 63 | w.spacing = 5; 64 | w.presentSmall(); 65 | return w; 66 | } 67 | 68 | async function getinfo() { 69 | const dbheader = { 70 | Referer: `https://m.douban.com/pwa/cache_worker`, 71 | }; 72 | const dbRequest = { 73 | url: 74 | "https://m.douban.com/rexxar/api/v2/subject_collection/movie_real_time_hotest/items?start=0&count=50&items_only=1&for_mobile=1", 75 | headers: dbheader, 76 | }; 77 | const res = await $.get(dbRequest); 78 | log(res); 79 | return res; 80 | } 81 | 82 | function addTextToListWidget(text, listWidget) { 83 | let item = listWidget.addText(text); 84 | if (rancolor == true) { 85 | item.textColor = new Color(color16()); 86 | } else { 87 | item.textColor = Color.white(); 88 | } 89 | item.textSize = 12; 90 | } 91 | 92 | function color16() { 93 | var r = Math.floor(Math.random() * 256); 94 | if (r + 50 < 255) { 95 | r = r + 50; 96 | } 97 | if (r > 230 && r < 255) { 98 | r = r - 50; 99 | } 100 | var g = Math.floor(Math.random() * 256); 101 | if (g + 50 < 255) { 102 | g = g + 50; 103 | } 104 | if (g > 230 && g < 255) { 105 | g = g - 50; 106 | } 107 | var b = Math.floor(Math.random() * 256); 108 | if (b + 50 < 255) { 109 | b = b + 50; 110 | } 111 | if (b > 230 && b < 255) { 112 | b = b - 50; 113 | } 114 | var color = "#" + r.toString(16) + g.toString(16) + b.toString(16); 115 | return color; 116 | } 117 | 118 | //更新代码 119 | function update() { 120 | log("🔔更新脚本开始!"); 121 | scripts.forEach(async (script) => { 122 | await $.getFile(script); 123 | }); 124 | log("🔔更新脚本结束!"); 125 | } 126 | 127 | const scripts = [ 128 | { 129 | moduleName: "DoubanMonitor", 130 | url: 131 | "https://raw.githubusercontent.com/evilbutcher/Scriptables/master/DoubanMonitor.js", 132 | }, 133 | ]; 134 | if (goupdate == true) update(); 135 | -------------------------------------------------------------------------------- /Env.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: GideonSenku 3 | * Github: https://github.com/GideonSenku 4 | */ 5 | 6 | var locationData, sunData 7 | const currentDate = new Date() 8 | const request = new Request('') 9 | const files = FileManager.iCloud() 10 | const dict = files.documentsDirectory() 11 | files.isDirectory(`${dict}/Env`) ? `` : files.createDirectory(`${dict}/Env`) 12 | const defaultHeaders = { 13 | "Accept": "*/*", 14 | "Content-Type": "application/json" 15 | } 16 | const textFormat = { 17 | defaultText: { size: 14, color: "ffffff", font: "regular" }, 18 | battery: { size: 10, color: "", font: "bold" }, 19 | title: { size: 16, color: "", font: "semibold" }, 20 | SFMono: { size: 12, color: "ffffff", font: "SF Mono" } 21 | } 22 | /** 23 | * @description GET,返回String数据 24 | * @param {*} param0 request信息 25 | * @param {*} callback 回调返回response和JSON对象 26 | */ 27 | const get = async ({ url, headers = {} }, callback = () => {} ) => { 28 | request.url = url 29 | request.method = 'GET' 30 | request.headers = { 31 | ...headers, 32 | ...defaultHeaders 33 | } 34 | const data = await request.loadJSON() 35 | callback(request.response, data) 36 | return data 37 | } 38 | 39 | /** 40 | * @description GET,返回String数据 41 | * @param {*} param0 request信息 42 | * @param {*} callback 回调返回response和String对象 43 | */ 44 | const getStr = async ({ url, headers = {} }, callback = () => {} ) => { 45 | request.url = url 46 | request.method = 'GET' 47 | request.headers = { 48 | ...headers, 49 | ...defaultHeaders 50 | } 51 | const data = await request.loadString() 52 | callback(request.response, data) 53 | return data 54 | } 55 | 56 | /** 57 | * @description POST,返回String数据 58 | * @param {*} param0 request信息 59 | * @param {*} callback 回调返回response和String 60 | */ 61 | const post = async ({ url, body, headers = {} }, callback = () => {} ) => { 62 | request.url = url 63 | request.body = body 64 | request.method = 'POST' 65 | request.headers = { 66 | ...defaultHeaders, 67 | ...headers 68 | } 69 | const data = await request.loadString() 70 | callback(request.response, data) 71 | return data 72 | } 73 | 74 | /** 75 | * @description POST,返回JSON数据 76 | * @param {*} param0 request信息 77 | * @param {*} callback 回调返回response和JSON 78 | */ 79 | const _post = async ({ url, body, headers = {} }, callback = () => {} ) => { 80 | request.url = url 81 | request.body = body 82 | request.method = 'POST' 83 | request.headers = { 84 | ...defaultHeaders, 85 | ...headers 86 | } 87 | const data = await request.loadJSON() 88 | callback(request.response, data) 89 | return data 90 | } 91 | 92 | /** 93 | * @description 下载文件 94 | * @param {*} param0 95 | */ 96 | const getFile = async ({moduleName, url}) => { 97 | log(`开始下载文件: 🌝 ${moduleName}`) 98 | const header = `// Variables used by Scriptable. 99 | // These must be at the very top of the file. Do not edit. 100 | // icon-color: deep-gray; icon-glyph: file-code;\n`; 101 | const content = await getStr({url}) 102 | const fileHeader = content.includes('icon-color') ? `` : header 103 | write(`${moduleName}`, `${fileHeader}${content}`) 104 | log(`文件下载完成: 🌚 ${moduleName}`) 105 | } 106 | 107 | /** 108 | * 109 | * @description 导入模块,不存在即下载模块,也可传入forceDownload: true 强制更新模块 110 | * @param {*} param0 111 | */ 112 | const require = ({ 113 | moduleName, 114 | url = '', 115 | forceDownload = false 116 | }) => { 117 | if (isFileExists(moduleName) && !forceDownload) { 118 | log(`导入模块: 🪐${moduleName}`) 119 | return importModule(moduleName) 120 | } else { 121 | getFile({ moduleName, url }) 122 | log(`导入模块: 🪐${moduleName}`) 123 | return importModule(moduleName) 124 | } 125 | } 126 | /** 127 | * 128 | * @description 将数据写入文件 129 | * @param {*} fileName 要写入的文件名,默认JS文件,可选其他,加上文件名后缀即可 130 | * @param {*} content 要写入的文件内容 131 | */ 132 | const write = (fileName, content) => { 133 | let file = initFile(fileName) 134 | const filePath = `${dict}/${file}` 135 | FileManager.iCloud().writeString(filePath, content) 136 | return true 137 | } 138 | 139 | /** 140 | * 141 | * @description 判断文件是否存在 142 | * @param {*} fileName 143 | */ 144 | const isFileExists = (fileName) => { 145 | let file = initFile(fileName) 146 | return FileManager.iCloud().fileExists(`${dict}/${file}`) 147 | } 148 | 149 | const initFile = (fileName) => { 150 | const hasSuffix = fileName.lastIndexOf('.') + 1 151 | return !hasSuffix ? `${fileName}.js` : fileName 152 | } 153 | 154 | /** 155 | * 156 | * @description 读取文件内容 157 | * @param {*} fileName 要读取的文件名,默认JS文件,可选其他,加上文件名后缀即可 158 | * @return 返回文件内容,字符串形式 159 | */ 160 | const read = (fileName) => { 161 | const file = initFile(fileName) 162 | return FileManager.iCloud().readString(`${dict}/${file}`) 163 | } 164 | 165 | /** 166 | * 167 | * @description 提示框 168 | * @param {*} title 提示框标题 169 | * @param {*} message 提示框内容 170 | * @param {*} btnMes 提示框按钮标题,默认Cancel 171 | */ 172 | const msg = (title, message, btnMes = 'Cancel') => { 173 | if (!config.runsInWidget) { 174 | const alert = new Alert() 175 | alert.title = title 176 | alert.message = message 177 | alert.addAction(btnMes) 178 | alert.present() 179 | } 180 | } 181 | 182 | const setdata = (Val, Key) => { 183 | Keychain.set(Val, Key) 184 | return true 185 | } 186 | 187 | const getdata = (Key) => { 188 | return Keychain.get(Key) 189 | } 190 | 191 | const hasdata = (Key) => { 192 | return Keychain.contains(Key) 193 | } 194 | 195 | const rmdata = (Key) => { 196 | Keychain.remove(Key) 197 | return true 198 | } 199 | 200 | // Presents an alert where the user can enter a value in a text field. 201 | // Returns the entered value. 202 | const input = async(title, message, placeholder, value = null) => { 203 | if (!config.runsInWidget) { 204 | let alert = new Alert() 205 | alert.title = title 206 | alert.message = message 207 | alert.addTextField(placeholder, value) 208 | alert.addAction("OK") 209 | alert.addCancelAction("Cancel") 210 | let idx = await alert.present() 211 | if (idx != -1) { 212 | return alert.textFieldValue(0) 213 | } else { 214 | throw new Error("Cancelled entering value") 215 | } 216 | } 217 | } 218 | 219 | /** 220 | * 221 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 222 | * :$.time('yyyyMMddHHmmssS') 223 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 224 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 225 | * @param {*} fmt 格式化参数 226 | * @param {*} ts 时间戳 13位 227 | */ 228 | const time = (fmt, ts = null) => { 229 | const date = ts ? new Date(ts) : new Date() 230 | let o = { 231 | 'M+': date.getMonth() + 1, 232 | 'd+': date.getDate(), 233 | 'H+': date.getHours(), 234 | 'm+': date.getMinutes(), 235 | 's+': date.getSeconds(), 236 | 'q+': Math.floor((date.getMonth() + 3) / 3), 237 | 'S': date.getMilliseconds() 238 | } 239 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) 240 | for (let k in o) 241 | if (new RegExp('(' + k + ')').test(fmt)) 242 | fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) 243 | return fmt 244 | } 245 | 246 | /** 247 | * @description create wiget 248 | * @param {*} title required 249 | * @param {*} texts required 250 | * @param {*} preview option 251 | */ 252 | const createWidget = async({ title, texts = { },spacing = 5, preview = '' }) => { 253 | let w = new ListWidget() 254 | w.spacing = spacing 255 | 256 | let gradient = new LinearGradient() 257 | let gradientSettings = await setupGradient() 258 | 259 | gradient.colors = gradientSettings.color() 260 | gradient.locations = gradientSettings.position() 261 | 262 | w.backgroundGradient = gradient 263 | texts['battery'] ? battery(w, title) : provideText(title, w, textFormat.title) 264 | for (const text in texts) { 265 | if (text != 'battery' && text != 'updateTime' && texts.hasOwnProperty(text) && texts[text]) { 266 | const element = texts[text] 267 | provideText(element, w, textFormat.SFMono) 268 | } 269 | } 270 | texts['updateTime'] ? provideText(`[更新] ${time('MM-dd HH:mm')}`, w, textFormat.SFMono) : `` 271 | 272 | widgetPreview = preview ? preview: 'small' 273 | 274 | if(widgetPreview == "small") { w.presentSmall() } 275 | else if (widgetPreview == "medium") { w.presentMedium() } 276 | else if (widgetPreview == "large") { w.presentLarge() } 277 | return w 278 | } 279 | 280 | 281 | /** 282 | * @description Provide a font based on the input. 283 | * @param {*} fontName 284 | * @param {*} fontSize 285 | */ 286 | const provideFont = (fontName, fontSize) => { 287 | const fontGenerator = { 288 | "ultralight": function() { return Font.ultraLightSystemFont(fontSize) }, 289 | "light": function() { return Font.lightSystemFont(fontSize) }, 290 | "regular": function() { return Font.regularSystemFont(fontSize) }, 291 | "medium": function() { return Font.mediumSystemFont(fontSize) }, 292 | "semibold": function() { return Font.semiboldSystemFont(fontSize) }, 293 | "bold": function() { return Font.boldSystemFont(fontSize) }, 294 | "heavy": function() { return Font.heavySystemFont(fontSize) }, 295 | "black": function() { return Font.blackSystemFont(fontSize) }, 296 | "italic": function() { return Font.italicSystemFont(fontSize) } 297 | } 298 | 299 | const systemFont = fontGenerator[fontName] 300 | if (systemFont) { return systemFont() } 301 | return new Font(fontName, fontSize) 302 | } 303 | 304 | 305 | /** 306 | * @description Add formatted text to a container. 307 | * @param {*} string 308 | * @param {*} container widget container 309 | * @param {*} format Object: size, color, font 310 | */ 311 | 312 | const provideText = (string, container, format) => { 313 | let url 314 | if (typeof string !== 'string') { 315 | url = string.url 316 | string = string.text 317 | } 318 | const stackItem = container.addStack() 319 | 320 | if (url) { 321 | stackItem.url = url 322 | } 323 | 324 | const textItem = stackItem.addText(string) 325 | const textFont = format.font || textFormat.defaultText.font 326 | const textSize = format.size || textFormat.defaultText.size 327 | const textColor = format.color || textFormat.defaultText.color 328 | 329 | textItem.font = provideFont(textFont, textSize) 330 | textItem.textColor = new Color(textColor) 331 | return stackItem 332 | } 333 | 334 | // Set up the gradient for the widget background. 335 | const setupGradient = async() => { 336 | 337 | // Requirements: sunrise 338 | if (!sunData) { await setupSunrise() } 339 | 340 | let gradient = { 341 | dawn: { 342 | color() { return [new Color("142C52"), new Color("1B416F"), new Color("62668B")] }, 343 | position() { return [0, 0.5, 1] }, 344 | }, 345 | 346 | sunrise: { 347 | color() { return [new Color("274875"), new Color("766f8d"), new Color("f0b35e")] }, 348 | position() { return [0, 0.8, 1.5] }, 349 | }, 350 | 351 | midday: { 352 | color() { return [new Color("3a8cc1"), new Color("90c0df")] }, 353 | position() { return [0, 1] }, 354 | }, 355 | 356 | noon: { 357 | color() { return [new Color("b2d0e1"), new Color("80B5DB"), new Color("3a8cc1")] }, 358 | position() { return [-0.2, 0.2, 1.5] }, 359 | }, 360 | 361 | sunset: { 362 | color() { return [new Color("32327A"), new Color("662E55"), new Color("7C2F43")] }, 363 | position() { return [0.1, 0.9, 1.2] }, 364 | }, 365 | 366 | twilight: { 367 | color() { return [new Color("021033"), new Color("16296b"), new Color("414791")] }, 368 | position() { return [0, 0.5, 1] }, 369 | }, 370 | 371 | night: { 372 | color() { return [new Color("16296b"), new Color("021033"), new Color("021033"), new Color("113245")] }, 373 | position() { return [-0.5, 0.2, 0.5, 1] }, 374 | }, 375 | } 376 | 377 | const sunrise = sunData.sunrise 378 | const sunset = sunData.sunset 379 | const utcTime = currentDate.getTime() 380 | 381 | function closeTo(time,mins) { 382 | return Math.abs(utcTime - time) < (mins * 60000) 383 | } 384 | 385 | // Use sunrise or sunset if we're within 30min of it. 386 | if (closeTo(sunrise,15)) { return gradient.sunrise } 387 | if (closeTo(sunset,15)) { return gradient.sunset } 388 | 389 | // In the 30min before/after, use dawn/twilight. 390 | if (closeTo(sunrise,45) && utcTime < sunrise) { return gradient.dawn } 391 | if (closeTo(sunset,45) && utcTime > sunset) { return gradient.twilight } 392 | 393 | // Otherwise, if it's night, return night. 394 | if (isNight(currentDate)) { return gradient.night } 395 | 396 | // If it's around noon, the sun is high in the sky. 397 | if (currentDate.getHours() == 12) { return gradient.noon } 398 | // Otherwise, return the "typical" theme. 399 | return gradient.midday 400 | } 401 | 402 | // Set up the sunData object. 403 | const setupSunrise = async () => { 404 | 405 | // Requirements: location 406 | if (!locationData) { await setupLocation() } 407 | 408 | // Set up the sunrise/sunset cache. 409 | const sunCachePath = files.joinPath(dict, "Env/Env-sun") 410 | const sunCacheExists = files.fileExists(sunCachePath) 411 | const sunCacheDate = sunCacheExists ? files.modificationDate(sunCachePath) : 0 412 | var sunDataRaw 413 | 414 | // If cache exists and it was created today, use cached data. 415 | if (sunCacheExists && sameDay(currentDate, sunCacheDate)) { 416 | const sunCache = files.readString(sunCachePath) 417 | sunDataRaw = JSON.parse(sunCache) 418 | 419 | // Otherwise, use the API to get sunrise and sunset times. 420 | } else { 421 | const sunReq = "https://api.sunrise-sunset.org/json?lat=" + locationData.latitude + "&lng=" + locationData.longitude + "&formatted=0&date=" + currentDate.getFullYear() + "-" + (currentDate.getMonth()+1) + "-" + currentDate.getDate() 422 | sunDataRaw = await new Request(sunReq).loadJSON() 423 | files.writeString(sunCachePath, JSON.stringify(sunDataRaw)) 424 | } 425 | 426 | // Store the timing values. 427 | sunData = {} 428 | sunData.sunrise = new Date(sunDataRaw.results.sunrise).getTime() 429 | sunData.sunset = new Date(sunDataRaw.results.sunset).getTime() 430 | } 431 | 432 | const setupLocation = async (lockLocation = true) => { 433 | 434 | locationData = {} 435 | const locationPath = files.joinPath(dict, "Env/Env-location") 436 | 437 | // If our location is unlocked or cache doesn't exist, ask iOS for location. 438 | var readLocationFromFile = false 439 | if (!lockLocation || !files.fileExists(locationPath)) { 440 | try { 441 | const location = await Location.current() 442 | locationData.latitude = location.latitude 443 | locationData.longitude = location.longitude 444 | files.writeString(locationPath, location.latitude + "," + location.longitude) 445 | 446 | } catch(e) { 447 | // If we fail in unlocked mode, read it from the cache. 448 | if (!lockLocation) { readLocationFromFile = true } 449 | 450 | // We can't recover if we fail on first run in locked mode. 451 | else { return } 452 | } 453 | } 454 | 455 | // If our location is locked or we need to read from file, do it. 456 | if (lockLocation || readLocationFromFile) { 457 | const locationStr = files.readString(locationPath).split(",") 458 | locationData.latitude = locationStr[0] 459 | locationData.longitude = locationStr[1] 460 | } 461 | return locationData 462 | } 463 | 464 | // Determines if the provided date is at night. 465 | const isNight = (dateInput) => { 466 | const timeValue = dateInput.getTime() 467 | return (timeValue < sunData.sunrise) || (timeValue > sunData.sunset) 468 | } 469 | // Determines if two dates occur on the same day 470 | const sameDay = (d1, d2) => { 471 | return d1.getFullYear() === d2.getFullYear() && 472 | d1.getMonth() === d2.getMonth() && 473 | d1.getDate() === d2.getDate() 474 | } 475 | /** 476 | * @description 返回电池百分比 477 | */ 478 | const renderBattery = () => { 479 | const batteryLevel = Device.batteryLevel() 480 | const batteryAscii = `${Math.round(batteryLevel * 100)}%` 481 | return batteryAscii 482 | } 483 | 484 | 485 | // Add a battery element to the widget; consisting of a battery icon and percentage. 486 | function battery(column,title) { 487 | const batteryLevel = Device.batteryLevel() 488 | // Set up the battery level item 489 | let batteryStack = column.addStack() 490 | provideText(title, batteryStack, textFormat.title) 491 | 492 | batteryStack.centerAlignContent() 493 | 494 | batteryStack.addSpacer() 495 | 496 | let batteryIcon = batteryStack.addImage(provideBatteryIcon()) 497 | batteryIcon.imageSize = new Size(20,20) 498 | 499 | 500 | // Change the battery icon to red if battery level is <= 20 to match system behavior 501 | if ( Math.round(batteryLevel * 100) > 20 || Device.isCharging() ) { 502 | 503 | batteryIcon.tintColor = Color.white() 504 | 505 | } else { 506 | 507 | batteryIcon.tintColor = Color.red() 508 | 509 | } 510 | 511 | // Display the battery status 512 | let batteryInfo = provideText(' '+renderBattery(), batteryStack, textFormat.battery) 513 | 514 | 515 | } 516 | 517 | 518 | // Provide a battery SFSymbol with accurate level drawn on top of it. 519 | function provideBatteryIcon() { 520 | 521 | if (Device.isCharging()) { return SFSymbol.named("battery.100.bolt").image } 522 | 523 | // Set the size of the battery icon. 524 | const batteryWidth = 87 525 | const batteryHeight = 41 526 | 527 | // Start our draw context. 528 | let draw = new DrawContext() 529 | draw.opaque = false 530 | draw.respectScreenScale = true 531 | draw.size = new Size(batteryWidth, batteryHeight) 532 | 533 | // Draw the battery. 534 | draw.drawImageInRect(SFSymbol.named("battery.0").image, new Rect(0, 0, batteryWidth, batteryHeight)) 535 | 536 | // Match the battery level values to the SFSymbol. 537 | const x = batteryWidth*0.1525 538 | const y = batteryHeight*0.247 539 | const width = batteryWidth*0.602 540 | const height = batteryHeight*0.505 541 | 542 | // Prevent unreadable icons. 543 | let level = Device.batteryLevel() 544 | if (level < 0.05) { level = 0.05 } 545 | 546 | // Determine the width and radius of the battery level. 547 | const current = width * level 548 | let radius = height/6.5 549 | 550 | // When it gets low, adjust the radius to match. 551 | if (current < (radius * 2)) { radius = current / 2 } 552 | 553 | // Make the path for the battery level. 554 | let barPath = new Path() 555 | barPath.addRoundedRect(new Rect(x, y, current, height), radius, radius) 556 | draw.addPath(barPath) 557 | draw.setFillColor(Color.black()) 558 | draw.fillPath() 559 | return draw.getImage() 560 | } 561 | 562 | const logErr = (e, messsage) => { 563 | console.error(e) 564 | } 565 | 566 | 567 | module.exports = { 568 | dict, 569 | get, 570 | getStr, 571 | post, 572 | _post, 573 | getFile, 574 | require, 575 | write, 576 | isFileExists, 577 | initFile, 578 | read, 579 | setdata, 580 | getdata, 581 | hasdata, 582 | rmdata, 583 | msg, 584 | input, 585 | time, 586 | createWidget, 587 | provideText, 588 | setupLocation, 589 | renderBattery, 590 | logErr 591 | } 592 | -------------------------------------------------------------------------------- /Env.scriptable: -------------------------------------------------------------------------------- 1 | { 2 | "always_run_in_app" : false, 3 | "icon" : { 4 | "color" : "brown", 5 | "glyph" : "terminal" 6 | }, 7 | "name" : "Env", 8 | "script" : "\/**\n * Author: GideonSenku\n * Github: https:\/\/github.com\/GideonSenku\n *\/\nmodule.exports = () => {\n return new(class {\n constructor() {\n this.request = new Request('')\n this.documentDirectory = FileManager.iCloud().documentsDirectory()\n this.defaultHeaders = {\n \"Accept\": \"application\/json\",\n \"Content-Type\": \"application\/json\"\n }\n }\n\n async get({ url, headers = {} }) {\n this.request.url = url\n this.request.method = 'GET'\n this.request.headers = {\n ...headers,\n ...this.defaultHeaders\n }\n return await this.request.loadJSON()\n }\n\n async getStr({ url, headers = {} }, callback = () => {}) {\n this.request.url = url\n this.request.method = 'GET'\n this.request.headers = {\n ...headers,\n ...this.defaultHeaders\n }\n const res = await this.request.loadString()\n callback(this.request.response)\n return res\n }\n\n async post({ url, body, headers = {} }) {\n this.request.url = url\n this.request.body = body ? JSON.stringify(body) : `{}`\n this.request.method = 'POST'\n this.request.headers = {\n ...headers,\n ...this.defaultHeaders\n }\n return await this.request.loadJSON()\n }\n \n async getFile({moduleName, url}) {\n const header = `\/\/ Variables used by Scriptable.\n\/\/ These must be at the very top of the file. Do not edit.\n\/\/ icon-color: deep-gray; icon-glyph: file-code;\\n`;\n const content = await this.getStr({ url })\n const fileHeader = content.includes('icon-color') ? `` : header\n this.writeFile(`${moduleName}`, `${fileHeader}${content}`)\n }\n \n async require({ moduleName, url = '', forceDownload = false }) {\n if (this.isFileExists(moduleName) && !forceDownload) {\n return importModule(moduleName)\n } else {\n await this.getFile({moduleName, url})\n return importModule(moduleName)\n }\n }\n\n writeFile(fileName, content) {\n let file = this.initFile(fileName)\n const filePath = `${this.documentDirectory}\/${file}`\n FileManager.iCloud().writeString(filePath, content)\n return true\n }\n \n isFileExists(fileName) {\n let file = this.initFile(fileName)\n return FileManager.iCloud().fileExists(`${this.documentDirectory}\/${file}`)\n }\n\n initFile(fileName) {\n const hasSuffix = fileName.lastIndexOf('.') + 1\n return !hasSuffix ? `${fileName}.js` : fileName\n }\n readFile(fileName) {\n const file = this.initFile(fileName)\n return FileManager.iCloud().readString(`${this.documentDirectory}\/${file}`)\n }\n\n })()\n}", 9 | "share_sheet_inputs" : [ 10 | 11 | ] 12 | } -------------------------------------------------------------------------------- /EnvExample.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: pink; icon-glyph: keyboard; 4 | /** 5 | * Author: GideonSenku 6 | * Github: https://github.com/GideonSenku 7 | */ 8 | 9 | const { createWidget } = require("./Env") 10 | 11 | // init 12 | // importModule all of Env 13 | const $ = importModule('Env') 14 | /** 15 | * importModule part of Env 16 | * example: 17 | * get(opts) for HTTP GET Methods 18 | */ 19 | const { get, post, msg } = importModule('Env') 20 | 21 | msg('title','message','option') 22 | 23 | // require some file,if file is JS and support module you can use it dirtctly 24 | const opts = { 25 | moduleName: "vue", 26 | url: "https://cdn.jsdelivr.net/npm/vue@2.6.11" 27 | /** option:foreceDownload type:bool 28 | */ 29 | } 30 | 31 | const Vue = await $.require(opts) 32 | 33 | 34 | const fileName = $.initFile('Env') 35 | log(fileName) 36 | // wirteFile 37 | $.writeFile('nihao.txt',` 38 | const name = Senku, 39 | const age = 18 40 | const height = 1.88 41 | `) 42 | // readFile 43 | const filedata = $.readFile('nihao.txt') 44 | log(filedata) 45 | 46 | // getStr and callback headers 47 | const url = `https://github.com/GideonSenku/Scriptable/blob/master/READMEEN.md` 48 | const res = $.getStr({url},(headers) => { 49 | log(headers) 50 | }) 51 | 52 | log(res) 53 | 54 | 55 | // input, value为input的默认值,可选,默认为null 56 | 57 | const inputValue = await $.input('title', 'message', 'placehoder','value') 58 | const inputValue1 = await $.input('title', 'message', 'placehoder') 59 | 60 | 61 | /** 62 | * 63 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 64 | * :$.time('yyyyMMddHHmmssS') 65 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 66 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 67 | * @param {*} fmt 格式化参数 68 | * @param {*} ts 时间戳 13位 69 | */ 70 | const time = $.time('yyyy-MM-dd HH:mm:ss') 71 | const time = $.time('MMdd HH:mm:ss',1599124137000) 72 | console.log(time) 73 | 74 | // 模版创建小组件 75 | await $.createWidget({ 76 | title: 'demo', // 必须 77 | texts: { // 可选 78 | one: '1', 79 | one0: '2', 80 | one1: '3', 81 | one2: '4', 82 | one3: '5', 83 | one4: '5', 84 | updateTime: 'true' // 可选参数,提供则添加更新时间线 85 | }, 86 | spacing: 1, // 可选参数,设置每行的间隙 87 | preview: 'large' // 可选参数,默认small,预览显小组件大小 88 | }) -------------------------------------------------------------------------------- /EnvExample.scriptable: -------------------------------------------------------------------------------- 1 | { 2 | "always_run_in_app" : false, 3 | "icon" : { 4 | "color" : "pink", 5 | "glyph" : "keyboard" 6 | }, 7 | "name" : "EnvExample", 8 | "script" : "\/**\n * Author: GideonSenku\n * Github: https:\/\/github.com\/GideonSenku\n *\/\n\n\/\/ init\nconst $ = new importModule('Env')()\n\/\/ require some file,if file is JS and support module you can use it dirtctly\nconst opts = {\n moduleName: \"vue\",\n url: \"https:\/\/cdn.jsdelivr.net\/npm\/vue@2.6.11\"\n \/** option:foreceDownload type:bool\n *\/\n}\nconst Vue = await $.require(opts)\nnew Vue({\n template:`\n
>>0?1:0),vt+pt),bt=_t+dt+(xt>>>0>>0?1:0);j=X,N=L,X=U,L=K,U=O,K=I,I=W+St|0,O=P+mt+(I>>>0 >>0?1:0)|0,P=M,W=F,M=E,F=R,E=C,R=D,D=St+xt|0,C=mt+bt+(D>>>0 >>0?1:0)|0}p=i.low=p+D,i.high=d+C+(p>>>0 >>0?1:0),v=n.low=v+R,n.high=_+E+(v>>>0 >>0?1:0),g=o.low=g+F,o.high=y+M+(g>>>0 >>0?1:0),w=s.low=w+W,s.high=B+P+(w>>>0 >>0?1:0),S=c.low=S+I,c.high=k+O+(S>>>0>>0?1:0),x=l.low=x+K,l.high=m+U+(x>>>0 >>0?1:0),H=f.low=H+L,f.high=b+X+(H>>>0 >>0?1:0),A=u.low=A+N,u.high=z+j+(A>>>0 >>0?1:0)},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;e[i>>>5]|=128<<24-i%32,e[30+(i+128>>>10<<5)]=Math.floor(r/4294967296),e[31+(i+128>>>10<<5)]=r,t.sigBytes=4*e.length,this._process();var n=this._hash.toX32();return n},clone:function(){var t=i.clone.call(this);return t._hash=this._hash.clone(),t},blockSize:32});e.SHA512=i._createHelper(f),e.HmacSHA512=i._createHmacHelper(f)}(),function(){var t=l,e=t.x64,r=e.Word,i=e.WordArray,n=t.algo,o=n.SHA512,s=n.SHA384=o.extend({_doReset:function(){this._hash=new i.init([new r.init(3418070365,3238371032),new r.init(1654270250,914150663),new r.init(2438529370,812702999),new r.init(355462360,4144912697),new r.init(1731405415,4290775857),new r.init(2394180231,1750603025),new r.init(3675008525,1694076839),new r.init(1203062813,3204075428)])},_doFinalize:function(){var t=o._doFinalize.call(this);return t.sigBytes-=16,t}});t.SHA384=o._createHelper(s),t.HmacSHA384=o._createHmacHelper(s)}(),l.lib.Cipher||function(t){var e=l,r=e.lib,i=r.Base,n=r.WordArray,o=r.BufferedBlockAlgorithm,s=e.enc,c=(s.Utf8,s.Base64),a=e.algo,h=a.EvpKDF,f=r.Cipher=o.extend({cfg:i.extend(),createEncryptor:function(t,e){return this.create(this._ENC_XFORM_MODE,t,e)},createDecryptor:function(t,e){return this.create(this._DEC_XFORM_MODE,t,e)},init:function(t,e,r){this.cfg=this.cfg.extend(r),this._xformMode=t,this._key=e,this.reset()},reset:function(){o.reset.call(this),this._doReset()},process:function(t){return this._append(t),this._process()},finalize:function(t){t&&this._append(t);var e=this._doFinalize();return e},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){function t(t){return"string"==typeof t?m:w}return function(e){return{encrypt:function(r,i,n){return t(i).encrypt(e,r,i,n)},decrypt:function(r,i,n){return t(i).decrypt(e,r,i,n)}}}}()}),u=(r.StreamCipher=f.extend({_doFinalize:function(){var t=this._process(!0);return t},blockSize:1}),e.mode={}),d=r.BlockCipherMode=i.extend({createEncryptor:function(t,e){return this.Encryptor.create(t,e)},createDecryptor:function(t,e){return this.Decryptor.create(t,e)},init:function(t,e){this._cipher=t,this._iv=e}}),p=u.CBC=function(){function e(e,r,i){var n,o=this._iv;o?(n=o,this._iv=t):n=this._prevBlock;for(var s=0;s>>2];t.sigBytes-=e}},y=(r.BlockCipher=f.extend({cfg:f.cfg.extend({mode:p,padding:v}),reset:function(){var t;f.reset.call(this);var e=this.cfg,r=e.iv,i=e.mode;this._xformMode==this._ENC_XFORM_MODE?t=i.createEncryptor:(t=i.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==t?this._mode.init(this,r&&r.words):(this._mode=t.call(i,this,r&&r.words),this._mode.__creator=t)},_doProcessBlock:function(t,e){this._mode.processBlock(t,e)},_doFinalize:function(){var t,e=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(e.pad(this._data,this.blockSize),t=this._process(!0)):(t=this._process(!0),e.unpad(t)),t},blockSize:4}),r.CipherParams=i.extend({init:function(t){this.mixIn(t)},toString:function(t){return(t||this.formatter).stringify(this)}})),g=e.format={},B=g.OpenSSL={stringify:function(t){var e,r=t.ciphertext,i=t.salt;return e=i?n.create([1398893684,1701076831]).concat(i).concat(r):r,e.toString(c)},parse:function(t){var e,r=c.parse(t),i=r.words;return 1398893684==i[0]&&1701076831==i[1]&&(e=n.create(i.slice(2,4)),i.splice(0,4),r.sigBytes-=16),y.create({ciphertext:r,salt:e})}},w=r.SerializableCipher=i.extend({cfg:i.extend({format:B}),encrypt:function(t,e,r,i){i=this.cfg.extend(i);var n=t.createEncryptor(r,i),o=n.finalize(e),s=n.cfg;return y.create({ciphertext:o,key:r,iv:s.iv,algorithm:t,mode:s.mode,padding:s.padding,blockSize:t.blockSize,formatter:i.format})},decrypt:function(t,e,r,i){i=this.cfg.extend(i),e=this._parse(e,i.format);var n=t.createDecryptor(r,i).finalize(e.ciphertext);return n},_parse:function(t,e){return"string"==typeof t?e.parse(t,this):t}}),k=e.kdf={},S=k.OpenSSL={execute:function(t,e,r,i){i||(i=n.random(8));var o=h.create({keySize:e+r}).compute(t,i),s=n.create(o.words.slice(e),4*r);return o.sigBytes=4*e,y.create({key:o,iv:s,salt:i})}},m=r.PasswordBasedCipher=w.extend({cfg:w.cfg.extend({kdf:S}),encrypt:function(t,e,r,i){i=this.cfg.extend(i);var n=i.kdf.execute(r,t.keySize,t.ivSize);i.iv=n.iv;var o=w.encrypt.call(this,t,e,n.key,i);return o.mixIn(n),o},decrypt:function(t,e,r,i){i=this.cfg.extend(i),e=this._parse(e,i.format);var n=i.kdf.execute(r,t.keySize,t.ivSize,e.salt);i.iv=n.iv;var o=w.decrypt.call(this,t,e,n.key,i);return o}})}(),l.mode.CFB=function(){function t(t,e,r,i){var n,o=this._iv;o?(n=o.slice(0),this._iv=void 0):n=this._prevBlock,i.encryptBlock(n,0);for(var s=0;s >>2]|=n<<24-o%4*8,t.sigBytes+=n},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},l.pad.Iso10126={pad:function(t,e){var r=4*e,i=r-t.sigBytes%r;t.concat(l.lib.WordArray.random(i-1)).concat(l.lib.WordArray.create([i<<24],1))},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},l.pad.Iso97971={pad:function(t,e){t.concat(l.lib.WordArray.create([2147483648],1)),l.pad.ZeroPadding.pad(t,e)},unpad:function(t){l.pad.ZeroPadding.unpad(t),t.sigBytes--}},l.mode.OFB=(a=l.lib.BlockCipherMode.extend(),h=a.Encryptor=a.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,n=this._iv,o=this._keystream;n&&(o=this._keystream=n.slice(0),this._iv=void 0),r.encryptBlock(o,0);for(var s=0;s>>8^255&l^99,n[r]=l,o[l]=r;var _=t[r],v=t[_],y=t[v],g=257*t[l]^16843008*l;s[r]=g<<24|g>>>8,c[r]=g<<16|g>>>16,a[r]=g<<8|g>>>24,h[r]=g;g=16843009*y^65537*v^257*_^16843008*r;f[l]=g<<24|g>>>8,u[l]=g<<16|g>>>16,d[l]=g<<8|g>>>24,p[l]=g,r?(r=_^t[t[t[y^_]]],i^=t[t[i]]):r=i=1}})();var _=[0,1,2,4,8,16,32,64,128,27,54],v=i.AES=r.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var t=this._keyPriorReset=this._key,e=t.words,r=t.sigBytes/4,i=this._nRounds=r+6,o=4*(i+1),s=this._keySchedule=[],c=0;c 6&&c%r==4&&(l=n[l>>>24]<<24|n[l>>>16&255]<<16|n[l>>>8&255]<<8|n[255&l]):(l=l<<8|l>>>24,l=n[l>>>24]<<24|n[l>>>16&255]<<16|n[l>>>8&255]<<8|n[255&l],l^=_[c/r|0]<<24),s[c]=s[c-r]^l);for(var a=this._invKeySchedule=[],h=0;h >>24]]^u[n[l>>>16&255]]^d[n[l>>>8&255]]^p[n[255&l]]}}},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._keySchedule,s,c,a,h,n)},decryptBlock:function(t,e){var r=t[e+1];t[e+1]=t[e+3],t[e+3]=r,this._doCryptBlock(t,e,this._invKeySchedule,f,u,d,p,o);r=t[e+1];t[e+1]=t[e+3],t[e+3]=r},_doCryptBlock:function(t,e,r,i,n,o,s,c){for(var a=this._nRounds,h=t[e]^r[0],l=t[e+1]^r[1],f=t[e+2]^r[2],u=t[e+3]^r[3],d=4,p=1;p>>24]^n[l>>>16&255]^o[f>>>8&255]^s[255&u]^r[d++],v=i[l>>>24]^n[f>>>16&255]^o[u>>>8&255]^s[255&h]^r[d++],y=i[f>>>24]^n[u>>>16&255]^o[h>>>8&255]^s[255&l]^r[d++],g=i[u>>>24]^n[h>>>16&255]^o[l>>>8&255]^s[255&f]^r[d++];h=_,l=v,f=y,u=g}_=(c[h>>>24]<<24|c[l>>>16&255]<<16|c[f>>>8&255]<<8|c[255&u])^r[d++],v=(c[l>>>24]<<24|c[f>>>16&255]<<16|c[u>>>8&255]<<8|c[255&h])^r[d++],y=(c[f>>>24]<<24|c[u>>>16&255]<<16|c[h>>>8&255]<<8|c[255&l])^r[d++],g=(c[u>>>24]<<24|c[h>>>16&255]<<16|c[l>>>8&255]<<8|c[255&f])^r[d++];t[e]=_,t[e+1]=v,t[e+2]=y,t[e+3]=g},keySize:8});t.AES=r._createHelper(v)}(),function(){function t(t,e){var r=(this._lBlock>>>t^this._rBlock)&e;this._rBlock^=r,this._lBlock^=r< >>t^this._lBlock)&e;this._lBlock^=r,this._rBlock^=r< >>5]>>>31-n%32&1}for(var o=this._subKeys=[],s=0;s<16;s++){var l=o[s]=[],f=h[s];for(i=0;i<24;i++)l[i/6|0]|=r[(a[i]-1+f)%28]<<31-i%6,l[4+(i/6|0)]|=r[28+(a[i+24]-1+f)%28]<<31-i%6;l[0]=l[0]<<1|l[0]>>>31;for(i=1;i<7;i++)l[i]=l[i]>>>4*(i-1)+3;l[7]=l[7]<<5|l[7]>>>27}var u=this._invSubKeys=[];for(i=0;i<16;i++)u[i]=o[15-i]},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._subKeys)},decryptBlock:function(t,e){this._doCryptBlock(t,e,this._invSubKeys)},_doCryptBlock:function(r,i,n){this._lBlock=r[i],this._rBlock=r[i+1],t.call(this,4,252645135),t.call(this,16,65535),e.call(this,2,858993459),e.call(this,8,16711935),t.call(this,1,1431655765);for(var o=0;o<16;o++){for(var s=n[o],c=this._lBlock,a=this._rBlock,h=0,l=0;l<8;l++)h|=f[l][((a^s[l])&u[l])>>>0];this._lBlock=a,this._rBlock=c^h}var d=this._lBlock;this._lBlock=this._rBlock,this._rBlock=d,t.call(this,1,1431655765),e.call(this,8,16711935),e.call(this,2,858993459),t.call(this,16,65535),t.call(this,4,252645135),r[i]=this._lBlock,r[i+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});r.DES=o._createHelper(d);var p=s.TripleDES=o.extend({_doReset:function(){var t=this._key,e=t.words;if(2!==e.length&&4!==e.length&&e.length<6)throw new Error("Invalid key length - 3DES requires the key length to be 64, 128, 192 or >192.");var r=e.slice(0,2),i=e.length<4?e.slice(0,2):e.slice(2,4),o=e.length<6?e.slice(0,2):e.slice(4,6);this._des1=d.createEncryptor(n.create(r)),this._des2=d.createEncryptor(n.create(i)),this._des3=d.createEncryptor(n.create(o))},encryptBlock:function(t,e){this._des1.encryptBlock(t,e),this._des2.decryptBlock(t,e),this._des3.encryptBlock(t,e)},decryptBlock:function(t,e){this._des3.decryptBlock(t,e),this._des2.encryptBlock(t,e),this._des1.decryptBlock(t,e)},keySize:6,ivSize:2,blockSize:2});r.TripleDES=o._createHelper(p)}(),function(){function t(){for(var t=this._S,e=this._i,r=this._j,i=0,n=0;n<4;n++){e=(e+1)%256,r=(r+t[e])%256;var o=t[e];t[e]=t[r],t[r]=o,i|=t[(t[e]+t[r])%256]<<24-8*n}return this._i=e,this._j=r,i}var e=l,r=e.lib,i=r.StreamCipher,n=e.algo,o=n.RC4=i.extend({_doReset:function(){for(var t=this._key,e=t.words,r=t.sigBytes,i=this._S=[],n=0;n<256;n++)i[n]=n;n=0;for(var o=0;n<256;n++){var s=n%r,c=e[s>>>2]>>>24-s%4*8&255;o=(o+i[n]+c)%256;var a=i[n];i[n]=i[o],i[o]=a}this._i=this._j=0},_doProcessBlock:function(e,r){e[r]^=t.call(this)},keySize:8,ivSize:0});e.RC4=i._createHelper(o);var s=n.RC4Drop=o.extend({cfg:o.cfg.extend({drop:192}),_doReset:function(){o._doReset.call(this);for(var e=this.cfg.drop;e>0;e--)t.call(this)}});e.RC4Drop=i._createHelper(s)}(),l.mode.CTRGladman=function(){function t(t){if(255==(t>>24&255)){var e=t>>16&255,r=t>>8&255,i=255&t;255===e?(e=0,255===r?(r=0,255===i?i=0:++i):++r):++e,t=0,t+=e<<16,t+=r<<8,t+=i}else t+=1<<24;return t}function e(e){return 0===(e[0]=t(e[0]))&&(e[1]=t(e[1])),e}var r=l.lib.BlockCipherMode.extend(),i=r.Encryptor=r.extend({processBlock:function(t,r){var i=this._cipher,n=i.blockSize,o=this._iv,s=this._counter;o&&(s=this._counter=o.slice(0),this._iv=void 0),e(s);var c=s.slice(0);i.encryptBlock(c,0);for(var a=0;a >>0 >>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],n=65535&i,o=i>>>16,a=((n*n>>>17)+n*o>>>15)+o*o,h=((4294901760&i)*i|0)+((65535&i)*i|0);c[r]=a^h}t[0]=c[0]+(c[7]<<16|c[7]>>>16)+(c[6]<<16|c[6]>>>16)|0,t[1]=c[1]+(c[0]<<8|c[0]>>>24)+c[7]|0,t[2]=c[2]+(c[1]<<16|c[1]>>>16)+(c[0]<<16|c[0]>>>16)|0,t[3]=c[3]+(c[2]<<8|c[2]>>>24)+c[1]|0,t[4]=c[4]+(c[3]<<16|c[3]>>>16)+(c[2]<<16|c[2]>>>16)|0,t[5]=c[5]+(c[4]<<8|c[4]>>>24)+c[3]|0,t[6]=c[6]+(c[5]<<16|c[5]>>>16)+(c[4]<<16|c[4]>>>16)|0,t[7]=c[7]+(c[6]<<8|c[6]>>>24)+c[5]|0}var e=l,r=e.lib,i=r.StreamCipher,n=e.algo,o=[],s=[],c=[],a=n.Rabbit=i.extend({_doReset:function(){for(var e=this._key.words,r=this.cfg.iv,i=0;i<4;i++)e[i]=16711935&(e[i]<<8|e[i]>>>24)|4278255360&(e[i]<<24|e[i]>>>8);var n=this._X=[e[0],e[3]<<16|e[2]>>>16,e[1],e[0]<<16|e[3]>>>16,e[2],e[1]<<16|e[0]>>>16,e[3],e[2]<<16|e[1]>>>16],o=this._C=[e[2]<<16|e[2]>>>16,4294901760&e[0]|65535&e[1],e[3]<<16|e[3]>>>16,4294901760&e[1]|65535&e[2],e[0]<<16|e[0]>>>16,4294901760&e[2]|65535&e[3],e[1]<<16|e[1]>>>16,4294901760&e[3]|65535&e[0]];this._b=0;for(i=0;i<4;i++)t.call(this);for(i=0;i<8;i++)o[i]^=n[i+4&7];if(r){var s=r.words,c=s[0],a=s[1],h=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8),l=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8),f=h>>>16|4294901760&l,u=l<<16|65535&h;o[0]^=h,o[1]^=f,o[2]^=l,o[3]^=u,o[4]^=h,o[5]^=f,o[6]^=l,o[7]^=u;for(i=0;i<4;i++)t.call(this)}},_doProcessBlock:function(e,r){var i=this._X;t.call(this),o[0]=i[0]^i[5]>>>16^i[3]<<16,o[1]=i[2]^i[7]>>>16^i[5]<<16,o[2]=i[4]^i[1]>>>16^i[7]<<16,o[3]=i[6]^i[3]>>>16^i[1]<<16;for(var n=0;n<4;n++)o[n]=16711935&(o[n]<<8|o[n]>>>24)|4278255360&(o[n]<<24|o[n]>>>8),e[r+n]^=o[n]},blockSize:4,ivSize:2});e.Rabbit=i._createHelper(a)}(),l.mode.CTR=function(){var t=l.lib.BlockCipherMode.extend(),e=t.Encryptor=t.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,n=this._iv,o=this._counter;n&&(o=this._counter=n.slice(0),this._iv=void 0);var s=o.slice(0);r.encryptBlock(s,0),o[i-1]=o[i-1]+1|0;for(var c=0;c>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],n=65535&i,o=i>>>16,a=((n*n>>>17)+n*o>>>15)+o*o,h=((4294901760&i)*i|0)+((65535&i)*i|0);c[r]=a^h}t[0]=c[0]+(c[7]<<16|c[7]>>>16)+(c[6]<<16|c[6]>>>16)|0,t[1]=c[1]+(c[0]<<8|c[0]>>>24)+c[7]|0,t[2]=c[2]+(c[1]<<16|c[1]>>>16)+(c[0]<<16|c[0]>>>16)|0,t[3]=c[3]+(c[2]<<8|c[2]>>>24)+c[1]|0,t[4]=c[4]+(c[3]<<16|c[3]>>>16)+(c[2]<<16|c[2]>>>16)|0,t[5]=c[5]+(c[4]<<8|c[4]>>>24)+c[3]|0,t[6]=c[6]+(c[5]<<16|c[5]>>>16)+(c[4]<<16|c[4]>>>16)|0,t[7]=c[7]+(c[6]<<8|c[6]>>>24)+c[5]|0}var e=l,r=e.lib,i=r.StreamCipher,n=e.algo,o=[],s=[],c=[],a=n.RabbitLegacy=i.extend({_doReset:function(){var e=this._key.words,r=this.cfg.iv,i=this._X=[e[0],e[3]<<16|e[2]>>>16,e[1],e[0]<<16|e[3]>>>16,e[2],e[1]<<16|e[0]>>>16,e[3],e[2]<<16|e[1]>>>16],n=this._C=[e[2]<<16|e[2]>>>16,4294901760&e[0]|65535&e[1],e[3]<<16|e[3]>>>16,4294901760&e[1]|65535&e[2],e[0]<<16|e[0]>>>16,4294901760&e[2]|65535&e[3],e[1]<<16|e[1]>>>16,4294901760&e[3]|65535&e[0]];this._b=0;for(var o=0;o<4;o++)t.call(this);for(o=0;o<8;o++)n[o]^=i[o+4&7];if(r){var s=r.words,c=s[0],a=s[1],h=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8),l=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8),f=h>>>16|4294901760&l,u=l<<16|65535&h;n[0]^=h,n[1]^=f,n[2]^=l,n[3]^=u,n[4]^=h,n[5]^=f,n[6]^=l,n[7]^=u;for(o=0;o<4;o++)t.call(this)}},_doProcessBlock:function(e,r){var i=this._X;t.call(this),o[0]=i[0]^i[5]>>>16^i[3]<<16,o[1]=i[2]^i[7]>>>16^i[5]<<16,o[2]=i[4]^i[1]>>>16^i[7]<<16,o[3]=i[6]^i[3]>>>16^i[1]<<16;for(var n=0;n<4;n++)o[n]=16711935&(o[n]<<8|o[n]>>>24)|4278255360&(o[n]<<24|o[n]>>>8),e[r+n]^=o[n]},blockSize:4,ivSize:2});e.RabbitLegacy=i._createHelper(a)}(),l.pad.ZeroPadding={pad:function(t,e){var r=4*e;t.clamp(),t.sigBytes+=r-(t.sigBytes%r||r)},unpad:function(t){var e=t.words,r=t.sigBytes-1;for(r=t.sigBytes-1;r>=0;r--)if(e[r>>>2]>>>24-r%4*8&255){t.sigBytes=r+1;break}}},l}); -------------------------------------------------------------------------------- /v2ex/hot.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: blue; icon-glyph: film; 4 | /* 5 | * Author: recall704 6 | * Github: https://github.com/recall704/Scriptable 7 | * 本脚本使用了@Gideon_Senku的Env.scriptable,感谢! 8 | */ 9 | const goupdate = false; 10 | const $ = importModule("Env"); 11 | const title = `📖 v2ex热榜`; 12 | const preview = "medium"; 13 | const spacing = 5; 14 | 15 | const res = await getinfo(); 16 | 17 | let widget = await createWidget(res); 18 | Script.setWidget(widget); 19 | Script.complete(); 20 | 21 | async function createWidget(res) { 22 | var group = res.data; 23 | items = []; 24 | urls = []; 25 | for (var i = 0; i < 6; i++) { 26 | var item = res[i].title; 27 | var v2exUrl = res[i].url; 28 | items.push(item); 29 | urls.push(v2exUrl); 30 | } 31 | console.log(items); 32 | 33 | const opts = { 34 | title, 35 | texts: { 36 | text1: { text : `• ${items[0]}`, url: urls[0] }, 37 | text2: { text : `• ${items[1]}`, url: urls[1] }, 38 | text3: { text : `• ${items[2]}`, url: urls[2] }, 39 | text4: { text : `• ${items[3]}`, url: urls[3] }, 40 | text5: { text : `• ${items[4]}`, url: urls[4] }, 41 | text6: { text : `• ${items[5]}`, url: urls[5] }, 42 | battery: "true" 43 | }, 44 | preview, 45 | spacing, 46 | }; 47 | 48 | let widget = await $.createWidget(opts); 49 | return widget; 50 | } 51 | 52 | async function getinfo() { 53 | const url = { 54 | url: `https://www.v2ex.com/api/topics/hot.json`, 55 | }; 56 | const res = await $.get(url); 57 | log(res); 58 | return res; 59 | } 60 | 61 | //更新代码 62 | function update() { 63 | log("🔔更新脚本开始!"); 64 | scripts.forEach(async (script) => { 65 | await $.getFile(script); 66 | }); 67 | log("🔔更新脚本结束!"); 68 | } 69 | 70 | const scripts = [ 71 | { 72 | moduleName: "v2ex_hot", 73 | url: 74 | "https://raw.githubusercontent.com/recall704/Scriptable/master/v2ex/hot.js", 75 | }, 76 | ]; 77 | if (goupdate == true) update(); 78 | -------------------------------------------------------------------------------- /v2ex/latest.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: blue; icon-glyph: film; 4 | /* 5 | * Author: recall704 6 | * Github: https://github.com/recall704/Scriptable 7 | * 本脚本使用了@Gideon_Senku的Env.scriptable,感谢! 8 | */ 9 | const goupdate = false; 10 | const $ = importModule("Env"); 11 | const title = `📖 v2ex最新`; 12 | const preview = "medium"; 13 | const spacing = 5; 14 | 15 | const res = await getinfo(); 16 | 17 | let widget = await createWidget(res); 18 | Script.setWidget(widget); 19 | Script.complete(); 20 | 21 | async function createWidget(res) { 22 | var group = res.data; 23 | items = []; 24 | urls = []; 25 | for (var i = 0; i < 6; i++) { 26 | var item = res[i].title; 27 | var v2exUrl = res[i].url; 28 | items.push(item); 29 | urls.push(v2exUrl); 30 | } 31 | console.log(items); 32 | 33 | const opts = { 34 | title, 35 | texts: { 36 | text1: { text : `• ${items[0]}`, url: urls[0] }, 37 | text2: { text : `• ${items[1]}`, url: urls[1] }, 38 | text3: { text : `• ${items[2]}`, url: urls[2] }, 39 | text4: { text : `• ${items[3]}`, url: urls[3] }, 40 | text5: { text : `• ${items[4]}`, url: urls[4] }, 41 | text6: { text : `• ${items[5]}`, url: urls[5] }, 42 | battery: "true" 43 | }, 44 | preview, 45 | spacing, 46 | }; 47 | 48 | let widget = await $.createWidget(opts); 49 | return widget; 50 | } 51 | 52 | async function getinfo() { 53 | const url = { 54 | url: `https://www.v2ex.com/api/topics/latest.json`, 55 | }; 56 | const res = await $.get(url); 57 | log(res); 58 | return res; 59 | } 60 | 61 | //更新代码 62 | function update() { 63 | log("🔔更新脚本开始!"); 64 | scripts.forEach(async (script) => { 65 | await $.getFile(script); 66 | }); 67 | log("🔔更新脚本结束!"); 68 | } 69 | 70 | const scripts = [ 71 | { 72 | moduleName: "v2ex_latest", 73 | url: 74 | "https://raw.githubusercontent.com/recall704/Scriptable/master/v2ex/latest.js", 75 | }, 76 | ]; 77 | if (goupdate == true) update(); 78 | --------------------------------------------------------------------------------