├── Env ├── Env-location └── Env-sun ├── end.js ├── start.js ├── backup.js ├── BilibiliMonitor.js ├── DoubanMonitor.js ├── 知乎热榜.js ├── 网易云热评.js ├── Random Scriptable API.js ├── B站热榜.js ├── 微博热榜.js ├── 小左底.js ├── 透明背景适配最新iphone.js ├── 中国联通查询.js ├── Env 2.js ├── ENV.js ├── 彩云天气.js ├── 「DJG」彩云天气.js └── 动森.js /Env/Env-location: -------------------------------------------------------------------------------- 1 | 39.936374121767095,116.24527675404977 -------------------------------------------------------------------------------- /end.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: light-brown; icon-glyph: magic; 4 | FILE = FileManager.local() 5 | FILEPATH = FILE.joinPath(FILE.libraryDirectory(), "/DJG.js") 6 | const { DJG, Runing } = importModule(FILEPATH); 7 | log(DJG) -------------------------------------------------------------------------------- /start.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: magic; 4 | var json=["Installer","https://gitee.com/ClydeTime/scriptable/raw/master/DJG.js"]; 5 | text=`${json[1]}`; 6 | 7 | D = await new Request(text).load(); 8 | const file = FileManager.local(); 9 | 10 | const path = file.joinPath(file.libraryDirectory(), "DJG.js"); 11 | 12 | file.write(path, D); 13 | -------------------------------------------------------------------------------- /backup.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: magic; 4 | var json=["Installer","https://gitee.com/script_djg/scriptable/raw/master/Script/DJG.js"]; 5 | text=`${json[1]}`; 6 | 7 | D = await new Request(text); 8 | const file = FileManager.local(); 9 | 10 | const path = file.joinPath(file.libraryDirectory(), "DJG_backup.js"); 11 | 12 | file.write(path, D); 13 | 14 | log(D); -------------------------------------------------------------------------------- /Env/Env-sun: -------------------------------------------------------------------------------- 1 | {"results":{"sunset":"2022-08-20T11:07:06+00:00","nautical_twilight_end":"2022-08-20T12:08:43+00:00","astronomical_twilight_end":"2022-08-20T12:45:18+00:00","civil_twilight_begin":"2022-08-19T21:02:44+00:00","sunrise":"2022-08-19T21:29:53+00:00","solar_noon":"2022-08-20T04:18:29+00:00","day_length":49033,"civil_twilight_end":"2022-08-20T11:34:14+00:00","astronomical_twilight_begin":"2022-08-19T19:51:40+00:00","nautical_twilight_begin":"2022-08-19T20:28:15+00:00"},"status":"OK"} -------------------------------------------------------------------------------- /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 = false; 10 | const $ = importModule("Env"); 11 | var rid = 0; //rid对应不同的B站榜单:0全站,1动画,3音乐,4游戏,5娱乐,36科技,119鬼畜,129舞蹈。 12 | const title = `💗 B站榜单`; 13 | const preview = "medium"; 14 | const goto = 'app'; // 可更改为 browser,跳转到浏览器,选择跳转 app 时若未安装 app,则会无响应 15 | const spacing = 5; 16 | 17 | try { 18 | var { bilibili } = importModule("Config"); 19 | rid = bilibili(); 20 | console.log("将使用配置文件内B站配置"); 21 | } catch (e) { 22 | console.log("将使用脚本内B站配置"); 23 | } 24 | 25 | const res = await getinfo(); 26 | 27 | let widget = await createWidget(res); 28 | Script.setWidget(widget); 29 | Script.complete(); 30 | 31 | function decideGoto(item) { 32 | switch(goto) { 33 | case 'app': 34 | return item.uri; 35 | case 'browser': 36 | return `https://bilibili.com/${item.goto}${item.param}`; 37 | default: 38 | return void 0; 39 | } 40 | } 41 | 42 | async function createWidget(res) { 43 | var group = res.data; 44 | items = []; 45 | for (var i = 0; i < 6; i++) { 46 | var item = group[i].title; 47 | items.push(item); 48 | } 49 | console.log(items); 50 | 51 | const opts = { 52 | title, 53 | texts: { 54 | text1: { text: `• ${group[0].title}`, url: decideGoto(group[0]) }, 55 | text2: { text: `• ${group[1].title}`, url: decideGoto(group[1]) }, 56 | text3: { text: `• ${group[2].title}`, url: decideGoto(group[2]) }, 57 | text4: { text: `• ${group[3].title}`, url: decideGoto(group[3]) }, 58 | text5: { text: `• ${group[4].title}`, url: decideGoto(group[4]) }, 59 | text6: { text: `• ${group[5].title}`, url: decideGoto(group[5]) }, 60 | battery: "true", 61 | }, 62 | preview, 63 | spacing, 64 | }; 65 | 66 | let widget = await $.createWidget(opts); 67 | return widget; 68 | } 69 | 70 | async function getinfo() { 71 | const blRequest = { 72 | url: `https://app.bilibili.com/x/v2/rank/region?rid=${rid}`, 73 | }; 74 | const res = await $.get(blRequest); 75 | log(res); 76 | return res; 77 | } 78 | 79 | //更新代码 80 | function update() { 81 | log("🔔更新脚本开始!"); 82 | scripts.forEach(async (script) => { 83 | await $.getFile(script); 84 | }); 85 | log("🔔更新脚本结束!"); 86 | } 87 | 88 | const scripts = [ 89 | { 90 | moduleName: "BilibiliMonitor", 91 | url: 92 | "https://raw.githubusercontent.com/evilbutcher/Scriptables/master/BilibiliMonitor.js", 93 | }, 94 | ]; 95 | if (goupdate == true) update(); 96 | -------------------------------------------------------------------------------- /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 = false; 10 | const $ = importModule("Env"); 11 | const title = `🎞 豆瓣电影`; 12 | const preview = "medium"; 13 | const spacing = 5; 14 | const goto = 'app'; // 可更改为 browser,跳转到浏览器,选择跳转 app 时若未安装 app,则会无响应 15 | 16 | const res = await getinfo(); 17 | 18 | let widget = await createWidget(res); 19 | Script.setWidget(widget); 20 | Script.complete(); 21 | 22 | function decideGoto(item) { 23 | switch(goto) { 24 | case 'app': 25 | return item.uri; 26 | case 'browser': 27 | return item.url; 28 | default: 29 | return void 0; 30 | } 31 | } 32 | 33 | async function createWidget(res) { 34 | var group = res["subject_collection_items"]; 35 | items = []; 36 | for (var i = 0; i < 6; i++) { 37 | var gTitle = group[i].title; 38 | var rating = group[i].rating; 39 | if (rating == null) { 40 | var star = "暂无"; 41 | } else { 42 | star = rating["star_count"]; 43 | } 44 | var item = gTitle + " " + star + "✨"; 45 | items.push(item); 46 | } 47 | console.log(items); 48 | 49 | const opts = { 50 | title, 51 | texts: { 52 | text1: { text: `• ${items[0]}`, url: decideGoto(group[0]) }, 53 | text2: { text: `• ${items[1]}`, url: decideGoto(group[1]) }, 54 | text3: { text: `• ${items[2]}`, url: decideGoto(group[2]) }, 55 | text4: { text: `• ${items[3]}`, url: decideGoto(group[3]) }, 56 | text5: { text: `• ${items[4]}`, url: decideGoto(group[4]) }, 57 | text6: { text: `• ${items[5]}`, url: decideGoto(group[5]) }, 58 | battery: "true", 59 | }, 60 | preview, 61 | spacing, 62 | }; 63 | 64 | let widget = await $.createWidget(opts); 65 | return widget; 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 | //更新代码 83 | function update() { 84 | log("🔔更新脚本开始!"); 85 | scripts.forEach(async (script) => { 86 | await $.getFile(script); 87 | }); 88 | log("🔔更新脚本结束!"); 89 | } 90 | 91 | const scripts = [ 92 | { 93 | moduleName: "DoubanMonitor", 94 | url: 95 | "https://raw.githubusercontent.com/evilbutcher/Scriptables/master/DoubanMonitor.js", 96 | }, 97 | ]; 98 | if (goupdate == true) update(); 99 | -------------------------------------------------------------------------------- /知乎热榜.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: feather-alt; 4 | /* 5 | // *************************** 6 | // 环境框架 :@ DmYY 7 | // author :原作者未知 DJG修改 8 | */ 9 | 10 | const { DJG, Runing } = importModule( 11 | FileManager.local().joinPath( 12 | FileManager.local().libraryDirectory(), 13 | "/DJG.js" 14 | ) 15 | ); 16 | 17 | // @组件代码开始 18 | class Widget extends DJG { 19 | constructor(arg) { 20 | super(arg); 21 | this.name = '知乎热榜' 22 | this.widget_ID = "DJG-120" 23 | this.version = "V3.1" 24 | this.API = 'https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50&desktop=true'; 25 | this.logo = 'https://s1.ax1x.com/2022/07/10/jyQAVs.png' 26 | 27 | this.Run(module.filename, args); 28 | } 29 | 30 | /** 31 | * 渲染函数,函数名固定 32 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 33 | */ 34 | async render () { 35 | let widget = this.getWidget(); 36 | await this.getWidgetBackgroundImage(widget) 37 | try{ 38 | let data = await this.getData(); 39 | switch (this.widgetFamily) { 40 | case 'small': 41 | await this.renderSmall(widget, data); 42 | break; 43 | case 'medium': 44 | await this.renderMedium(widget, data); 45 | break; 46 | default: 47 | await this.renderMedium(widget, data, 13); 48 | break; 49 | } 50 | }catch(e){ 51 | this.ERROR.push({error:e.toString()}); 52 | } 53 | return widget; 54 | } 55 | 56 | // 小组件 57 | async renderSmall (w, data) { 58 | await this.renderHeader(w, this.logo, this.name) 59 | const cell = w.addStack() 60 | this.addText(cell, '1', 13, {font:'light', color:'#fe2d46'}) 61 | cell.addSpacer(8) 62 | this.addText(cell, data[0]['target']['title'], 13, {font:'light'}) 63 | w.addSpacer() 64 | let score = data[0]['target'].follower_count; 65 | this.addText(w, ` ${score}`, 10, {font:'light', opacity:0.8}); 66 | let url = `zhihu://question/${data[0].target.id}`; 67 | cell.url = url; 68 | } 69 | 70 | // 中组件 71 | async renderMedium (w, data, num = 5) { 72 | w.addSpacer(5); 73 | await this.renderHeader(w, this.logo, this.name, 8) 74 | data.slice(0, num).map((d, i) => { 75 | const cell = w.addStack(); 76 | cell.centerAlignContent(); 77 | let col = null; 78 | if (i === 0) { col = '#fe2d46' 79 | } else if (i === 1) { col = '#ff6600' 80 | } else if (i === 2) { col = '#faa90e' 81 | } else { col = '#9195a3'} 82 | this.addText(cell, String(i+1), 13, {font:'light', color:col, font:'bold'}) 83 | cell.addSpacer(10); 84 | let title = d['target']['title']; 85 | this.addText(cell, title, 13, {font:'light', lineLimit:1}) 86 | cell.addSpacer(); 87 | let url = `zhihu://question/${d.target.id}`; 88 | cell.url = url; 89 | w.addSpacer(6) 90 | }) 91 | } 92 | 93 | /** 94 | * 获取数据函数,函数名可不固定 95 | */ 96 | async getData () { 97 | let data = await this.httpGet(this.API); 98 | return data.data; 99 | } 100 | 101 | actionUrl (name = '', data = '') { 102 | let u = URLScheme.forRunningScript() 103 | let q = `act=${encodeURIComponent(name)}&data=${encodeURIComponent(data)}&__arg=${encodeURIComponent(this.arg)}&__size=${this.widgetFamily}` 104 | let result = '' 105 | if (u.includes('run?')) { 106 | result = `${u}&${q}` 107 | } else { 108 | result = `${u}?${q}` 109 | } 110 | return result 111 | } 112 | 113 | // 添加设置信息 114 | Run(filename, args) { 115 | if (config.runsInApp) { 116 | this.registerAction("基础设置", this.setWidgetConfig); 117 | } 118 | } 119 | } 120 | 121 | // @组件代码结束 122 | await Runing(Widget) -------------------------------------------------------------------------------- /网易云热评.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: feather-alt; 4 | /* 5 | // *************************** 6 | // 环境框架 :@ DmYY 7 | // script :原作者不详, 由DJG修改 8 | */ 9 | 10 | const { DJG, Runing } = importModule( 11 | FileManager.local().joinPath( 12 | FileManager.local().libraryDirectory(), 13 | "/DJG.js" 14 | ) 15 | ); 16 | 17 | // @组件代码开始 18 | class Widget extends DJG { 19 | constructor(arg) { 20 | super(arg); 21 | this.name = '网易云热评' 22 | this.widget_ID = "DJG-107" 23 | this.version = "V3.5" 24 | 25 | this.Run(module.filename, args); 26 | } 27 | 28 | /** 29 | * 渲染函数,函数名固定 30 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 31 | */ 32 | async render () { 33 | let widget = this.getWidget(); 34 | await this.getWidgetBackgroundImage(widget); 35 | if (!this.settings.url) { 36 | if(config.runsInWidget) return await this.renderAlert('需要申请 \'网易云热评\' 接口'); 37 | return await this.inputKey();; 38 | } 39 | try{ 40 | switch (this.widgetFamily) { 41 | case 'small': 42 | await this.renderMedium(widget); 43 | break; 44 | case 'medium': 45 | await this.renderMedium(widget); 46 | break; 47 | default: 48 | await this.renderMedium(widget); 49 | break; 50 | } 51 | }catch(e){ 52 | this.ERROR.push({error:e.toString()}); 53 | } 54 | return widget; 55 | } 56 | 57 | // 中组件 58 | async renderMedium(w){ 59 | let hotcommentsData = await this.getData(); 60 | 61 | this.settings.text = hotcommentsData.data.content; 62 | this.saveSettings(false); 63 | 64 | w.setPadding(10, 10, 10, 10); 65 | this.addText(w, '❝ ', 18); 66 | w.addSpacer(); 67 | this.addText(w, `${hotcommentsData.data.content}`, 16, {font:'light', align:'center', zoom:0.2}); 68 | 69 | w.addSpacer(); 70 | 71 | const footerStack = w.addStack(); 72 | footerStack.bottomAlignContent(); 73 | 74 | const profileStack = footerStack.addStack(); 75 | profileStack.topAlignContent(); 76 | 77 | const image = await this.getImageByUrl(hotcommentsData.data.avatar); 78 | this.addImage(profileStack, image, {w:30, h:30}, {corner:15, borderColor:'bfbfbf', borderW:4}); 79 | 80 | profileStack.addSpacer(10); 81 | 82 | const nameStack = profileStack.addStack(); 83 | nameStack.layoutVertically(); 84 | this.addText(nameStack, hotcommentsData.data.nickname, 10, {color:'FF7F00', url:'orpheuswidget://'}) 85 | nameStack.addSpacer(4) 86 | this.addText(nameStack, `—— 评论来自《${hotcommentsData.data.songAutho}》`, 10, {font:'semibold', opacity:0.6}) 87 | 88 | footerStack.addSpacer(); 89 | 90 | let docsSymbol = SFSymbol.named("book") 91 | this.addImage(footerStack, docsSymbol.image, {w:20, h:20}, {color:'this', url:hotcommentsData.data.url}); 92 | 93 | footerStack.addSpacer(8) 94 | } 95 | 96 | async getData(){ 97 | const data = await this.httpGet(this.settings.url) 98 | if(data.code != 200){ 99 | let error = `错误码:${data.code},请在接口文档中查看错误码说明`; 100 | this.ERROR.push({error: error}); 101 | } 102 | return data; 103 | } 104 | 105 | // 木小果API 106 | async inputKey(){ 107 | const title = '申请\'网易云热评\'接口'; 108 | const message = '🟢 登陆成功后 🟢\n点击左上角,选择接口列表\n找到\'网易云热评\',向左滑动\n🔸点击购买,无需付费🔸\n跳到我的接口后,向左滑动\n找到并复制接口链接'; 109 | const idx = await this.generateAlert(message, ['申请接口','输入接口'], title); 110 | if(idx === 0) return await Safari.open('https://api.muxiaoguo.cn/user/register.html'); 111 | await this.setCustomAction("木小果API", "请正确输入接口链接", { 112 | url: "接口链接", 113 | }); 114 | } 115 | 116 | // 添加设置信息 117 | Run(filename, args) { 118 | if (config.runsInApp) { 119 | this.registerAction("基础设置", this.setWidgetConfig); 120 | this.registerAction("复制热评", async () => { 121 | if(this.settings.text){ 122 | Pasteboard.copyString(this.settings.text); 123 | this.notify(this.name,'当前网易云热评复制成功!'); 124 | }else{ 125 | this.notify('⚠️复制失败','请先运行一次预览组件,再点击此按钮。'); 126 | }; 127 | }, { name: 'chart.bar', color: '#7B68EE' }); 128 | this.registerAction("木小果API", async () => { 129 | await this.inputKey(); 130 | }, { name: 'key.icloud', color: '#B8860B' }); 131 | } 132 | } 133 | } 134 | // @组件代码结束 135 | await Runing(Widget) -------------------------------------------------------------------------------- /Random Scriptable API.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: book; 4 | // This script shows a random Scriptable API in a widget. The script is meant to be used with a widget configured on the Home Screen. 5 | // You can run the script in the app to preview the widget or you can go to the Home Screen, add a new Scriptable widget and configure the widget to run this script. 6 | // You can also try creating a shortcut that runs this script. Running the shortcut will show widget. 7 | let api = await randomAPI() 8 | let widget = await createWidget(api) 9 | if (config.runsInWidget) { 10 | // The script runs inside a widget, so we pass our instance of ListWidget to be shown inside the widget on the Home Screen. 11 | Script.setWidget(widget) 12 | } else { 13 | // The script runs inside the app, so we preview the widget. 14 | widget.presentMedium() 15 | } 16 | // Calling Script.complete() signals to Scriptable that the script have finished running. 17 | // This can speed up the execution, in particular when running the script from Shortcuts or using Siri. 18 | Script.complete() 19 | 20 | async function createWidget(api) { 21 | let appIcon = await loadAppIcon() 22 | let title = "Random Scriptable API" 23 | let widget = new ListWidget() 24 | // Add background gradient 25 | let gradient = new LinearGradient() 26 | gradient.locations = [0, 1] 27 | gradient.colors = [ 28 | new Color("141414"), 29 | new Color("13233F") 30 | ] 31 | widget.backgroundGradient = gradient 32 | // Show app icon and title 33 | let titleStack = widget.addStack() 34 | let appIconElement = titleStack.addImage(appIcon) 35 | appIconElement.imageSize = new Size(15, 15) 36 | appIconElement.cornerRadius = 4 37 | titleStack.addSpacer(4) 38 | let titleElement = titleStack.addText(title) 39 | titleElement.textColor = Color.white() 40 | titleElement.textOpacity = 0.7 41 | titleElement.font = Font.mediumSystemFont(13) 42 | widget.addSpacer(12) 43 | // Show API 44 | let nameElement = widget.addText(api.name) 45 | nameElement.textColor = Color.white() 46 | nameElement.font = Font.boldSystemFont(18) 47 | widget.addSpacer(2) 48 | let descriptionElement = widget.addText(api.description) 49 | descriptionElement.minimumScaleFactor = 0.5 50 | descriptionElement.textColor = Color.white() 51 | descriptionElement.font = Font.systemFont(18) 52 | // UI presented in Siri ans Shortcuta is non-interactive, so we only show the footer when not running the script from Siri. 53 | if (!config.runsWithSiri) { 54 | widget.addSpacer(8) 55 | // Add button to open documentation 56 | let linkSymbol = SFSymbol.named("arrow.up.forward") 57 | let footerStack = widget.addStack() 58 | let linkStack = footerStack.addStack() 59 | linkStack.centerAlignContent() 60 | linkStack.url = api.url 61 | let linkElement = linkStack.addText("Read more") 62 | linkElement.font = Font.mediumSystemFont(13) 63 | linkElement.textColor = Color.blue() 64 | linkStack.addSpacer(3) 65 | let linkSymbolElement = linkStack.addImage(linkSymbol.image) 66 | linkSymbolElement.imageSize = new Size(11, 11) 67 | linkSymbolElement.tintColor = Color.blue() 68 | footerStack.addSpacer() 69 | // Add link to documentation 70 | let docsSymbol = SFSymbol.named("book") 71 | let docsElement = footerStack.addImage(docsSymbol.image) 72 | docsElement.imageSize = new Size(20, 20) 73 | docsElement.tintColor = Color.white() 74 | docsElement.imageOpacity = 0.5 75 | docsElement.url = "https://docs.scriptable.app" 76 | } 77 | return widget 78 | } 79 | 80 | async function randomAPI() { 81 | let docs = await loadDocs() 82 | let apiNames = Object.keys(docs) 83 | let num = Math.round(Math.random() * apiNames.length) 84 | let apiName = apiNames[num] 85 | let api = docs[apiName] 86 | return { 87 | name: apiName, 88 | description: api["!doc"], 89 | url: api["!url"] 90 | } 91 | } 92 | 93 | async function loadDocs() { 94 | let url = "https://docs.scriptable.app/scriptable.json" 95 | let req = new Request(url) 96 | return await req.loadJSON() 97 | } 98 | 99 | async function loadAppIcon() { 100 | let url = "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/21/1e/13/211e13de-2e74-4221-f7db-d6d2c53b4323/AppIcon-1x_U007emarketing-0-7-0-85-220.png/540x540sr.jpg" 101 | let req = new Request(url) 102 | return req.loadImage() 103 | } -------------------------------------------------------------------------------- /B站热榜.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: red; icon-glyph: fighter-jet; 4 | /* 5 | // *************************** 6 | // 环境框架 :@ DmYY 7 | // author :原作者2Ya 由DJG修改 8 | */ 9 | 10 | const { DJG, Runing } = importModule( 11 | FileManager.local().joinPath( 12 | FileManager.local().libraryDirectory(), 13 | "/DJG.js" 14 | ) 15 | ); 16 | 17 | // @组件代码开始 18 | class Widget extends DJG { 19 | constructor(arg) { 20 | super(arg); 21 | this.name = 'B站热榜' 22 | this.widget_ID = "DJG-112" 23 | this.version = "V3.0"; 24 | this.logo = 'https://s1.ax1x.com/2022/07/10/jszrp6.png'; 25 | 26 | this.Run(module.filename, args); 27 | } 28 | 29 | /** 30 | * 渲染函数,函数名固定 31 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 32 | */ 33 | async render () { 34 | let widget = this.getWidget(); 35 | await this.getWidgetBackgroundImage(widget); 36 | if(!this.settings.choiceAction && !this.settings.openMode) { 37 | this.settings.choiceAction = "a"; 38 | this.settings.openMode = "a"; 39 | } 40 | try{ 41 | let data = await this.getData() 42 | switch (this.widgetFamily) { 43 | case 'small': 44 | await this.renderSmall(widget, data); 45 | break; 46 | case 'medium': 47 | await this.renderMedium(widget, data); 48 | break; 49 | default: 50 | await this.renderMedium(widget, data, 13); 51 | break; 52 | } 53 | }catch(e){ 54 | this.ERROR.push({error:e.toString()}); 55 | } 56 | return widget; 57 | } 58 | 59 | // 小组件 60 | async renderSmall (w, data) { 61 | await this.renderHeader(w, this.logo, this.name) 62 | const cell = w.addStack() 63 | this.addText(cell, '1', 13, {font:'light', color:'fe2d46'}) 64 | cell.addSpacer(8) 65 | this.addText(cell, data.data[0]['title'], 13, {font:'light'}) 66 | w.addSpacer() 67 | let _score = data.data[0]['play'] 68 | this.addText(w, ` ${_score}`, 10, {font:'light', opacity:0.8}) 69 | let _url = this.decideGoto(data.data[0]) 70 | cell.url = _url 71 | } 72 | 73 | // 中组件 74 | async renderMedium (w, data, num = 5) { 75 | w.addSpacer(5); 76 | await this.renderHeader(w, this.logo, this.name, 8) 77 | for( let i = 0; i < num; i++){ 78 | let d = data.data[i] 79 | const cell = w.addStack(); 80 | cell.centerAlignContent(); 81 | let col = null; 82 | if (i === 0) { col = '#fe2d46' 83 | } else if (i === 1) { col = '#ff6600' 84 | } else if (i === 2) { col = '#faa90e' 85 | } else { col = '#9195a3'} 86 | this.addText(cell, String(i+1), 13, {font:'light', color:col, font:'bold'}) 87 | cell.addSpacer(10) 88 | let _title = d['title'] 89 | this.addText(cell, _title, 13, {font:'light', lineLimit:1}) 90 | cell.addSpacer() 91 | let _score = String(d['play']) 92 | this.addText(cell, _score, 10, {font:'light', opacity:0.8}) 93 | let _url = this.decideGoto(d) 94 | cell.url = _url 95 | w.addSpacer(6) 96 | } 97 | } 98 | /** 99 | * 获取数据函数,函数名可不固定 100 | */ 101 | async getData () { 102 | let title = ['0', '1', '3', '4', '5', '36', '119', '129'] 103 | const optionStatus = this.settings.optionStatus || new Array(8); 104 | let index = optionStatus.indexOf(true); 105 | index = index==-1 ? 0 : index; 106 | const url = `https://app.bilibili.com/x/v2/rank/region?rid=${title[index]}`; 107 | let data = await this.httpGet(url, 2) 108 | return data 109 | } 110 | // 获取对应的打开方式 111 | decideGoto(item) { 112 | const optionStatus2 = this.settings.optionStatus2 || new Array(2); 113 | let index = optionStatus2.indexOf(true); 114 | index = index==-1 ? 0 : index; 115 | if(index == 0){ 116 | return `https://bilibili.com/${item.goto}${item.param}`; 117 | }else{ 118 | return item.uri; 119 | } 120 | } 121 | // 添加设置信息 122 | Run(filename, args) { 123 | if (config.runsInApp) { 124 | this.registerAction("基础设置", this.setWidgetConfig); 125 | this.registerAction("榜单设置", async () => { 126 | await this.setChoiceAction("榜单设置", "选择不同类型的B站榜单", [ 127 | '全站', '动画', '音乐', '游戏', '娱乐', '科技', '鬼畜', '舞蹈' 128 | ]); 129 | }, { name: 'chart.bar', color: '#7B68EE' }); 130 | this.registerAction("打开设置", async () => { 131 | await this.setChoiceAction("打开设置", "点击某条榜单时的打开方式", [ 132 | "浏览器", "客户端" 133 | ], true, 'optionStatus2'); 134 | }, { name: 'envelope.open', color: '#66CD00' }); 135 | } 136 | } 137 | } 138 | 139 | // @组件代码结束 140 | await Runing(Widget) -------------------------------------------------------------------------------- /微博热榜.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: feather-alt; 4 | /* 5 | // 抖音搜索:大舅哥科技 6 | // 微信搜索公众号「大舅哥科技」 7 | // 获取更多精美实用 iOS 桌面组件! 8 | // 更多精选快捷指令、壁纸,等你! 9 | // *************************** 10 | // 环境框架 :@ DmYY 11 | // author :原作者Honye, 由DJG修改 12 | */ 13 | 14 | const { DJG, Runing } = importModule( 15 | FileManager.local().joinPath( 16 | FileManager.local().libraryDirectory(), 17 | "/DJG.js" 18 | ) 19 | ); 20 | 21 | // @组件代码开始 22 | class Widget extends DJG { 23 | constructor (arg) { 24 | super(arg) 25 | this.name = '微博热榜+Top7' 26 | this.widget_ID = "DJG-106" 27 | this.version = "V3.6"; 28 | this.apiUrl = "https://weibointl.api.weibo.cn/portal.php?ct=feed&a=search_topic"; 29 | this.logo = "https://s1.ax1x.com/2022/07/10/jrj056.png"; 30 | 31 | this.Run(); 32 | } 33 | 34 | async render () { 35 | let widget = this.getWidget(); 36 | await this.getWidgetBackgroundImage(widget); 37 | if(!this.settings.choiceAction) this.settings.choiceAction = "b"; 38 | try{ 39 | switch (this.widgetFamily) { 40 | case 'small': 41 | await this.renderSmall(widget); 42 | break; 43 | case 'medium': 44 | await this.renderMedium(widget); 45 | break; 46 | default: 47 | await this.renderMedium(widget, 16); 48 | break; 49 | } 50 | }catch(e){ 51 | this.ERROR.push({error:e.toString()}); 52 | } 53 | return widget; 54 | } 55 | 56 | /** 57 | * 渲染小尺寸组件 58 | */ 59 | async renderSmall (w) { 60 | let res = await this.httpGet(this.apiUrl); 61 | let data = res['data']; 62 | let topic = data[0]; 63 | // 显示数据 64 | await this.renderHeader(w, this.logo, '微博热搜') 65 | let body = w.addStack(); 66 | this.addText(body, topic['title'], 13, {font:'light', align:'left'}); 67 | 68 | w.addSpacer() 69 | let footer = w.addStack() 70 | footer.centerAlignContent() 71 | footer.addSpacer(5) 72 | if (topic['icon']) { 73 | let hot = footer.addImage(await this.getImageByUrl(topic['icon'])) 74 | hot.imageSize = new Size(13, 13) 75 | footer.addSpacer(5) 76 | } 77 | this.addText(footer, String(topic['number']), 10, {font:'light', opacity:0.5}); 78 | } 79 | 80 | /** 81 | * 渲染中尺寸组件 82 | */ 83 | async renderMedium (widget, count = 7) { 84 | let widgetBottom, stackBottom; 85 | let res = await this.httpGet(this.apiUrl); 86 | let data = res['data']; 87 | // 显示数据 88 | widget.setPadding(6, 12, 6, 14); 89 | for (let i = 0; i < count; ++i) { 90 | const item = data[i]; 91 | if (i === 0) { 92 | const stack = widget.addStack(); 93 | await this.addItem(stack, item); 94 | stack.addSpacer(); 95 | const upTime = this.getDateStr('HH:mm'); 96 | this.addText(stack, `更新于:${upTime}`, 10, {opacity:0.5}) 97 | } else if (i < count - 2) { 98 | await this.addItem(widget, item); 99 | } else { 100 | if (!widgetBottom) { 101 | stackBottom = widget.addStack(); 102 | stackBottom.bottomAlignContent(); 103 | widgetBottom = stackBottom.addStack(); 104 | widgetBottom.layoutVertically(); 105 | await this.addItem(widgetBottom, item); 106 | } else { 107 | await this.addItem(widgetBottom, item); 108 | } 109 | widgetBottom.length = (widgetBottom.length || 0) + 1; 110 | if (widgetBottom.length === 2) { 111 | stackBottom.addSpacer(); 112 | this.addImage(stackBottom, await this.getImageByUrl(this.logo), {w:30, h:30}); 113 | } 114 | } 115 | } 116 | } 117 | 118 | async addItem (widget, item) { 119 | /** 微博 H5 应用页面 */ 120 | const search = (keyword) => `https://m.weibo.cn/search?containerid=${encodeURIComponent('100103type=1&t=10&q=' + keyword)}`; 121 | 122 | const stack = widget.addStack(); 123 | const queryString = item.scheme.split('?')[1]; 124 | //const [, queryString] = item.scheme.split('?'); 125 | const query = {}; 126 | queryString.split('&').forEach((item) => { 127 | const dome = item.split('='); 128 | const key = dome[0]; 129 | const value = dome[1]; 130 | //const [key, value] = item.split('='); 131 | query[key] = value; 132 | }); 133 | stack.url = search(query.keyword); 134 | stack.centerAlignContent(); 135 | stack.size = new Size(-1, 20); 136 | const stackIndex = stack.addStack(); 137 | stackIndex.size = new Size(14 * 1.4, -1); 138 | const color = item.pic_id > 3 ? '#f5c94c' : '#fe4f67'; 139 | this.addText(stackIndex, String(item.pic_id), 13, {align:'right', font:'bold', opacity:0.9, color:color}); 140 | stack.addSpacer(4); 141 | this.addText(stack, item.title, 13, {lineLimit:1, opacity: 0.7}); 142 | if (item.icon) { 143 | stack.addSpacer(4); 144 | this.addImage(stack, await this.getImageByUrl(item.icon), {w:12, h:12}); 145 | } 146 | stack.addSpacer(); 147 | }; 148 | 149 | // 添加设置信息 150 | Run(filename, args) { 151 | if (config.runsInApp) { 152 | this.registerAction("基础设置", this.setWidgetConfig); 153 | } 154 | } 155 | } 156 | // @组件代码结束 157 | await Runing(Widget) 158 | -------------------------------------------------------------------------------- /小左底.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: light-brown; icon-glyph: genderless; 4 | 5 | // Variables used by Scriptable. 6 | // These must be at the very top of the file. Do not edit. 7 | // icon-color: deep-purple; icon-glyph: image; 8 | 9 | // This widget was created by Max Zeryck @mzeryck 10 | 11 | // Widgets are unique based on the name of the script. 12 | const filename = Script.name() + ".jpg"; 13 | const files = FileManager.local(); 14 | const path = files.joinPath(files.documentsDirectory(), filename); 15 | 16 | if (config.runsInWidget) { 17 | let widget = new ListWidget(); 18 | widget.backgroundImage = files.readImage(path); 19 | 20 | // You can your own code here to add additional items to the "invisible" background of the widget. 21 | 22 | Script.setWidget(widget); 23 | Script.complete(); 24 | 25 | /* 26 | * The code below this comment is used to set up the invisible widget. 27 | * =================================================================== 28 | */ 29 | } else { 30 | // Determine if user has taken the screenshot. 31 | var message; 32 | message = 33 | "以下是【透明背景】生成步骤,如果你没有屏幕截图请退出,并返回主屏幕长按进入编辑模式。滑动到最右边的空白页截图。然后重新运行!"; 34 | let exitOptions = ["继续(已有截图)", "退出(没有截图)"]; 35 | let shouldExit = await generateAlert(message, exitOptions); 36 | if (shouldExit) return; 37 | 38 | // Get screenshot and determine phone size. 39 | let img = await Photos.fromLibrary(); 40 | let height = img.size.height; 41 | let phone = phoneSizes()[height]; 42 | if (!phone) { 43 | message = "您似乎选择了非iPhone屏幕截图的图像,或者不支持您的iPhone。请使用其他图像再试一次!"; 44 | await generateAlert(message, ["好的!我现在去截图"]); 45 | return; 46 | } 47 | 48 | // Prompt for widget size and position. 49 | message = "您想要创建什么尺寸的小部件?"; 50 | let sizes = ["小号", "中号", "大号"]; 51 | let size = await generateAlert(message, sizes); 52 | let widgetSize = sizes[size]; 53 | 54 | message = "您想它在什么位置?"; 55 | message += height == 1136 ? " (请注意,您的设备仅支持两行小部件,因此中间和底部选项相同。)" : ""; 56 | 57 | // Determine image crop based on phone size. 58 | let crop = { w: "", h: "", x: "", y: "" }; 59 | if (widgetSize == "小号") { 60 | crop.w = phone.小号; 61 | crop.h = phone.小号; 62 | let positions = ["顶部 左边", "顶部 右边", "中间 左边", "中间 右边", "底部 左边", "底部 右边"]; 63 | let position = await generateAlert(message, positions); 64 | 65 | // Convert the two words into two keys for the phone size dictionary. 66 | let keys = positions[position].toLowerCase().split(" "); 67 | crop.y = phone[keys[0]]; 68 | crop.x = phone[keys[1]]; 69 | } else if (widgetSize == "中号") { 70 | crop.w = phone.中号; 71 | crop.h = phone.小号; 72 | 73 | // Medium and large widgets have a fixed x-value. 74 | crop.x = phone.左边; 75 | let positions = ["顶部", "中间", "底部"]; 76 | let position = await generateAlert(message, positions); 77 | let key = positions[position].toLowerCase(); 78 | crop.y = phone[key]; 79 | } else if (widgetSize == "大号") { 80 | crop.w = phone.中号; 81 | crop.h = phone.大号; 82 | crop.x = phone.左边; 83 | let positions = ["顶部", "底部"]; 84 | let position = await generateAlert(message, positions); 85 | 86 | // Large widgets at the bottom have the "middle" y-value. 87 | crop.y = position ? phone.中间 : phone.顶部; 88 | } 89 | 90 | // Crop image and finalize the widget. 91 | let imgCrop = cropImage(img, new Rect(crop.x, crop.y, crop.w, crop.h)); 92 | 93 | message = "您的小部件背景已准备就绪。您想在Scriptable的小部件中使用它还是导出图像?"; 94 | const exportPhotoOptions = ["在小部件中使用", "导出到相册"]; 95 | const exportPhoto = await generateAlert(message, exportPhotoOptions); 96 | 97 | if (exportPhoto) { 98 | Photos.save(imgCrop); 99 | } else { 100 | files.writeImage(path, imgCrop); 101 | } 102 | 103 | Script.complete(); 104 | } 105 | 106 | // Generate an alert with the provided array of options. 107 | async function generateAlert(message, options) { 108 | let alert = new Alert(); 109 | alert.message = message; 110 | 111 | for (const option of options) { 112 | alert.addAction(option); 113 | } 114 | 115 | let response = await alert.presentAlert(); 116 | return response; 117 | } 118 | 119 | // Crop an image into the specified rect. 120 | function cropImage(img, rect) { 121 | let draw = new DrawContext(); 122 | draw.size = new Size(rect.width, rect.height); 123 | 124 | draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y)); 125 | return draw.getImage(); 126 | } 127 | 128 | // Pixel sizes and positions for widgets on all supported phones. 129 | function phoneSizes() { 130 | let phones = { 131 | 2688: { 132 | 小号: 507, 133 | 中号: 1080, 134 | 大号: 1137, 135 | 左边: 81, 136 | 右边: 654, 137 | 顶部: 228, 138 | 中间: 858, 139 | 底部: 1488, 140 | }, 141 | 142 | 1792: { 143 | 小号: 338, 144 | 中号: 720, 145 | 大号: 758, 146 | 左边: 54, 147 | 右边: 436, 148 | 顶部: 160, 149 | 中间: 580, 150 | 底部: 1000, 151 | }, 152 | 153 | 2436: { 154 | 小号: 465, 155 | 中号: 987, 156 | 大号: 1035, 157 | 左边: 69, 158 | 右边: 591, 159 | 顶部: 213, 160 | 中间: 783, 161 | 底部: 1353, 162 | }, 163 | 164 | 2208: { 165 | 小号: 471, 166 | 中号: 1044, 167 | 大号: 1071, 168 | 左边: 99, 169 | 右边: 672, 170 | 顶部: 114, 171 | 中间: 696, 172 | 底部: 1278, 173 | }, 174 | 175 | 1334: { 176 | 小号: 296, 177 | 中号: 642, 178 | 大号: 648, 179 | 左边: 54, 180 | 右边: 400, 181 | 顶部: 60, 182 | 中间: 412, 183 | 底部: 764, 184 | }, 185 | 186 | 1136: { 187 | 小号: 282, 188 | 中号: 584, 189 | 大号: 622, 190 | 左边: 30, 191 | 右边: 332, 192 | 顶部: 59, 193 | 中间: 399, 194 | 底部: 399, 195 | }, 196 | 197 | 2532: { 198 | 小号: 472, 199 | 中号: 1012, 200 | 大号: 1058, 201 | 左边: 78, 202 | 右边: 618, 203 | 顶部: 230, 204 | 中间: 818, 205 | 底部: 1408, 206 | }, 207 | 2340: { 208 | 小号: 436, 209 | 中号: 936, 210 | 大号: 980, 211 | 左边: 72, 212 | 右边: 570, 213 | 顶部: 212, 214 | 中间: 756, 215 | 底部: 1300, 216 | }, 217 | 218 | 2778: { 219 | 小号: 614, 220 | 中号: 1114, 221 | 大号: 1162, 222 | 左边: 44, 223 | 右边: 678, 224 | 顶部: 245, 225 | 中间: 844, 226 | 底部: 1518, 227 | }, 228 | 229 | 230 | }; 231 | return phones; 232 | } 233 | -------------------------------------------------------------------------------- /透明背景适配最新iphone.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: light-brown; icon-glyph: genderless; 4 | 5 | // Variables used by Scriptable. 6 | // These must be at the very top of the file. Do not edit. 7 | // icon-color: deep-purple; icon-glyph: image; 8 | 9 | // This widget was created by Max Zeryck @mzeryck 10 | 11 | // Widgets are unique based on the name of the script. 12 | const filename = Script.name() + ".jpg"; 13 | const files = FileManager.local(); 14 | const path = files.joinPath(files.documentsDirectory(), filename); 15 | 16 | if (config.runsInWidget) { 17 | let widget = new ListWidget(); 18 | widget.backgroundImage = files.readImage(path); 19 | 20 | // You can your own code here to add additional items to the "invisible" background of the widget. 21 | 22 | Script.setWidget(widget); 23 | Script.complete(); 24 | 25 | /* 26 | * The code below this comment is used to set up the invisible widget. 27 | * =================================================================== 28 | */ 29 | } else { 30 | // Determine if user has taken the screenshot. 31 | var message; 32 | message = 33 | "以下是【透明背景】生成步骤,如果你没有屏幕截图请退出,并返回主屏幕长按进入编辑模式。滑动到最右边的空白页截图。然后重新运行!"; 34 | let exitOptions = ["继续(已有截图)", "退出(没有截图)"]; 35 | let shouldExit = await generateAlert(message, exitOptions); 36 | if (shouldExit) return; 37 | 38 | // Get screenshot and determine phone size. 39 | let img = await Photos.fromLibrary(); 40 | let height = img.size.height; 41 | let phone = phoneSizes()[height]; 42 | if (!phone) { 43 | message = "您似乎选择了非iPhone屏幕截图的图像,或者不支持您的iPhone。请使用其他图像再试一次!"; 44 | await generateAlert(message, ["好的!我现在去截图"]); 45 | return; 46 | } 47 | 48 | // Prompt for widget size and position. 49 | message = "您想要创建什么尺寸的小部件?"; 50 | let sizes = ["小号", "中号", "大号"]; 51 | let size = await generateAlert(message, sizes); 52 | let widgetSize = sizes[size]; 53 | 54 | message = "您想它在什么位置?"; 55 | message += height == 1136 ? " (请注意,您的设备仅支持两行小部件,因此中间和底部选项相同。)" : ""; 56 | 57 | // Determine image crop based on phone size. 58 | let crop = { w: "", h: "", x: "", y: "" }; 59 | if (widgetSize == "小号") { 60 | crop.w = phone.小号; 61 | crop.h = phone.小号; 62 | let positions = ["顶部 左边", "顶部 右边", "中间 左边", "中间 右边", "底部 左边", "底部 右边"]; 63 | let position = await generateAlert(message, positions); 64 | 65 | // Convert the two words into two keys for the phone size dictionary. 66 | let keys = positions[position].toLowerCase().split(" "); 67 | crop.y = phone[keys[0]]; 68 | crop.x = phone[keys[1]]; 69 | } else if (widgetSize == "中号") { 70 | crop.w = phone.中号; 71 | crop.h = phone.小号; 72 | 73 | // Medium and large widgets have a fixed x-value. 74 | crop.x = phone.左边; 75 | let positions = ["顶部", "中间", "底部"]; 76 | let position = await generateAlert(message, positions); 77 | let key = positions[position].toLowerCase(); 78 | crop.y = phone[key]; 79 | } else if (widgetSize == "大号") { 80 | crop.w = phone.中号; 81 | crop.h = phone.大号; 82 | crop.x = phone.左边; 83 | let positions = ["顶部", "底部"]; 84 | let position = await generateAlert(message, positions); 85 | 86 | // Large widgets at the bottom have the "middle" y-value. 87 | crop.y = position ? phone.中间 : phone.顶部; 88 | } 89 | 90 | // Crop image and finalize the widget. 91 | let imgCrop = cropImage(img, new Rect(crop.x, crop.y, crop.w, crop.h)); 92 | 93 | message = "您的小部件背景已准备就绪。您想在Scriptable的小部件中使用它还是导出图像?"; 94 | const exportPhotoOptions = ["在小部件中使用", "导出到相册"]; 95 | const exportPhoto = await generateAlert(message, exportPhotoOptions); 96 | 97 | if (exportPhoto) { 98 | Photos.save(imgCrop); 99 | } else { 100 | files.writeImage(path, imgCrop); 101 | } 102 | 103 | Script.complete(); 104 | } 105 | 106 | // Generate an alert with the provided array of options. 107 | async function generateAlert(message, options) { 108 | let alert = new Alert(); 109 | alert.message = message; 110 | 111 | for (const option of options) { 112 | alert.addAction(option); 113 | } 114 | 115 | let response = await alert.presentAlert(); 116 | return response; 117 | } 118 | 119 | // Crop an image into the specified rect. 120 | function cropImage(img, rect) { 121 | let draw = new DrawContext(); 122 | draw.size = new Size(rect.width, rect.height); 123 | 124 | draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y)); 125 | return draw.getImage(); 126 | } 127 | 128 | // Pixel sizes and positions for widgets on all supported phones. 129 | function phoneSizes() { 130 | let phones = { 131 | 2688: { 132 | 小号: 507, 133 | 中号: 1080, 134 | 大号: 1137, 135 | 左边: 81, 136 | 右边: 654, 137 | 顶部: 228, 138 | 中间: 858, 139 | 底部: 1488, 140 | }, 141 | 142 | 1792: { 143 | 小号: 338, 144 | 中号: 720, 145 | 大号: 758, 146 | 左边: 54, 147 | 右边: 436, 148 | 顶部: 160, 149 | 中间: 580, 150 | 底部: 1000, 151 | }, 152 | 153 | 2436: { 154 | 小号: 465, 155 | 中号: 987, 156 | 大号: 1035, 157 | 左边: 69, 158 | 右边: 591, 159 | 顶部: 213, 160 | 中间: 783, 161 | 底部: 1353, 162 | }, 163 | 164 | 2208: { 165 | 小号: 471, 166 | 中号: 1044, 167 | 大号: 1071, 168 | 左边: 99, 169 | 右边: 672, 170 | 顶部: 114, 171 | 中间: 696, 172 | 底部: 1278, 173 | }, 174 | 175 | 1334: { 176 | 小号: 296, 177 | 中号: 642, 178 | 大号: 648, 179 | 左边: 54, 180 | 右边: 400, 181 | 顶部: 60, 182 | 中间: 412, 183 | 底部: 764, 184 | }, 185 | 186 | 1136: { 187 | 小号: 282, 188 | 中号: 584, 189 | 大号: 622, 190 | 左边: 30, 191 | 右边: 332, 192 | 顶部: 59, 193 | 中间: 399, 194 | 底部: 399, 195 | }, 196 | 197 | 2532: { 198 | 小号: 472, 199 | 中号: 1012, 200 | 大号: 1058, 201 | 左边: 78, 202 | 右边: 618, 203 | 顶部: 230, 204 | 中间: 818, 205 | 底部: 1408, 206 | }, 207 | 2340: { 208 | 小号: 436, 209 | 中号: 936, 210 | 大号: 980, 211 | 左边: 72, 212 | 右边: 570, 213 | 顶部: 212, 214 | 中间: 756, 215 | 底部: 1300, 216 | }, 217 | 218 | 2778: { 219 | 小号: 512, 220 | 中号: 1106, 221 | 大号: 1162, 222 | 左边: 89.5, 223 | 右边: 678, 224 | 顶部: 244, 225 | 中间: 844, 226 | 底部: 1518, 227 | }, 228 | 229 | 230 | }; 231 | return phones; 232 | } 233 | -------------------------------------------------------------------------------- /中国联通查询.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: feather-alt; 4 | /* 5 | // *************************** 6 | // 环境框架 :@ DmYY 7 | // author :原作者「脑瓜」, 由DJG修改 8 | */ 9 | 10 | const { DJG, Runing } = importModule( 11 | FileManager.local().joinPath( 12 | FileManager.local().libraryDirectory(), 13 | "/DJG.js" 14 | ) 15 | ); 16 | 17 | // @组件代码开始 18 | class Widget extends DJG { 19 | constructor(arg) { 20 | super(arg); 21 | this.name = '中国联通' 22 | this.widget_ID = "DJG-109" 23 | this.version = "V1.4" 24 | this.logo = 'https://s1.ax1x.com/2022/07/10/jsxyZj.png'; 25 | this.verticalLogo = 'https://s1.ax1x.com/2022/07/10/jsxwz8.png'; 26 | 27 | this.Run(module.filename, args); 28 | } 29 | 30 | /** 31 | * 渲染函数,函数名固定 32 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 33 | */ 34 | async render () { 35 | if(!this.settings.cookie){ 36 | const error = "需要输入联通cookie" 37 | return this.ERROR = [{error:error}]; 38 | } 39 | let widget = this.getWidget(); 40 | await this.getWidgetBackgroundImage(widget); 41 | try{ 42 | await this.getData(); 43 | await this.getFlowInfo(); 44 | this.saveSettings(false); 45 | switch (this.widgetFamily) { 46 | case 'small': 47 | await this.renderSmall(widget); 48 | break; 49 | case 'medium': 50 | await this.renderMedium(widget); 51 | break; 52 | default: 53 | return await this.renderAlert(); 54 | } 55 | }catch(e){ 56 | this.ERROR.push({error:e.toString()}); 57 | } 58 | return widget; 59 | } 60 | 61 | fee = { 62 | LinearColor: ['d7000f', 'EED5D7'] 63 | } 64 | 65 | flow = { 66 | icon: 'antenna.radiowaves.left.and.right', 67 | iconColor: '1ab6f8', 68 | FGColor: '12A6E4', 69 | BGColor: 'F86527', 70 | LinearColor: ['12A6E4', 'B9E4F6'] 71 | }; 72 | 73 | voice = { 74 | icon: 'phone.fill', 75 | iconColor: '30d15b', 76 | FGColor: 'F86527', 77 | BGColor: 'F86527', 78 | LinearColor: ['F86527', 'F0DED6'] 79 | }; 80 | 81 | point = { 82 | unit: '', 83 | icon: 'tag.fill', 84 | iconColor: 'fc6d6d', 85 | } 86 | 87 | addText(stack, text, size, param = {}, useShadow = true){ 88 | const format = {opacity:'textOpacity', url:'url', lineLimit:'lineLimit', zoom:'minimumScaleFactor'}; 89 | let title = stack.addText(`${text}`); 90 | title.font = param.font ? this.provideFont(param.font, size) : Font.systemFont(size); 91 | title.textColor = param.color ? new Color(param.color) : this.widgetColor; 92 | Object.keys(param).forEach((key) => { 93 | if(format[key]!=null) title[format[key]] = param[key]; 94 | }) 95 | param.align && title[param.align+'AlignText'](); 96 | const lightShadow = this.settings.lightShadowColor; 97 | const darkShadow = this.settings.darkShadowColor; 98 | if(useShadow && (lightShadow || darkShadow)) { 99 | title.shadowColor = Color.dynamic( 100 | new Color(lightShadow || 'fff', 0.8), 101 | new Color(darkShadow || '000', 0.8), 102 | ); 103 | title.shadowOffset = new Point(1, 1); 104 | title.shadowRadius = 1; 105 | } 106 | return title; 107 | } 108 | 109 | // 小组件 110 | async renderSmall(widget){ 111 | widget.setPadding(12, 12, 12, 12); 112 | const headerStack = widget.addStack(); 113 | headerStack.addSpacer(); 114 | let image = await this.getImageByUrl(this.logo); 115 | this.addImage(headerStack, image, {w:415 * 0.24, h:125 * 0.24}); 116 | headerStack.addSpacer(); 117 | widget.addSpacer(); 118 | 119 | const feeStack = widget.addStack(); 120 | feeStack.addSpacer(); 121 | this.addText(feeStack, `${this.settings.fee.number}`, 21); 122 | feeStack.addSpacer(); 123 | widget.addSpacer(); 124 | const bodyStack = widget.addStack(); 125 | bodyStack.layoutVertically(); 126 | let flow = this.getFlow(); 127 | if (this.settings.button[1]){ 128 | this.textLayout(bodyStack, {...flow, ...this.flow}); 129 | bodyStack.addSpacer(7); 130 | this.textLayout(bodyStack, {...this.settings.voice, ...this.voice}); 131 | bodyStack.addSpacer(7); 132 | this.textLayout(bodyStack, {...this.settings.point, ...this.point}); 133 | } else { 134 | const canvas = this.makeCanvas(178, 178); 135 | const ringStack = bodyStack.addStack(); 136 | this.imageCell(canvas, ringStack, {...flow, ...this.flow}); 137 | ringStack.addSpacer(); 138 | this.imageCell(canvas, ringStack, {...this.settings.voice, ...this.voice}); 139 | } 140 | } 141 | 142 | textLayout(stack, data) { 143 | const rowStack = stack.addStack(); 144 | rowStack.centerAlignContent(); 145 | const icon = SFSymbol.named(data.icon); 146 | icon.applyHeavyWeight(); 147 | this.addImage(rowStack, icon.image, {w:13, h:13}, {color:data.iconColor}); 148 | rowStack.addSpacer(4); 149 | this.addText(rowStack, data.title, 13); 150 | rowStack.addSpacer(); 151 | this.addText(rowStack, data.number + data.unit, 13); 152 | } 153 | 154 | async imageCell(canvas, stack, data, fee, percent) { 155 | const canvaStack = stack.addStack(); 156 | canvaStack.layoutVertically(); 157 | if (!fee) { 158 | const opts = {size:178, radius:80, width:18, percent:data.percent}; 159 | this.drawArc(canvas, opts, data.FGColor, data.BGColor); 160 | canvaStack.size = new Size(61, 61); 161 | canvaStack.backgroundImage = canvas.getImage(); 162 | this.ringContent(canvaStack, data, percent); 163 | } else { 164 | canvaStack.addSpacer(10); 165 | const smallLogo = await this.getImageByUrl(this.verticalLogo); 166 | const logoStack = canvaStack.addStack(); 167 | logoStack.size = new Size(40, 40); 168 | logoStack.backgroundImage = smallLogo; 169 | } 170 | } 171 | 172 | ringContent(stack, data, percent = false) { 173 | const rowIcon = stack.addStack(); 174 | rowIcon.addSpacer(); 175 | const icon = SFSymbol.named(data.icon); 176 | icon.applyHeavyWeight(); 177 | const iconElement = rowIcon.addImage(icon.image); 178 | iconElement.tintColor = new Color(data.FGColor); 179 | iconElement.imageSize = new Size(12, 12); 180 | iconElement.imageOpacity = 0.7; 181 | rowIcon.addSpacer(); 182 | 183 | stack.addSpacer(1); 184 | 185 | const rowNumber = stack.addStack(); 186 | rowNumber.addSpacer(); 187 | let title = percent ? `${data.percent}` : `${data.number}`; 188 | this.addText(rowNumber, title, 12, {font:'medium'}); 189 | rowNumber.addSpacer(); 190 | 191 | const rowUnit = stack.addStack(); 192 | rowUnit.addSpacer(); 193 | title = percent ? '%' : data.unit; 194 | this.addText(rowUnit, title, 8, {font:'bold', opacity:0.5}); 195 | rowUnit.addSpacer(); 196 | } 197 | 198 | // 中组件 199 | async renderMedium(widget){ 200 | widget.setPadding(10, 10, 10, 10); 201 | const canvas = this.makeCanvas(178, 178); 202 | const bodyStack = widget.addStack(); 203 | await this.mediumCell(canvas, bodyStack, {...this.settings.fee, ...this.fee}, true); 204 | bodyStack.addSpacer(10); 205 | let flow = this.getFlow(); 206 | await this.mediumCell(canvas, bodyStack, {...flow, ...this.flow}, false, true); 207 | bodyStack.addSpacer(10); 208 | await this.mediumCell(canvas, bodyStack, {...this.settings.voice, ...this.voice}, false, true); 209 | } 210 | 211 | async mediumCell(canvas, stack, data, fee = false, percent) { 212 | let color = data.LinearColor[0]; 213 | const bg = new LinearGradient(); 214 | bg.locations = [0, 1]; 215 | bg.colors = [ 216 | Color.dynamic( 217 | new Color(data.LinearColor[0], 0.03), 218 | new Color(data.LinearColor[1], 0.03) 219 | ), 220 | Color.dynamic( 221 | new Color(data.LinearColor[0], 0.1), 222 | new Color(data.LinearColor[1], 0.1) 223 | ), 224 | ]; 225 | const dataStack = stack.addStack(); 226 | dataStack.backgroundGradient = bg; 227 | dataStack.cornerRadius = 20; 228 | dataStack.layoutVertically(); 229 | dataStack.addSpacer(); 230 | 231 | const topStack = dataStack.addStack(); 232 | topStack.addSpacer(); 233 | await this.imageCell(canvas, topStack, data, fee, percent); 234 | topStack.addSpacer(); 235 | 236 | if (fee) { 237 | dataStack.addSpacer(5); 238 | const updateStack = dataStack.addStack(); 239 | updateStack.addSpacer(); 240 | updateStack.centerAlignContent(); 241 | const updataIcon = SFSymbol.named('arrow.2.circlepath'); 242 | updataIcon.applyHeavyWeight(); 243 | const updateImg = updateStack.addImage(updataIcon.image); 244 | updateImg.tintColor = new Color(color, 0.6); 245 | updateImg.imageSize = new Size(10, 10); 246 | updateStack.addSpacer(3); 247 | const updateTime = this.getDateStr('HH:mm'); 248 | this.addText(updateStack, `${updateTime}`, 10, {font:'medium', color:color, opacity:0.6}); 249 | updateStack.addSpacer(); 250 | } 251 | 252 | dataStack.addSpacer(); 253 | 254 | const numberStack = dataStack.addStack(); 255 | numberStack.addSpacer(); 256 | this.addText(numberStack, `${data.number} ${data.unit}`, 15, {font:'semibold', color:color}); 257 | numberStack.addSpacer(); 258 | 259 | dataStack.addSpacer(3); 260 | 261 | const titleStack = dataStack.addStack(); 262 | titleStack.addSpacer(); 263 | this.addText(titleStack, data.title, 11, {font:'medium', color:color, opacity:0.7}); 264 | titleStack.addSpacer(); 265 | 266 | dataStack.addSpacer(15); 267 | } 268 | 269 | getFlow(){ 270 | let flow = null; 271 | let optionStatus = this.settings.optionStatus || [true]; 272 | if(optionStatus[0]){ 273 | flow = this.settings.flows[0]; 274 | }else if(optionStatus[1]){ 275 | flow = this.settings.flows[1]; 276 | }else if(optionStatus[2]){ 277 | flow = this.settings.flows[2]; 278 | } 279 | return flow; 280 | } 281 | 282 | async login() { 283 | const url = 'https://m.client.10010.com/dailylottery/static/textdl/userLogin?version=iphone_c@8.0200&desmobile='; 284 | const opts = { 285 | headers: {'cookie': this.settings.cookie} 286 | } 287 | const signInfo = await this.httpGet(url, false, true, opts); 288 | try { 289 | if (signInfo.indexOf('天天抽奖') >= 0 && signInfo.indexOf('请稍后重试') < 0) { 290 | console.log('用户登录成功'); 291 | } else { 292 | this.flow.FGColor = 'C4C4C4'; 293 | this.voice.FGColor = 'C4C4C4'; 294 | this.notify(this.name, '用户登录失败,cookie失效'); 295 | console.log('用户登录失败,cookie失效'); 296 | } 297 | } catch (e) { 298 | console.log('用户登录失败:' + e); 299 | } 300 | } 301 | 302 | async getData() { 303 | await this.login(); 304 | const url= 'https://m.client.10010.com/mobileserviceimportant/home/queryUserInfoSeven?version=iphone_c@8.0200&desmobiel=&showType=0'; 305 | 306 | const opts = { 307 | headers: {'cookie': this.settings.cookie} 308 | } 309 | const userInfo = await this.httpGet(url, true, true, opts); 310 | try { 311 | if (userInfo.code === 'Y') { 312 | console.log('获取信息成功'); 313 | userInfo.data.dataList.forEach((item) => { 314 | if (item.type === 'fee') { 315 | let fee = {en: '¥'}; 316 | if (item.unit ==='万元') { 317 | fee.number = item.number * 10000; 318 | fee.unit = '元'; 319 | } else { 320 | fee.number = item.number; 321 | fee.unit = item.unit; 322 | } 323 | fee.title = item.remainTitle; 324 | this.settings.fee = fee; 325 | } 326 | if (item.type === 'flow') { 327 | let flow = {}; 328 | flow.itemName = '套餐内总流量'; 329 | flow.unit = item.unit; 330 | flow.en = item.unit; 331 | if(!this.settings.button[0]){ 332 | flow.title = '剩余流量'; 333 | flow.number = item.number; 334 | flow.percent = (100 - item.persent).toFixed(2); 335 | } else { 336 | flow.title = '已用流量'; 337 | flow.number = (item.number/(1 - item.persent/100) - item.number).toFixed(2); 338 | flow.percent = item.persent || '0.00'; 339 | } 340 | this.settings.flow = flow; 341 | } 342 | if (item.type === 'voice') { 343 | let voice = {en: 'MIN'}; 344 | voice.number = item.number; 345 | voice.unit = item.unit; 346 | voice.percent = (100 - item.persent).toFixed(2); 347 | voice.title = item.remainTitle; 348 | this.settings.voice = voice; 349 | } 350 | if (item.type === 'point') { 351 | let point = {}; 352 | point.number = item.number; 353 | point.title = item.remainTitle; 354 | this.settings.point = point; 355 | } 356 | }); 357 | } else { 358 | this.ERROR.push({error:'cookie失效|服务器维护'}); 359 | } 360 | } catch (e) { 361 | this.ERROR.push({error:e.toString()}); 362 | console.log('获取信息失败:' + e); 363 | } 364 | } 365 | 366 | async getFlowInfo() { 367 | const url= 'https://m.client.10010.com/servicequerybusiness/operationservice/queryOcsPackageFlowLeftContentRevisedInJune'; 368 | const opts = { 369 | headers: {'cookie': this.settings.cookie} 370 | } 371 | let flowInfo = await this.httpGet(url, true, true, opts); 372 | try { 373 | let _flowInfo = flowInfo.resources[0].details; 374 | if(!!!_flowInfo || _flowInfo.length == 0) _flowInfo = flowInfo.unshared[0].details; 375 | let flows = [this.settings.flow]; 376 | _flowInfo.forEach((item) => { 377 | let flow = {}; 378 | let total = parseInt(item.total); 379 | if(total == 0) { 380 | let use = parseInt(item.use); 381 | flow = this.formatFlow(use); 382 | flow.title = '已用流量'; 383 | flow.percent = '1.00' 384 | } else { 385 | if(!this.settings.button[0]){ 386 | let remain = parseInt(item.remain); 387 | flow = this.formatFlow(remain); 388 | flow.title = '剩余流量'; 389 | flow.percent = (remain/total*100).toFixed(2); 390 | } else { 391 | flow = this.unlimitUser(item); 392 | } 393 | } 394 | flow.itemName = item.feePolicyName; 395 | flows.push(flow); 396 | }); 397 | this.settings.flows = flows; 398 | } catch (e) { 399 | console.log('获取信息失败:' + e); 400 | } 401 | return this.flows; 402 | } 403 | 404 | formatFlow(number) { 405 | const n = number; 406 | if (n < 1024) { 407 | return {number: n.toFixed(2), unit: 'MB'}; 408 | } else if (n < 1024 * 1024) { 409 | return {number: (n / 1024).toFixed(2), unit: 'GB'}; 410 | } else { 411 | return {number: (n / (1024*1024)).toFixed(2), unit: 'TB'}; 412 | } 413 | } 414 | 415 | unlimitUser(item) { 416 | let flow = {}; 417 | const usedFlow = this.formatFlow(parseInt(item.use)); 418 | flow.title = '已用流量'; 419 | flow.number = usedFlow.number; 420 | flow.unit = usedFlow.unit; 421 | flow.en = usedFlow.unit; 422 | flow.percent = ((item.use / item.total * 100)).toFixed(2); 423 | return flow; 424 | } 425 | 426 | async setFeeDetails () { 427 | const flows = this.settings.flows || []; 428 | let opts = flows.map((item) => { 429 | return item.itemName; 430 | }) 431 | await this.setChoiceAction("流量显示", "选择需要加载的流量套餐。\n如无内容,请先预览!", opts); 432 | } 433 | 434 | async renderTables() { 435 | const actions = [ 436 | { 437 | title: "流量套餐", 438 | onClick: async () => { 439 | await this.setFeeDetails(); 440 | } 441 | }, 442 | { 443 | title: '已用流量', 444 | but: 0, 445 | }, 446 | { 447 | explain: '显示已用流量,不限量或伪不限量用户可开启' 448 | }, 449 | { 450 | title: '组件样式', 451 | but: 1, 452 | }, 453 | { 454 | explain: '开启后,小号组件以列表方式加载' 455 | } 456 | ]; 457 | const table = new UITable(); 458 | table.showSeparators = true; 459 | await this.dynamicMenu(table, actions, "样式设置"); 460 | await table.present(); 461 | } 462 | 463 | // 添加设置信息 464 | Run(filename, args) { 465 | if (config.runsInApp) { 466 | this.registerAction("基础设置", this.setWidgetConfig); 467 | this.registerAction("账号设置", async () => { 468 | await this.setCustomAction("账号信息", "- cookie需抓包获取-", { 469 | cookie: "cookie", 470 | }); 471 | }, { name: 'person.crop.circle', color: '#66CD00' }); 472 | this.registerAction("设置样式", async () => { 473 | await this.renderTables() 474 | }, { name: 'antenna.radiowaves.left.and.right', color: '#CD6600' }); 475 | } 476 | } 477 | } 478 | // @组件代码结束 479 | await Runing(Widget) -------------------------------------------------------------------------------- /Env 2.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: magic; 4 | /** 5 | * Author: GideonSenku 6 | * Github: https://github.com/GideonSenku 7 | */ 8 | 9 | var locationData, sunData 10 | const currentDate = new Date() 11 | const request = new Request('') 12 | const files = FileManager.iCloud() 13 | const dict = files.documentsDirectory() 14 | files.isDirectory(`${dict}/Env`) ? `` : files.createDirectory(`${dict}/Env`) 15 | const defaultHeaders = { 16 | "Accept": "*/*", 17 | "Content-Type": "application/json" 18 | } 19 | const textFormat = { 20 | defaultText: { size: 14, color: "ffffff", font: "regular" }, 21 | battery: { size: 10, color: "", font: "bold" }, 22 | title: { size: 16, color: "", font: "semibold" }, 23 | SFMono: { size: 12, color: "ffffff", font: "SF Mono" } 24 | } 25 | /** 26 | * @description GET,返回String数据 27 | * @param {*} param0 request信息 28 | * @param {*} callback 回调返回response和JSON对象 29 | */ 30 | const get = async ({ url, headers = {} }, callback = () => {} ) => { 31 | request.url = url 32 | request.method = 'GET' 33 | request.headers = { 34 | ...headers, 35 | ...defaultHeaders 36 | } 37 | const data = await request.loadJSON() 38 | callback(request.response, data) 39 | return data 40 | } 41 | 42 | /** 43 | * @description GET,返回String数据 44 | * @param {*} param0 request信息 45 | * @param {*} callback 回调返回response和String对象 46 | */ 47 | const getStr = async ({ url, headers = {} }, callback = () => {} ) => { 48 | request.url = url 49 | request.method = 'GET' 50 | request.headers = { 51 | ...headers, 52 | ...defaultHeaders 53 | } 54 | const data = await request.loadString() 55 | callback(request.response, data) 56 | return data 57 | } 58 | 59 | /** 60 | * @description POST,返回String数据 61 | * @param {*} param0 request信息 62 | * @param {*} callback 回调返回response和String 63 | */ 64 | const post = async ({ url, body, headers = {} }, callback = () => {} ) => { 65 | request.url = url 66 | request.body = body 67 | request.method = 'POST' 68 | request.headers = { 69 | ...defaultHeaders, 70 | ...headers 71 | } 72 | const data = await request.loadString() 73 | callback(request.response, data) 74 | return data 75 | } 76 | 77 | /** 78 | * @description POST,返回JSON数据 79 | * @param {*} param0 request信息 80 | * @param {*} callback 回调返回response和JSON 81 | */ 82 | const _post = async ({ url, body, headers = {} }, callback = () => {} ) => { 83 | request.url = url 84 | request.body = body 85 | request.method = 'POST' 86 | request.headers = { 87 | ...defaultHeaders, 88 | ...headers 89 | } 90 | const data = await request.loadJSON() 91 | callback(request.response, data) 92 | return data 93 | } 94 | 95 | /** 96 | * @description 下载文件 97 | * @param {*} param0 98 | */ 99 | const getFile = async ({moduleName, url}) => { 100 | log(`开始下载文件: 🌝 ${moduleName}`) 101 | const header = `// Variables used by Scriptable. 102 | // These must be at the very top of the file. Do not edit. 103 | // icon-color: deep-gray; icon-glyph: file-code;\n`; 104 | const content = await getStr({url}) 105 | const fileHeader = content.includes('icon-color') ? `` : header 106 | write(`${moduleName}`, `${fileHeader}${content}`) 107 | log(`文件下载完成: 🌚 ${moduleName}`) 108 | } 109 | 110 | /** 111 | * 112 | * @description 导入模块,不存在即下载模块,也可传入forceDownload: true 强制更新模块 113 | * @param {*} param0 114 | */ 115 | const require = ({ 116 | moduleName, 117 | url = '', 118 | forceDownload = false 119 | }) => { 120 | if (isFileExists(moduleName) && !forceDownload) { 121 | log(`导入模块: 🪐${moduleName}`) 122 | return importModule(moduleName) 123 | } else { 124 | getFile({ moduleName, url }) 125 | log(`导入模块: 🪐${moduleName}`) 126 | return importModule(moduleName) 127 | } 128 | } 129 | /** 130 | * 131 | * @description 将数据写入文件 132 | * @param {*} fileName 要写入的文件名,默认JS文件,可选其他,加上文件名后缀即可 133 | * @param {*} content 要写入的文件内容 134 | */ 135 | const write = (fileName, content) => { 136 | let file = initFile(fileName) 137 | const filePath = `${dict}/${file}` 138 | FileManager.iCloud().writeString(filePath, content) 139 | return true 140 | } 141 | 142 | /** 143 | * 144 | * @description 判断文件是否存在 145 | * @param {*} fileName 146 | */ 147 | const isFileExists = (fileName) => { 148 | let file = initFile(fileName) 149 | return FileManager.iCloud().fileExists(`${dict}/${file}`) 150 | } 151 | 152 | const initFile = (fileName) => { 153 | const hasSuffix = fileName.lastIndexOf('.') + 1 154 | return !hasSuffix ? `${fileName}.js` : fileName 155 | } 156 | 157 | /** 158 | * 159 | * @description 读取文件内容 160 | * @param {*} fileName 要读取的文件名,默认JS文件,可选其他,加上文件名后缀即可 161 | * @return 返回文件内容,字符串形式 162 | */ 163 | const read = (fileName) => { 164 | const file = initFile(fileName) 165 | return FileManager.iCloud().readString(`${dict}/${file}`) 166 | } 167 | 168 | /** 169 | * 170 | * @description 提示框 171 | * @param {*} title 提示框标题 172 | * @param {*} message 提示框内容 173 | * @param {*} btnMes 提示框按钮标题,默认Cancel 174 | */ 175 | const msg = (title, message, btnMes = 'Cancel') => { 176 | if (!config.runsInWidget) { 177 | const alert = new Alert() 178 | alert.title = title 179 | alert.message = message 180 | alert.addAction(btnMes) 181 | alert.present() 182 | } 183 | } 184 | 185 | const setdata = (Val, Key) => { 186 | Keychain.set(Val, Key) 187 | return true 188 | } 189 | 190 | const getdata = (Key) => { 191 | return Keychain.get(Key) 192 | } 193 | 194 | const hasdata = (Key) => { 195 | return Keychain.contains(Key) 196 | } 197 | 198 | const rmdata = (Key) => { 199 | Keychain.remove(Key) 200 | return true 201 | } 202 | 203 | // Presents an alert where the user can enter a value in a text field. 204 | // Returns the entered value. 205 | const input = async(title, message, placeholder, value = null) => { 206 | if (!config.runsInWidget) { 207 | let alert = new Alert() 208 | alert.title = title 209 | alert.message = message 210 | alert.addTextField(placeholder, value) 211 | alert.addAction("OK") 212 | alert.addCancelAction("Cancel") 213 | let idx = await alert.present() 214 | if (idx != -1) { 215 | return alert.textFieldValue(0) 216 | } else { 217 | throw new Error("Cancelled entering value") 218 | } 219 | } 220 | } 221 | 222 | /** 223 | * 224 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 225 | * :$.time('yyyyMMddHHmmssS') 226 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 227 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 228 | * @param {*} fmt 格式化参数 229 | * @param {*} ts 时间戳 13位 230 | */ 231 | const time = (fmt, ts = null) => { 232 | const date = ts ? new Date(ts) : new Date() 233 | let o = { 234 | 'M+': date.getMonth() + 1, 235 | 'd+': date.getDate(), 236 | 'H+': date.getHours(), 237 | 'm+': date.getMinutes(), 238 | 's+': date.getSeconds(), 239 | 'q+': Math.floor((date.getMonth() + 3) / 3), 240 | 'S': date.getMilliseconds() 241 | } 242 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) 243 | for (let k in o) 244 | if (new RegExp('(' + k + ')').test(fmt)) 245 | fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) 246 | return fmt 247 | } 248 | 249 | /** 250 | * @description create wiget 251 | * @param {*} title required 252 | * @param {*} texts required 253 | * @param {*} preview option 254 | */ 255 | const createWidget = async({ title, texts = { },spacing = 5, preview = '' }) => { 256 | let w = new ListWidget() 257 | w.spacing = spacing 258 | 259 | let gradient = new LinearGradient() 260 | let gradientSettings = await setupGradient() 261 | 262 | gradient.colors = gradientSettings.color() 263 | gradient.locations = gradientSettings.position() 264 | 265 | w.backgroundGradient = gradient 266 | texts['battery'] ? battery(w, title) : provideText(title, w, textFormat.title) 267 | for (const text in texts) { 268 | if (text != 'battery' && text != 'updateTime' && texts.hasOwnProperty(text) && texts[text]) { 269 | const element = texts[text] 270 | provideText(element, w, textFormat.SFMono) 271 | } 272 | } 273 | texts['updateTime'] ? provideText(`[更新] ${time('MM-dd HH:mm')}`, w, textFormat.SFMono) : `` 274 | 275 | widgetPreview = preview ? preview: 'small' 276 | 277 | if(widgetPreview == "small") { w.presentSmall() } 278 | else if (widgetPreview == "medium") { w.presentMedium() } 279 | else if (widgetPreview == "large") { w.presentLarge() } 280 | return w 281 | } 282 | 283 | 284 | /** 285 | * @description Provide a font based on the input. 286 | * @param {*} fontName 287 | * @param {*} fontSize 288 | */ 289 | const provideFont = (fontName, fontSize) => { 290 | const fontGenerator = { 291 | "ultralight": function() { return Font.ultraLightSystemFont(fontSize) }, 292 | "light": function() { return Font.lightSystemFont(fontSize) }, 293 | "regular": function() { return Font.regularSystemFont(fontSize) }, 294 | "medium": function() { return Font.mediumSystemFont(fontSize) }, 295 | "semibold": function() { return Font.semiboldSystemFont(fontSize) }, 296 | "bold": function() { return Font.boldSystemFont(fontSize) }, 297 | "heavy": function() { return Font.heavySystemFont(fontSize) }, 298 | "black": function() { return Font.blackSystemFont(fontSize) }, 299 | "italic": function() { return Font.italicSystemFont(fontSize) } 300 | } 301 | 302 | const systemFont = fontGenerator[fontName] 303 | if (systemFont) { return systemFont() } 304 | return new Font(fontName, fontSize) 305 | } 306 | 307 | 308 | /** 309 | * @description Add formatted text to a container. 310 | * @param {*} string 311 | * @param {*} container widget container 312 | * @param {*} format Object: size, color, font 313 | */ 314 | 315 | const provideText = (string, container, format) => { 316 | let url 317 | if (typeof string !== 'string') { 318 | url = string.url 319 | string = string.text 320 | } 321 | const stackItem = container.addStack() 322 | 323 | if (url) { 324 | stackItem.url = url 325 | } 326 | 327 | const textItem = stackItem.addText(string) 328 | const textFont = format.font || textFormat.defaultText.font 329 | const textSize = format.size || textFormat.defaultText.size 330 | const textColor = format.color || textFormat.defaultText.color 331 | 332 | textItem.font = provideFont(textFont, textSize) 333 | textItem.textColor = new Color(textColor) 334 | return stackItem 335 | } 336 | 337 | // Set up the gradient for the widget background. 338 | const setupGradient = async() => { 339 | 340 | // Requirements: sunrise 341 | if (!sunData) { await setupSunrise() } 342 | 343 | let gradient = { 344 | dawn: { 345 | color() { return [new Color("142C52"), new Color("1B416F"), new Color("62668B")] }, 346 | position() { return [0, 0.5, 1] }, 347 | }, 348 | 349 | sunrise: { 350 | color() { return [new Color("274875"), new Color("766f8d"), new Color("f0b35e")] }, 351 | position() { return [0, 0.8, 1.5] }, 352 | }, 353 | 354 | midday: { 355 | color() { return [new Color("3a8cc1"), new Color("90c0df")] }, 356 | position() { return [0, 1] }, 357 | }, 358 | 359 | noon: { 360 | color() { return [new Color("b2d0e1"), new Color("80B5DB"), new Color("3a8cc1")] }, 361 | position() { return [-0.2, 0.2, 1.5] }, 362 | }, 363 | 364 | sunset: { 365 | color() { return [new Color("32327A"), new Color("662E55"), new Color("7C2F43")] }, 366 | position() { return [0.1, 0.9, 1.2] }, 367 | }, 368 | 369 | twilight: { 370 | color() { return [new Color("021033"), new Color("16296b"), new Color("414791")] }, 371 | position() { return [0, 0.5, 1] }, 372 | }, 373 | 374 | night: { 375 | color() { return [new Color("16296b"), new Color("021033"), new Color("021033"), new Color("113245")] }, 376 | position() { return [-0.5, 0.2, 0.5, 1] }, 377 | }, 378 | } 379 | 380 | const sunrise = sunData.sunrise 381 | const sunset = sunData.sunset 382 | const utcTime = currentDate.getTime() 383 | 384 | function closeTo(time,mins) { 385 | return Math.abs(utcTime - time) < (mins * 60000) 386 | } 387 | 388 | // Use sunrise or sunset if we're within 30min of it. 389 | if (closeTo(sunrise,15)) { return gradient.sunrise } 390 | if (closeTo(sunset,15)) { return gradient.sunset } 391 | 392 | // In the 30min before/after, use dawn/twilight. 393 | if (closeTo(sunrise,45) && utcTime < sunrise) { return gradient.dawn } 394 | if (closeTo(sunset,45) && utcTime > sunset) { return gradient.twilight } 395 | 396 | // Otherwise, if it's night, return night. 397 | if (isNight(currentDate)) { return gradient.night } 398 | 399 | // If it's around noon, the sun is high in the sky. 400 | if (currentDate.getHours() == 12) { return gradient.noon } 401 | // Otherwise, return the "typical" theme. 402 | return gradient.midday 403 | } 404 | 405 | // Set up the sunData object. 406 | const setupSunrise = async () => { 407 | 408 | // Requirements: location 409 | if (!locationData) { await setupLocation() } 410 | 411 | // Set up the sunrise/sunset cache. 412 | const sunCachePath = files.joinPath(dict, "Env/Env-sun") 413 | const sunCacheExists = files.fileExists(sunCachePath) 414 | const sunCacheDate = sunCacheExists ? files.modificationDate(sunCachePath) : 0 415 | var sunDataRaw 416 | 417 | // If cache exists and it was created today, use cached data. 418 | if (sunCacheExists && sameDay(currentDate, sunCacheDate)) { 419 | const sunCache = files.readString(sunCachePath) 420 | sunDataRaw = JSON.parse(sunCache) 421 | 422 | // Otherwise, use the API to get sunrise and sunset times. 423 | } else { 424 | const sunReq = "https://api.sunrise-sunset.org/json?lat=" + locationData.latitude + "&lng=" + locationData.longitude + "&formatted=0&date=" + currentDate.getFullYear() + "-" + (currentDate.getMonth()+1) + "-" + currentDate.getDate() 425 | sunDataRaw = await new Request(sunReq).loadJSON() 426 | files.writeString(sunCachePath, JSON.stringify(sunDataRaw)) 427 | } 428 | 429 | // Store the timing values. 430 | sunData = {} 431 | sunData.sunrise = new Date(sunDataRaw.results.sunrise).getTime() 432 | sunData.sunset = new Date(sunDataRaw.results.sunset).getTime() 433 | } 434 | 435 | const setupLocation = async (lockLocation = true) => { 436 | 437 | locationData = {} 438 | const locationPath = files.joinPath(dict, "Env/Env-location") 439 | 440 | // If our location is unlocked or cache doesn't exist, ask iOS for location. 441 | var readLocationFromFile = false 442 | if (!lockLocation || !files.fileExists(locationPath)) { 443 | try { 444 | const location = await Location.current() 445 | locationData.latitude = location.latitude 446 | locationData.longitude = location.longitude 447 | files.writeString(locationPath, location.latitude + "," + location.longitude) 448 | 449 | } catch(e) { 450 | // If we fail in unlocked mode, read it from the cache. 451 | if (!lockLocation) { readLocationFromFile = true } 452 | 453 | // We can't recover if we fail on first run in locked mode. 454 | else { return } 455 | } 456 | } 457 | 458 | // If our location is locked or we need to read from file, do it. 459 | if (lockLocation || readLocationFromFile) { 460 | const locationStr = files.readString(locationPath).split(",") 461 | locationData.latitude = locationStr[0] 462 | locationData.longitude = locationStr[1] 463 | } 464 | return locationData 465 | } 466 | 467 | // Determines if the provided date is at night. 468 | const isNight = (dateInput) => { 469 | const timeValue = dateInput.getTime() 470 | return (timeValue < sunData.sunrise) || (timeValue > sunData.sunset) 471 | } 472 | // Determines if two dates occur on the same day 473 | const sameDay = (d1, d2) => { 474 | return d1.getFullYear() === d2.getFullYear() && 475 | d1.getMonth() === d2.getMonth() && 476 | d1.getDate() === d2.getDate() 477 | } 478 | /** 479 | * @description 返回电池百分比 480 | */ 481 | const renderBattery = () => { 482 | const batteryLevel = Device.batteryLevel() 483 | const batteryAscii = `${Math.round(batteryLevel * 100)}%` 484 | return batteryAscii 485 | } 486 | 487 | 488 | // Add a battery element to the widget; consisting of a battery icon and percentage. 489 | function battery(column,title) { 490 | const batteryLevel = Device.batteryLevel() 491 | // Set up the battery level item 492 | let batteryStack = column.addStack() 493 | provideText(title, batteryStack, textFormat.title) 494 | 495 | batteryStack.centerAlignContent() 496 | 497 | batteryStack.addSpacer() 498 | 499 | let batteryIcon = batteryStack.addImage(provideBatteryIcon()) 500 | batteryIcon.imageSize = new Size(20,20) 501 | 502 | 503 | // Change the battery icon to red if battery level is <= 20 to match system behavior 504 | if ( Math.round(batteryLevel * 100) > 20 || Device.isCharging() ) { 505 | 506 | batteryIcon.tintColor = Color.white() 507 | 508 | } else { 509 | 510 | batteryIcon.tintColor = Color.red() 511 | 512 | } 513 | 514 | // Display the battery status 515 | let batteryInfo = provideText(' '+renderBattery(), batteryStack, textFormat.battery) 516 | 517 | 518 | } 519 | 520 | 521 | // Provide a battery SFSymbol with accurate level drawn on top of it. 522 | function provideBatteryIcon() { 523 | 524 | if (Device.isCharging()) { return SFSymbol.named("battery.100.bolt").image } 525 | 526 | // Set the size of the battery icon. 527 | const batteryWidth = 87 528 | const batteryHeight = 41 529 | 530 | // Start our draw context. 531 | let draw = new DrawContext() 532 | draw.opaque = false 533 | draw.respectScreenScale = true 534 | draw.size = new Size(batteryWidth, batteryHeight) 535 | 536 | // Draw the battery. 537 | draw.drawImageInRect(SFSymbol.named("battery.0").image, new Rect(0, 0, batteryWidth, batteryHeight)) 538 | 539 | // Match the battery level values to the SFSymbol. 540 | const x = batteryWidth*0.1525 541 | const y = batteryHeight*0.247 542 | const width = batteryWidth*0.602 543 | const height = batteryHeight*0.505 544 | 545 | // Prevent unreadable icons. 546 | let level = Device.batteryLevel() 547 | if (level < 0.05) { level = 0.05 } 548 | 549 | // Determine the width and radius of the battery level. 550 | const current = width * level 551 | let radius = height/6.5 552 | 553 | // When it gets low, adjust the radius to match. 554 | if (current < (radius * 2)) { radius = current / 2 } 555 | 556 | // Make the path for the battery level. 557 | let barPath = new Path() 558 | barPath.addRoundedRect(new Rect(x, y, current, height), radius, radius) 559 | draw.addPath(barPath) 560 | draw.setFillColor(Color.black()) 561 | draw.fillPath() 562 | return draw.getImage() 563 | } 564 | 565 | const logErr = (e, messsage) => { 566 | console.error(e) 567 | } 568 | 569 | 570 | module.exports = { 571 | dict, 572 | get, 573 | getStr, 574 | post, 575 | _post, 576 | getFile, 577 | require, 578 | write, 579 | isFileExists, 580 | initFile, 581 | read, 582 | setdata, 583 | getdata, 584 | hasdata, 585 | rmdata, 586 | msg, 587 | input, 588 | time, 589 | createWidget, 590 | provideText, 591 | setupLocation, 592 | renderBattery, 593 | logErr 594 | } 595 | -------------------------------------------------------------------------------- /ENV.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: orange; icon-glyph: key; 4 | // Variables used by Scriptable. 5 | 6 | /* ----------------------------------------------- 7 | 这是一份模板,可以导入进行使用里面封装的方法变量,谨慎修改!! 8 | ----------------------------------------------- */ 9 | 10 | //------------------------------------------------ 11 | const fm = FileManager.local() 12 | let path = "" 13 | let exports = {} 14 | //------------------------------------------------ 15 | let configs = { 16 | previewSize: "Medium", // 预览大小【小:Small,中:Medium,大:Large】 17 | changePicBg: true, // 是否需要更换背景 18 | colorMode: false, // 是否是纯色背景 19 | bgColor: undefined, // 小组件背景色 20 | topPadding: 0, // 内容区边距 21 | leftPadding: 0, // 内容区边距 22 | bottomPadding: 0, // 内容区边距 23 | rightPadding: 0, // 内容区边距 24 | refreshInterval: 0 // 刷新间隔,单位分钟,非精准,会有3-5分钟差距 25 | } 26 | exports.configs = configs 27 | //------------------------------------------------ 28 | // 标题样式定义 29 | const textStyle = { 30 | stack: undefined, // 加入到哪个内容栈显示 31 | marginStart: 0, // 开始距离,水平方向的就是左边距离,垂直方向的就是顶部距离 32 | marginEnd: 0, // 结束距离,水平方向的就是右边距离,垂直方向的就是底部距离 33 | text: "", // 显示的文字 34 | width: 0, // 宽 35 | height: 0, // 长 36 | lineLimit: 0, // 行数控制,0是全部展示 37 | font: undefined, // 字体 38 | textColor: undefined, // 文字颜色 39 | } 40 | exports.textStyle = textStyle 41 | //------------------------------------------------ 42 | // 图片样式定义 43 | const imgStyle = { 44 | stack: undefined, // 加入到哪个内容栈显示 45 | marginStart: 0, // 开始距离,水平方向的就是左边距离,垂直方向的就是顶部距离 46 | marginEnd: 0, // 结束距离,水平方向的就是右边距离,垂直方向的就是底部距离 47 | img: undefined, // 图片资源 48 | width: 50, // 宽 49 | height: 50, // 长 50 | tintColor: undefined, // 图片渲染颜色 51 | } 52 | exports.imgStyle = textStyle 53 | //------------------------------------------------ 54 | 55 | 56 | 57 | //------------------------------------------------ 58 | exports.run = async function (scriptName, widget, autoDak = false, autoRun = true) { 59 | let appearance = (await exports.isUsingDarkAppearance()) ? 'dark' : 'light' 60 | let appearanceStr = appearance == 'dark' ? '暗黑模式' : '白天模式' 61 | let fileImgName = `${scriptName}.jpg` 62 | if (autoDak) { 63 | fileImgName = `${scriptName}-${appearance}.jpg` 64 | } else { 65 | appearanceStr = "" 66 | } 67 | 68 | path = fm.joinPath(fm.documentsDirectory(), fileImgName) 69 | 70 | if (!exports.configs.changePicBg || exports.configs.colorMode || config.runsInWidget) { 71 | if (autoRun) { 72 | // 结束并且进行预览 73 | completeWidget(widget) 74 | } 75 | return 76 | } 77 | 78 | let subTips = "" 79 | if (appearanceStr.length > 0) { 80 | subTips = `在${appearanceStr}下的` 81 | } 82 | const okTips = `您的小部件${subTips}背景已准备就绪,请退到桌面查看即可。` 83 | let message = "图片模式支持相册照片&背景透明" 84 | let options = ["图片选择", "透明背景"] 85 | let isTransparentMode = await generateAlert(message, options) 86 | if (!isTransparentMode) { 87 | let img = await Photos.fromLibrary() 88 | message = okTips 89 | const resultOptions = ["好的"] 90 | await generateAlert(message, resultOptions) 91 | fm.writeImage(path, img) 92 | } else { 93 | message = "以下是【透明背景】生成步骤,如果你没有屏幕截图请退出,并返回主屏幕长按进入编辑模式。滑动到最右边的空白页截图,然后重新运行!" 94 | let exitOptions = ["继续(已有截图)", "退出(没有截图)"] 95 | 96 | let shouldExit = await generateAlert(message, exitOptions) 97 | if (shouldExit) return 98 | 99 | // Get screenshot and determine phone size. 100 | let img = await Photos.fromLibrary() 101 | let height = img.size.height 102 | let phone = phoneSizes()[height] 103 | if (!phone) { 104 | message = "您似乎选择了非iPhone屏幕截图的图像,或者不支持您的iPhone。请使用其他图像再试一次!" 105 | await generateAlert(message, ["好的!我现在去截图"]) 106 | return 107 | } 108 | 109 | // Prompt for widget size and position. 110 | message = "您想要创建什么尺寸的小部件?" 111 | let sizes = ["小号", "中号", "大号"] 112 | let size = await generateAlert(message, sizes) 113 | let widgetSize = sizes[size] 114 | 115 | message = "您想它在什么位置?" 116 | message += (height == 1136 ? " (请注意,您的设备仅支持两行小部件,因此中间和底部选项相同。)" : "") 117 | 118 | // Determine image crop based on phone size. 119 | let crop = { w: "", h: "", x: "", y: "" } 120 | if (widgetSize == "小号") { 121 | crop.w = phone.小号 122 | crop.h = phone.小号 123 | let positions = ["顶部 左边", "顶部 右边", "中间 左边", "中间 右边", "底部 左边", "底部 右边"] 124 | let position = await generateAlert(message, positions) 125 | 126 | // Convert the two words into two keys for the phone size dictionary. 127 | let keys = positions[position].split(' ') 128 | crop.y = phone[keys[0]] 129 | crop.x = phone[keys[1]] 130 | 131 | } else if (widgetSize == "中号") { 132 | crop.w = phone.中号 133 | crop.h = phone.小号 134 | 135 | // 中号 and 大号 widgets have a fixed x-value. 136 | crop.x = phone.左边 137 | let positions = ["顶部", "中间", "底部"] 138 | let position = await generateAlert(message, positions) 139 | let key = positions[position].toLowerCase() 140 | crop.y = phone[key] 141 | 142 | } else if (widgetSize == "大号") { 143 | crop.w = phone.中号 144 | crop.h = phone.大号 145 | crop.x = phone.左边 146 | let positions = ["顶部", "底部"] 147 | let position = await generateAlert(message, positions) 148 | 149 | // 大号 widgets at the 底部 have the "中间" y-value. 150 | crop.y = position ? phone.中间 : phone.顶部 151 | } 152 | 153 | // Crop image and finalize the widget. 154 | let imgCrop = cropImage(img, new Rect(crop.x, crop.y, crop.w, crop.h)) 155 | 156 | message = okTips 157 | const resultOptions = ["好的"] 158 | await generateAlert(message, resultOptions) 159 | fm.writeImage(path, imgCrop) 160 | } 161 | 162 | if (autoRun) { 163 | // 结束并且进行预览 164 | completeWidget(widget) 165 | } 166 | } 167 | //------------------------------------------------ 168 | async function completeWidget(widget) { 169 | // 背景 170 | if (exports.configs.colorMode) { 171 | widget.backgroundColor = exports.configs.bgColor 172 | } else { 173 | widget.backgroundImage = fm.readImage(path) 174 | } 175 | exports.finish(widget) 176 | } 177 | //------------------------------------------------ 178 | exports.readBgImg = function () { 179 | return fm.readImage(path) 180 | } 181 | //------------------------------------------------ 182 | exports.finish = function (widget, preview = true) { 183 | // 设置边距(上,左,下,右) 184 | widget.setPadding(exports.configs.topPadding, exports.configs.leftPadding, exports.configs.bottomPadding, exports.configs.rightPadding) 185 | // 设置组件 186 | Script.setWidget(widget) 187 | // 完成脚本 188 | Script.complete() 189 | if (preview) { 190 | // 预览 191 | if (exports.configs.previewSize == "Large") { 192 | widget.presentLarge() 193 | } else if (exports.configs.previewSize == "Medium") { 194 | widget.presentMedium() 195 | } else { 196 | widget.presentSmall() 197 | } 198 | } 199 | 200 | } 201 | //------------------------------------------------ 202 | // Generate an alert with the provided array of options. 203 | async function generateAlert(message, options) { 204 | let alert = new Alert() 205 | alert.message = message 206 | 207 | for (const option of options) { 208 | alert.addAction(option) 209 | } 210 | 211 | let response = await alert.presentAlert() 212 | return response 213 | } 214 | //------------------------------------------------ 215 | // Crop an image into the specified rect. 216 | function cropImage(img, rect) { 217 | let draw = new DrawContext() 218 | draw.size = new Size(rect.width, rect.height) 219 | draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y)) 220 | return draw.getImage() 221 | } 222 | //------------------------------------------------ 223 | // Pixel sizes and positions for widgets on all supported phones. 224 | function phoneSizes() { 225 | let phones = { 226 | "2340": { // 12mini 227 | "小号": 436, 228 | "中号": 936, 229 | "大号": 980, 230 | "左边": 72, 231 | "右边": 570, 232 | "顶部": 212, 233 | "中间": 756, 234 | "底部": 1300, 235 | }, 236 | 237 | "2532": { // 12/12 Pro 238 | "小号": 472, 239 | "中号": 1012, 240 | "大号": 1058, 241 | "左边": 78, 242 | "右边": 618, 243 | "顶部": 230, 244 | "中间": 824, 245 | "底部": 1408, 246 | }, 247 | 248 | "2778": { // 12 Pro Max 249 | "小号": 510, 250 | "中号": 1114, 251 | "大号": 1162, 252 | "左边": 96, 253 | "右边": 678, 254 | "顶部": 256, 255 | "中间": 882, 256 | "底部": 1544, 257 | }, 258 | 259 | "2688": { 260 | "小号": 507, 261 | "中号": 1080, 262 | "大号": 1137, 263 | "左边": 81, 264 | "右边": 654, 265 | "顶部": 228, 266 | "中间": 858, 267 | "底部": 1488 268 | }, 269 | 270 | "1792": { 271 | "小号": 338, 272 | "中号": 720, 273 | "大号": 758, 274 | "左边": 54, 275 | "右边": 436, 276 | "顶部": 160, 277 | "中间": 580, 278 | "底部": 1000 279 | }, 280 | 281 | "2436": { 282 | "小号": 465, 283 | "中号": 987, 284 | "大号": 1035, 285 | "左边": 69, 286 | "右边": 591, 287 | "顶部": 213, 288 | "中间": 783, 289 | "底部": 1353 290 | }, 291 | 292 | "2208": { 293 | "小号": 471, 294 | "中号": 1044, 295 | "大号": 1071, 296 | "左边": 99, 297 | "右边": 672, 298 | "顶部": 114, 299 | "中间": 696, 300 | "底部": 1278 301 | }, 302 | 303 | "1334": { 304 | "小号": 296, 305 | "中号": 642, 306 | "大号": 648, 307 | "左边": 54, 308 | "右边": 400, 309 | "顶部": 60, 310 | "中间": 412, 311 | "底部": 764 312 | }, 313 | 314 | "1136": { 315 | "小号": 282, 316 | "中号": 584, 317 | "大号": 622, 318 | "左边": 30, 319 | "右边": 332, 320 | "顶部": 59, 321 | "中间": 399, 322 | "底部": 399 323 | } 324 | } 325 | return phones 326 | } 327 | //------------------------------------------------ 328 | exports.isUsingDarkAppearance = async function () { 329 | const wv = new WebView() 330 | let js = "(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)" 331 | let r = await wv.evaluateJavaScript(js) 332 | return r 333 | } 334 | //================================================ 335 | //================================================ 336 | //================================================ 337 | //================================================ 338 | //================================================ 339 | exports.getImage = async function (url) { 340 | const request = new Request(url) 341 | const data = await request.loadImage() 342 | return data 343 | } 344 | //------------------------------------------------ 345 | exports.getJson = async function (url) { 346 | const request = new Request(url) 347 | const defaultHeaders = { 348 | "Accept": "*/*", 349 | "Content-Type": "application/json" 350 | } 351 | 352 | request.url = url 353 | request.method = 'GET' 354 | request.headers = defaultHeaders 355 | 356 | const data = await request.loadJSON() 357 | return data 358 | } 359 | //------------------------------------------------ 360 | exports.alignVerticallyCenterStack = function (alignmentStack) { 361 | let returnStack = alignmentStack.addStack() 362 | returnStack.layoutVertically() 363 | returnStack.centerAlignContent() 364 | return returnStack 365 | } 366 | //------------------------------------------------ 367 | exports.alignHorizontallyCenterStack = function (alignmentStack) { 368 | let returnStack = alignmentStack.addStack() 369 | returnStack.layoutHorizontally() 370 | returnStack.centerAlignContent() 371 | return returnStack 372 | } 373 | //------------------------------------------------ 374 | exports.alignBottomStack = function (alignmentStack, marginBottom) { 375 | let contentStack = alignmentStack.addStack() 376 | contentStack.layoutVertically() 377 | contentStack.addSpacer() 378 | 379 | let returnStack = contentStack.addStack() 380 | 381 | // 添加下边距 382 | if (marginBottom != undefined && marginBottom != 0) { 383 | contentStack.addSpacer(marginBottom) 384 | } 385 | 386 | return returnStack 387 | } 388 | //------------------------------------------------ 389 | exports.alignTopStack = function (alignmentStack, marginTop) { 390 | let contentStack = alignmentStack.addStack() 391 | contentStack.layoutVertically() 392 | 393 | // 添加左边距 394 | if (marginTop != undefined && marginTop != 0) { 395 | contentStack.addSpacer(marginTop) 396 | } 397 | 398 | let returnStack = contentStack.addStack() 399 | returnStack.layoutVertically() 400 | 401 | contentStack.addSpacer() 402 | return returnStack 403 | } 404 | //------------------------------------------------ 405 | exports.alignLeftStack = function (alignmentStack, marginLeft) { 406 | let contentStack = alignmentStack.addStack() 407 | contentStack.layoutHorizontally() 408 | 409 | let returnStack = contentStack.addStack() 410 | returnStack.layoutHorizontally() 411 | 412 | // 添加左边距 413 | if (marginLeft != undefined && marginLeft != 0) { 414 | returnStack.addSpacer(marginLeft) 415 | } 416 | 417 | contentStack.addSpacer() 418 | return returnStack 419 | } 420 | //------------------------------------------------ 421 | exports.alignRightStack = function (alignmentStack, marginRight) { 422 | let contentStack = alignmentStack.addStack() 423 | contentStack.layoutHorizontally() 424 | contentStack.addSpacer() 425 | 426 | let returnStack = contentStack.addStack() 427 | 428 | // 添加右边距 429 | if (marginRight != undefined && marginRight != 0) { 430 | contentStack.addSpacer(marginRight) 431 | } 432 | 433 | return returnStack 434 | } 435 | //------------------------------------------------ 436 | exports.addStyleImg = function () { 437 | const imgStyle = exports.imgStyle 438 | const contentStack = imgStyle.stack 439 | if (contentStack == undefined) { 440 | return 441 | } 442 | 443 | const marginStart = textStyle.marginStart 444 | if (marginStart != undefined && marginStart != 0) { 445 | contentStack.addSpacer(marginStart) 446 | } 447 | 448 | const imgSpan = contentStack.addImage(imgStyle.img) 449 | imgSpan.imageSize = new Size(imgStyle.width, imgStyle.height) 450 | const tintColor = imgStyle.tintColor 451 | if (tintColor != undefined) { 452 | imgSpan.tintColor = tintColor 453 | } 454 | 455 | const marginEnd = textStyle.marginEnd 456 | if (marginStart != undefined && marginStart != 0) { 457 | contentStack.addSpacer(marginEnd) 458 | } 459 | 460 | // 重置样式 461 | resetImgStyle() 462 | } 463 | //------------------------------------------------ 464 | exports.addStyleText = function () { 465 | const textStyle = exports.textStyle 466 | const contentStack = textStyle.stack 467 | if (contentStack == undefined) { 468 | return 469 | } 470 | 471 | const marginStart = textStyle.marginStart 472 | if (marginStart != undefined && marginStart != 0) { 473 | contentStack.addSpacer(marginStart) 474 | } 475 | 476 | const textSpan = contentStack.addText(`${textStyle.text}`) 477 | contentStack.size = new Size(textStyle.width, textStyle.height) 478 | textSpan.lineLimit = textStyle.lineLimit 479 | textSpan.font = textStyle.font 480 | textSpan.textColor = textStyle.textColor 481 | 482 | const marginEnd = textStyle.marginEnd 483 | if (marginStart != undefined && marginStart != 0) { 484 | contentStack.addSpacer(marginEnd) 485 | } 486 | 487 | // 重置样式 488 | resetTextStyle() 489 | } 490 | //------------------------------------------------ 491 | exports.getLocation = async function () { 492 | let locationData = { 493 | "latitude": undefined, 494 | "longitude": undefined, 495 | "locality": undefined, 496 | "subLocality": undefined 497 | } 498 | 499 | // 缓存目录 500 | const cachePath = fm.joinPath(fm.documentsDirectory(), "env-lsp-location-cache") 501 | 502 | try { 503 | const location = await Location.current() 504 | const geocode = await Location.reverseGeocode(location.latitude, location.longitude, "zh_cn") 505 | locationData.latitude = location.latitude 506 | locationData.longitude = location.longitude 507 | const geo = geocode[0] 508 | // 市 509 | if (locationData.locality == undefined) { 510 | locationData.locality = geo.locality 511 | } 512 | // 区 513 | if (locationData.subLocality == undefined) { 514 | locationData.subLocality = geo.subLocality 515 | } 516 | // 街道 517 | locationData.street = geo.thoroughfare 518 | 519 | // 缓存数据 520 | fm.writeString(cachePath, JSON.stringify(locationData)) 521 | 522 | log("定位信息:latitude=" + location.latitude + ",longitude=" + location.longitude + ",locality=" 523 | + locationData.locality + ",subLocality=" + locationData.subLocality + ",street=" + locationData.street) 524 | } catch (e) { 525 | log(`定位出错了,${e.toString()}`) 526 | // 读取缓存数据 527 | const locationCache = fm.readString(cachePath) 528 | log(`读取定位缓存数据:${locationCache}`) 529 | locationData = JSON.parse(locationCache) 530 | } 531 | 532 | return locationData 533 | } 534 | //------------------------------------------------ 535 | function resetImgStyle() { 536 | exports.imgStyle.stack = undefined // 加入到哪个内容栈显示 537 | exports.textStyle.marginStart = 0 538 | exports.textStyle.marginEnd = 0 539 | exports.imgStyle.img = undefined // 图片资源 540 | exports.imgStyle.width = 0 // 宽 541 | exports.imgStyle.height = 0 // 长 542 | exports.imgStyle.tintColor = undefined // 图片渲染颜色 543 | } 544 | //------------------------------------------------ 545 | function resetTextStyle() { 546 | exports.textStyle.stack = undefined // 加入到哪个内容栈显示 547 | exports.textStyle.marginStart = 0 548 | exports.textStyle.marginEnd = 0 549 | exports.textStyle.text = "" // 显示的文字 550 | exports.textStyle.width = 0 // 宽 551 | exports.textStyle.height = 0 // 长 552 | exports.textStyle.lineLimit = 0 // 行数控制,0是全部展示 553 | exports.textStyle.font = undefined // 字体 554 | exports.textStyle.textColor = undefined // 文字颜色 555 | } 556 | //------------------------------------------------ 557 | // 是否使用缓存 558 | exports.useCache = function (cachePath) { 559 | let use = false 560 | const cacheExists = fm.fileExists(cachePath) 561 | const cacheDate = cacheExists ? fm.modificationDate(cachePath) : 0 562 | log(`缓存时间:${exports.getDateStr(new Date(cacheDate), "MM月dd日HH:mm")}`) 563 | const refreshInterval = configs.refreshInterval * (60 * 1000) 564 | log(`缓存过期:${configs.refreshInterval}min`) 565 | if (cacheExists && ((new Date()).getTime() - cacheDate.getTime()) < refreshInterval) { 566 | use = true 567 | } 568 | 569 | log(`是否使用缓存:${use}`) 570 | 571 | return use 572 | } 573 | //------------------------------------------------ 574 | // 格式化时间 575 | exports.getDateStr = function (date, formatter = "yyyy年MM月d日 EEE", locale = "zh_cn") { 576 | let df = new DateFormatter() 577 | df.locale = locale 578 | df.dateFormat = formatter 579 | return df.string(date) 580 | } 581 | //------------------------------------------------ 582 | module.exports = exports 583 | //------------------------------------------------ -------------------------------------------------------------------------------- /彩云天气.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: teal; icon-glyph: magic; 4 | //------------------------------------------------ 5 | const env = importModule("ENV.js"); 6 | //------------------------------------------------ 7 | // 配置区 8 | env.configs.previewSize = "Medium"; // 预览大小【小:Small,中:Medium,大:Large】 9 | env.configs.changePicBg = true; // 是否需要更换背景 10 | env.configs.colorMode = false; // 是否是纯色背景 11 | env.configs.bgColor = new Color("000000"); // 小组件背景色 12 | env.configs.topPadding = 4; // 内容区边距 13 | env.configs.leftPadding = 4; // 内容区边距 14 | env.configs.bottomPadding = 0; // 内容区边距 15 | env.configs.rightPadding = 4; // 内容区边距 16 | env.configs.refreshInterval = 20; // 刷新间隔,单位分钟,非精准,会有3-5分钟差距 17 | ////////////////////////////////// 18 | const imgStyle = env.imgStyle; 19 | const textStyle = env.textStyle; 20 | ///////////////////////////////// 21 | 22 | // 地区 23 | const locale = "zh_cn"; 24 | 25 | // 彩云天气的apiKey,下面的apiKey为彩云天气官方apiKey,可以去免费申请自己的apiKey:https://caiyunapp.com 26 | const apiKey = "TAkhjf8d1nlSlspN"; 27 | 28 | // 默认的定位信息,定位失败的时候默认读取 29 | // https://open.caiyunapp.com/File:Adcode-release-2020-06-10.xlsx.zip 30 | // 上述链接查看对应地区的详细经纬度 31 | let locationData = { 32 | latitude: undefined, 33 | longitude: undefined, 34 | locality: undefined, 35 | subLocality: undefined, 36 | }; 37 | // 锁定地区,直接使用上述填写的地址信息不进行定位 38 | const lockLocation = false; 39 | 40 | // 日程显示条数 41 | const maxSchedules = 1; 42 | 43 | // 顶部问候语,英文花样文字:https://beizhedenglong.github.io/weird-fonts/ 44 | const greetingText = { 45 | nightGreeting: "🦉 𝑇𝑖𝑚𝑒 𝑡𝑜 𝑔𝑒𝑡 𝑙𝑎𝑖𝑑~", 46 | morningGreeting: "💫 𝐺𝑜𝑜𝑑 𝑚𝑜𝑟𝑛𝑖𝑛𝑔~", 47 | noonGreeting: "🥳 𝐺𝑜𝑜𝑑 𝑛𝑜𝑜𝑛~", 48 | afternoonGreeting: "🐡 𝐺𝑜𝑜𝑑 𝑎𝑓𝑡𝑒𝑟𝑛𝑜𝑜𝑛~", 49 | eveningGreeting: "🐳 𝐺𝑜𝑜𝑑 𝑒𝑣𝑒𝑛𝑖𝑛𝑔~", 50 | }; 51 | 52 | // 天气对应的icon 53 | const weatherIcos = { 54 | CLEAR_DAY: "https://s1.ax1x.com/2020/10/24/BZSMJe.png", // 晴(白天) CLEAR_DAY 55 | CLEAR_NIGHT: "https://s1.ax1x.com/2020/10/24/BZS8sI.png", // 晴(夜间) CLEAR_NIGHT 56 | PARTLY_CLOUDY_DAY: "https://s1.ax1x.com/2020/10/24/BZSKiD.png", // 多云(白天) PARTLY_CLOUDY_DAY 57 | PARTLY_CLOUDY_NIGHT: "https://s1.ax1x.com/2020/10/24/BZSKiD.png", // 多云(夜间) PARTLY_CLOUDY_NIGHT 58 | CLOUDY: "https://s1.ax1x.com/2020/10/24/BZSnIO.png", // 阴(白天) CLOUDY 59 | CLOUDY_NIGHT: "https://s1.ax1x.com/2020/10/24/BZS3QA.png", // 阴(夜间) CLOUDY 60 | LIGHT_HAZE: "https://s1.ax1x.com/2020/10/24/BZ8Rrn.png", // 轻度雾霾 LIGHT_HAZE 61 | MODERATE_HAZE: "https://s1.ax1x.com/2020/10/24/BZ3whF.png", // 中度雾霾 MODERATE_HAZE 62 | HEAVY_HAZE: "https://s1.ax1x.com/2020/10/24/BZ3akT.png", // 重度雾霾 HEAVY_HAZE 63 | LIGHT_RAIN: "https://s1.ax1x.com/2020/10/24/BZSdJg.png", // 小雨 LIGHT_RAIN 64 | MODERATE_RAIN: "https://s1.ax1x.com/2020/10/24/BZSwWQ.png", // 中雨 MODERATE_RAIN 65 | HEAVY_RAIN: "https://s1.ax1x.com/2020/10/24/BZS0zj.png", // 大雨 HEAVY_RAIN 66 | STORM_RAIN: "https://s1.ax1x.com/2020/10/24/BZSsLq.png", // 暴雨 STORM_RAIN 67 | FOG: "https://s1.ax1x.com/2020/10/24/BZ82Ks.png", // 雾 FOG 68 | LIGHT_SNOW: "https://s1.ax1x.com/2020/10/24/BZSbTK.png", // 小雪 LIGHT_SNOW 69 | MODERATE_SNOW: "https://s1.ax1x.com/2020/10/24/BZSLFO.png", // 中雪 MODERATE_SNOW 70 | HEAVY_SNOW: "https://s1.ax1x.com/2020/10/24/BZSOYD.png", // 大雪 HEAVY_SNOW 71 | STORM_SNOW: "https://s1.ax1x.com/2020/10/24/BZ8A4U.png", // 暴雪 STORM_SNOW 72 | DUST: "https://s1.ax1x.com/2020/10/24/BZ8hV0.png", // 浮尘 DUST 73 | SAND: "https://s1.ax1x.com/2020/10/24/BZ84aV.png", // 沙尘 SAND 74 | WIND: "https://s1.ax1x.com/2020/10/24/BZ8TGF.png", // 大风 WIND 75 | }; 76 | 77 | // 天气信息控制 78 | const weatherControl = { 79 | HUMIDITY: true, // 是否显示相对湿度 80 | COMFORT: true, // 是否显示舒适指数 81 | ULTRAVIOLET: true, // 是否显示紫外线指数 82 | AQI: true, // 是否显示空气质量指数 83 | HEIGHT_LOW: true, // 是否显示温度范围 84 | SUNRISE_SUNSET: true, // 是否显示日出日落时间 85 | UPDATE_TIME: true, // 是否显示天气更新时间 86 | }; 87 | 88 | // 默认字体颜色 89 | const defaultTextColor = new Color("ffffff"); 90 | 91 | ////////////////////////////////////////// 92 | // 当前日期 93 | const currentDate = new Date(); 94 | // 年份 95 | const year = currentDate.getFullYear(); 96 | // 月份 97 | const month = currentDate.getMonth() + 1; 98 | // 日期 99 | const day = currentDate.getDate(); 100 | // 小时 101 | const hour = currentDate.getHours(); 102 | // 分钟 103 | const minute = currentDate.getMinutes(); 104 | //------------------------------------------------ 105 | // 脚本名字 106 | const name = Script.name(); 107 | // 文件 108 | const fm = FileManager.local(); 109 | //------------------------------------------------ 110 | 111 | //↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓内容区↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 112 | //------------------------------------------------ 113 | 114 | /****************************小组件内容START****************************/ 115 | // 彩云天气信息 116 | const weatherInfo = await getWeather(); 117 | // 农历信息 118 | const lunarInfo = await getLunar(); 119 | // 日程信息 120 | const showSchedules = await getSchedules(); 121 | 122 | ////////////////////////////////////////// 123 | // 内容排版 124 | const widget = new ListWidget(); 125 | let contentStack = widget.addStack(); 126 | contentStack.layoutHorizontally(); 127 | // 整体内容居中对齐 128 | contentStack.centerAlignContent(); 129 | ////////////////////////////////////////// 130 | 131 | //////////////////////////////////////////////////////////////////////////////////// 132 | // 左侧内容 133 | let leftStack = contentStack.addStack(); 134 | leftStack.layoutVertically(); 135 | 136 | ////////////////////////////////////////// 137 | // 问候 138 | let titleStack = env.alignHorizontallyCenterStack(leftStack); 139 | // 问候语获取内容 140 | const greeting = provideGreeting(currentDate); 141 | // 添加显示标题 142 | textStyle.stack = titleStack; 143 | textStyle.text = greeting; 144 | textStyle.font = Font.systemFont(22); 145 | textStyle.textColor = defaultTextColor; 146 | env.addStyleText(); 147 | 148 | ////////////////////////////////////////// 149 | // 年月日周 150 | const dateStr = getDateStr(currentDate); 151 | // 显示 152 | textStyle.stack = leftStack; 153 | textStyle.marginStart = 2; 154 | textStyle.text = dateStr; 155 | textStyle.font = Font.systemFont(16); 156 | textStyle.textColor = new Color("ffcc99"); 157 | env.addStyleText(); 158 | 159 | ////////////////////////////////////////// 160 | // 星期几 / 农历日期 161 | const weekDayColor = new Color("ffffff", 0.9); 162 | leftStack.addSpacer(2); 163 | let dateStack = env.alignHorizontallyCenterStack(leftStack); 164 | 165 | // 农历信息 166 | const lunarData = lunarInfo.data[0]; 167 | let infoLunarText = lunarData.lunarText; 168 | infoLunarText = infoLunarText.substring(5, infoLunarText.length); 169 | // 节假期信息 170 | const lunarHoliday = lunarData.calendarDay.lunarHoliday.key; 171 | const solarHoliday = lunarData.calendarDay.solarHoliday.key; 172 | // 农历节气 173 | const solarTerm = lunarData.calendarDay.solarTerm; 174 | // 下一个节气的时间间隔 175 | const solarTermDays = lunarData.calendarDay.solarTermDays; 176 | const holidayText = `${lunarHoliday ? lunarHoliday + "◇" : ""}${ 177 | solarHoliday ? solarHoliday + "◇" : "" 178 | }${solarTerm ? solarTerm : ""}`; 179 | log(`节假日信息:${holidayText}`); 180 | // 添加显示农历 181 | textStyle.stack = dateStack; 182 | textStyle.text = `${infoLunarText} ${holidayText}`; 183 | textStyle.font = Font.systemFont(14); 184 | textStyle.textColor = weekDayColor; 185 | env.addStyleText(); 186 | 187 | // 电池信息 188 | dateStack.addSpacer(4); 189 | const batteryLevel = Device.batteryLevel() * 100; 190 | const batteryStr = `〓${getBatteryLevel()}〓`; 191 | // 添加显示电池具体信息 192 | textStyle.stack = dateStack; 193 | textStyle.text = batteryStr; 194 | textStyle.font = Font.systemFont(14); 195 | textStyle.textColor = weekDayColor; 196 | env.addStyleText(); 197 | 198 | ////////////////////////////////////////// 199 | // 天气预警、预告信息 200 | const weatherAlertInfo = weatherInfo.alertWeatherTitle; 201 | let weatherDesc = weatherInfo.weatherDesc; 202 | if (weatherAlertInfo != undefined) { 203 | weatherDesc = weatherAlertInfo; 204 | } 205 | // 添加显示天气预告信息 206 | textStyle.stack = leftStack; 207 | textStyle.marginStart = 3; 208 | textStyle.text = weatherDesc; 209 | textStyle.lineLimit = 1; 210 | textStyle.font = Font.systemFont(12); 211 | textStyle.textColor = defaultTextColor; 212 | env.addStyleText(); 213 | 214 | ////////////////////////////////////////// 215 | // 日程、诗词 216 | const schedulePoetryColor = new Color("ffffff", 0.7); 217 | const scheduleSize = showSchedules.length; 218 | if (scheduleSize > 0) { 219 | leftStack.addSpacer(1); 220 | // 添加分割线 221 | textStyle.stack = leftStack; 222 | textStyle.marginStart = 1; 223 | textStyle.text = "----------------------------------------"; 224 | textStyle.lineLimit = 1; 225 | textStyle.font = Font.systemFont(10); 226 | textStyle.textColor = schedulePoetryColor; 227 | env.addStyleText(); 228 | // 添加日程 229 | leftStack.addSpacer(2); 230 | let scheduleIndex = 0; 231 | for (let schedule of showSchedules) { 232 | // 索引值 233 | scheduleIndex++; 234 | if (scheduleIndex > maxSchedules) { 235 | return; 236 | } 237 | 238 | const scheduleStack = env.alignHorizontallyCenterStack(leftStack); 239 | // 图片 240 | const img = SFSymbol.named("rosette").image; 241 | // 展示ico 242 | imgStyle.stack = scheduleStack; 243 | imgStyle.width = 15; 244 | imgStyle.height = 15; 245 | imgStyle.img = img; 246 | imgStyle.tintColor = schedulePoetryColor; 247 | env.addStyleImg(); 248 | scheduleStack.addSpacer(4); 249 | 250 | // 日程标题 251 | textStyle.stack = scheduleStack; 252 | textStyle.text = schedule.title; 253 | textStyle.lineLimit = 1; 254 | textStyle.font = Font.systemFont(11); 255 | textStyle.textColor = schedulePoetryColor; 256 | env.addStyleText(); 257 | 258 | // 开始时间 259 | leftStack.addSpacer(2); 260 | const scheduleTimeStack = leftStack.addStack(); 261 | scheduleTimeStack.layoutHorizontally(); 262 | scheduleTimeStack.addSpacer(18); 263 | // 展示时间 264 | textStyle.stack = scheduleTimeStack; 265 | textStyle.text = schedule.timeText; 266 | textStyle.lineLimit = 1; 267 | textStyle.font = Font.systemFont(10); 268 | textStyle.textColor = schedulePoetryColor; 269 | env.addStyleText(); 270 | } 271 | } else { 272 | // 请求今日诗词 273 | const poetry = await getPoetry(); 274 | 275 | // 添加今日诗词 276 | leftStack.addSpacer(8); 277 | const poetryStack = leftStack.addStack(); 278 | // 诗词背景 279 | poetryStack.backgroundColor = new Color("666", 0.5); 280 | poetryStack.cornerRadius = 4; 281 | poetryStack.layoutVertically(); 282 | poetryStack.addSpacer(4); 283 | // 284 | const poetryInfoStack = poetryStack.addStack(); 285 | poetryInfoStack.layoutHorizontally(); 286 | poetryInfoStack.addSpacer(4); 287 | const poetryInfo = poetry.data; 288 | // 添加显示诗词 289 | const potryContent = `"${poetryInfo.content.substring( 290 | 0, 291 | poetryInfo.content.length - 1 292 | )}"`; 293 | textStyle.stack = poetryInfoStack; 294 | textStyle.text = potryContent; 295 | textStyle.lineLimit = 1; 296 | textStyle.font = Font.systemFont(11); 297 | textStyle.textColor = schedulePoetryColor; 298 | env.addStyleText(); 299 | 300 | // 添加作者 301 | const authStack = poetryStack.addStack(); 302 | authStack.layoutHorizontally(); 303 | authStack.addSpacer(); 304 | // 显示作者 305 | const authorText = `⊱${poetryInfo.origin.dynasty}·${poetryInfo.origin.author}⊰`; 306 | textStyle.stack = authStack; 307 | textStyle.text = authorText; 308 | textStyle.lineLimit = 1; 309 | textStyle.font = Font.systemFont(11); 310 | textStyle.textColor = schedulePoetryColor; 311 | env.addStyleText(); 312 | ////// 313 | authStack.addSpacer(4); 314 | poetryStack.addSpacer(4); 315 | } 316 | 317 | //////////////////////////////////////////////////////////////////////////////////// 318 | 319 | //////////////////////////////////////////////////////////////////////////////////// 320 | // 右侧内容 321 | contentStack.addSpacer(); 322 | let rightStack = contentStack.addStack(); 323 | // 写死右侧宽度 324 | rightStack.size = new Size(115, 0); 325 | rightStack.layoutVertically(); 326 | ////////////////////////////////////////// 327 | 328 | ////////////////////////////////////////// 329 | // 天气图标 330 | const weatherStack = env.alignRightStack(rightStack); 331 | weatherStack.bottomAlignContent(); 332 | // 缓存目录 333 | const weatherImgCachePath = fm.joinPath( 334 | fm.documentsDirectory(), 335 | "weatherImg-cache" 336 | ); 337 | let weatherImg = undefined; 338 | try { 339 | weatherImg = await env.getImage(weatherInfo.weatherIco); 340 | fm.writeImage(weatherImgCachePath, weatherImg); 341 | log(`天气图标写入缓存`); 342 | } catch (e) { 343 | weatherImg = fm.readImage(weatherImgCachePath); 344 | log(`读取天气图标缓存`); 345 | } 346 | 347 | // 显示天气 348 | imgStyle.stack = weatherStack; 349 | imgStyle.width = 35; 350 | imgStyle.height = 35; 351 | imgStyle.img = weatherImg; 352 | env.addStyleImg(); 353 | // 体感温度 354 | weatherStack.addSpacer(4); 355 | const bodyFeelingTemperature = weatherInfo.bodyFeelingTemperature; 356 | // 显示体感温度 357 | textStyle.stack = weatherStack; 358 | textStyle.text = `${bodyFeelingTemperature}°C`; 359 | textStyle.lineLimit = 1; 360 | textStyle.font = Font.boldMonospacedSystemFont(23); 361 | textStyle.textColor = defaultTextColor; 362 | env.addStyleText(); 363 | ////////////////////////////////////////// 364 | 365 | // 相对湿度 366 | if (weatherControl.HUMIDITY) { 367 | rightStack.addSpacer(4); 368 | const humidityStack = env.alignRightStack(rightStack); 369 | // 显示 370 | textStyle.stack = humidityStack; 371 | textStyle.text = `相对湿度:${weatherInfo.humidity}`; 372 | textStyle.lineLimit = 1; 373 | textStyle.font = Font.systemFont(11); 374 | textStyle.textColor = defaultTextColor; 375 | env.addStyleText(); 376 | } 377 | 378 | ////////////////////////////////////////// 379 | // 舒适指数 380 | if (weatherControl.COMFORT) { 381 | rightStack.addSpacer(1); 382 | const comfortStack = env.alignRightStack(rightStack); 383 | // 显示 384 | textStyle.stack = comfortStack; 385 | textStyle.text = `舒适指数:${weatherInfo.comfort}`; 386 | textStyle.lineLimit = 1; 387 | textStyle.font = Font.systemFont(11); 388 | textStyle.textColor = defaultTextColor; 389 | env.addStyleText(); 390 | } 391 | 392 | ////////////////////////////////////////// 393 | // 紫外线指数 394 | if (weatherControl.ULTRAVIOLET) { 395 | rightStack.addSpacer(1); 396 | const ultravioletStack = env.alignRightStack(rightStack); 397 | // 显示 398 | textStyle.stack = ultravioletStack; 399 | textStyle.text = `紫外线:${weatherInfo.ultraviolet}`; 400 | textStyle.lineLimit = 1; 401 | textStyle.font = Font.systemFont(11); 402 | textStyle.textColor = defaultTextColor; 403 | env.addStyleText(); 404 | } 405 | 406 | ////////////////////////////////////////// 407 | // 空气质量 408 | if (weatherControl.AQI) { 409 | rightStack.addSpacer(1); 410 | const aqiInfoStack = env.alignRightStack(rightStack); 411 | // 显示 412 | textStyle.stack = aqiInfoStack; 413 | textStyle.marginStart = 8; 414 | textStyle.text = `空气质量:${weatherInfo.aqiInfo}`; 415 | textStyle.lineLimit = 1; 416 | textStyle.font = Font.systemFont(11); 417 | textStyle.textColor = defaultTextColor; 418 | env.addStyleText(); 419 | } 420 | 421 | ////////////////////////////////////////// 422 | // 高低温 423 | if (weatherControl.HEIGHT_LOW) { 424 | const minTemperature = weatherInfo.minTemperature; 425 | const maxTemperature = weatherInfo.maxTemperature; 426 | // 右对齐 427 | rightStack.addSpacer(3); 428 | const tempStack = env.alignRightStack(rightStack); 429 | // 显示箭头 430 | textStyle.stack = tempStack; 431 | textStyle.text = `↑`; 432 | textStyle.lineLimit = 1; 433 | textStyle.font = Font.systemFont(10); 434 | textStyle.textColor = new Color("ff0000"); 435 | env.addStyleText(); 436 | // 高温温度 437 | tempStack.addSpacer(2); 438 | textStyle.stack = tempStack; 439 | textStyle.text = `${weatherInfo.maxTemperature}°`; 440 | textStyle.lineLimit = 1; 441 | textStyle.font = Font.systemFont(10); 442 | textStyle.textColor = defaultTextColor; 443 | env.addStyleText(); 444 | // 低温箭头 445 | tempStack.addSpacer(6); 446 | textStyle.stack = tempStack; 447 | textStyle.text = `↓`; 448 | textStyle.lineLimit = 1; 449 | textStyle.font = Font.systemFont(10); 450 | textStyle.textColor = new Color("2bae85"); 451 | env.addStyleText(); 452 | // 低温温度 453 | tempStack.addSpacer(2); 454 | textStyle.stack = tempStack; 455 | textStyle.text = `${weatherInfo.minTemperature}°`; 456 | textStyle.lineLimit = 1; 457 | textStyle.font = Font.systemFont(10); 458 | textStyle.textColor = defaultTextColor; 459 | env.addStyleText(); 460 | } 461 | 462 | ////////////////////////////////////////// 463 | // 日出 464 | if (weatherControl.SUNRISE_SUNSET) { 465 | let symbolStack = rightStack.addStack(); 466 | symbolStack.layoutHorizontally(); 467 | symbolStack.addSpacer(); 468 | symbolStack.bottomAlignContent(); 469 | // 添加日出图标 470 | let sunriseImg = SFSymbol.named("sunrise.fill").image; 471 | imgStyle.stack = symbolStack; 472 | imgStyle.width = 15; 473 | imgStyle.height = 15; 474 | imgStyle.img = sunriseImg; 475 | env.addStyleImg(); 476 | symbolStack.addSpacer(4); 477 | // 日出时间 / 样式 478 | textStyle.stack = symbolStack; 479 | textStyle.text = weatherInfo.sunrise; 480 | textStyle.lineLimit = 1; 481 | textStyle.font = Font.systemFont(10); 482 | textStyle.textColor = defaultTextColor; 483 | env.addStyleText(); 484 | //***********************// 485 | // 日落 486 | symbolStack.addSpacer(6); 487 | // 添加日落图标 488 | let sunsetImg = SFSymbol.named("sunset.fill").image; 489 | imgStyle.stack = symbolStack; 490 | imgStyle.width = 15; 491 | imgStyle.height = 15; 492 | imgStyle.img = sunsetImg; 493 | env.addStyleImg(); 494 | symbolStack.addSpacer(4); 495 | // 日落时间 / 样式 496 | textStyle.stack = symbolStack; 497 | textStyle.text = weatherInfo.sunset; 498 | textStyle.lineLimit = 1; 499 | textStyle.font = Font.systemFont(10); 500 | textStyle.textColor = defaultTextColor; 501 | env.addStyleText(); 502 | } 503 | 504 | ////////////////////////////////////////// 505 | // 天气更新时间 506 | if (weatherControl.UPDATE_TIME) { 507 | // 更新时间 508 | rightStack.addSpacer(3); 509 | const updateTimeStack = env.alignRightStack(rightStack); 510 | textStyle.stack = updateTimeStack; 511 | textStyle.text = `上次更新 → ${getDateStr(new Date(), "HH:mm")}`; 512 | textStyle.lineLimit = 1; 513 | textStyle.font = Font.systemFont(8); 514 | textStyle.textColor = new Color("ffffff", 0.8); 515 | env.addStyleText(); 516 | } 517 | 518 | /*****************************小组件内容ENd*****************************/ 519 | 520 | /* 521 | ************************************** 522 | * 获取彩云天气 523 | ************************************** 524 | */ 525 | async function getWeather() { 526 | // 缓存目录 527 | const weatherCachePath = fm.joinPath( 528 | fm.documentsDirectory(), 529 | "weather-caiyun-cache" 530 | ); 531 | // 天气数据 532 | let weatherInfo = {}; 533 | const location = await getLocation(); 534 | log("定位信息:" + location.locality + "·" + location.subLocality); 535 | 536 | // 彩云天气域名 537 | const DOMAIN = `https://api.caiyunapp.com/v2.5/${apiKey}/${location.longitude},${location.latitude}/weather.json?alert=true`; 538 | let weatherJsonData = undefined; 539 | try { 540 | weatherJsonData = await env.getJson(DOMAIN); 541 | log("天气数据请求成功,进行缓存。"); 542 | } catch (e) { 543 | const cache = fm.readString(weatherCachePath); 544 | log("读取彩云天气缓存数据。"); 545 | weatherJsonData = JSON.parse(cache); 546 | } 547 | 548 | if (weatherJsonData.status == "ok") { 549 | // 写入缓存 550 | fm.writeString(weatherCachePath, JSON.stringify(weatherJsonData)); 551 | 552 | // 天气突发预警 553 | const alertWeatherTitle = weatherJsonData.result.alert.content.title; 554 | log("突发的天气预警==>" + alertWeatherTitle); 555 | weatherInfo.alertWeatherTitle = alertWeatherTitle; 556 | 557 | // 温度范围 558 | const temperatureData = weatherJsonData.result.daily.temperature[0]; 559 | // 最低温度 560 | const minTemperature = temperatureData.min; 561 | // 最高温度 562 | const maxTemperature = temperatureData.max; 563 | log("温度==>" + minTemperature + "|" + maxTemperature); 564 | weatherInfo.minTemperature = Math.round(minTemperature); 565 | weatherInfo.maxTemperature = Math.round(maxTemperature); 566 | 567 | // 体感温度 568 | const bodyFeelingTemperature = 569 | weatherJsonData.result.realtime.apparent_temperature; 570 | log("体感温度==>" + bodyFeelingTemperature); 571 | weatherInfo.bodyFeelingTemperature = Math.round(bodyFeelingTemperature); 572 | 573 | // 天气状况 weatherIcos[weatherIco] 574 | let weather = weatherJsonData.result.realtime.skycon; 575 | log("天气状况==>" + weather + "|" + weatherIcos[weather]); 576 | if (hour - 12 >= 7 && weather == "CLOUDY") { 577 | weather = "CLOUDY_NIGHT"; 578 | } 579 | weatherInfo.weatherIco = weatherIcos[weather]; 580 | 581 | // 天气描述 582 | const weatherDesc = weatherJsonData.result.forecast_keypoint; 583 | log("天气描述==>" + weatherDesc); 584 | weatherInfo.weatherDesc = weatherDesc.replace("。还在加班么?", ","); 585 | 586 | // 相对湿度 587 | const humidity = 588 | Math.round(weatherJsonData.result.realtime.humidity * 100) + "%"; 589 | log("相对湿度==>" + weatherJsonData.result.realtime.humidity); 590 | weatherInfo.humidity = addSpace(humidity); 591 | 592 | // 舒适指数 593 | const comfort = weatherJsonData.result.realtime.life_index.comfort.desc; 594 | log("舒适指数==>" + comfort); 595 | weatherInfo.comfort = addSpace(comfort); 596 | 597 | // 紫外线指数 598 | const ultraviolet = 599 | weatherJsonData.result.realtime.life_index.ultraviolet.desc; 600 | log("紫外线指数==>" + ultraviolet); 601 | weatherInfo.ultraviolet = addSpace(ultraviolet); 602 | 603 | // 空气质量 604 | const aqi = weatherJsonData.result.realtime.air_quality.aqi.chn; 605 | const aqiInfo = airQuality(aqi); 606 | log("空气质量==>" + aqiInfo); 607 | weatherInfo.aqiInfo = aqiInfo; 608 | 609 | // 日出日落 610 | const astro = weatherJsonData.result.daily.astro[0]; 611 | // 日出 612 | const sunrise = astro.sunrise.time; 613 | // 日落 614 | const sunset = astro.sunset.time; 615 | log("日出==>" + sunrise + ",日落==>" + sunset); 616 | weatherInfo.sunrise = sunrise.toString(); 617 | weatherInfo.sunset = sunset.toString(); 618 | } 619 | 620 | return weatherInfo; 621 | } 622 | 623 | /* 624 | ************************************** 625 | * 空气质量指标 626 | ************************************** 627 | */ 628 | function airQuality(levelNum) { 629 | // 0-50 优,51-100 良,101-150 轻度污染,151-200 中度污染 630 | // 201-300 重度污染,>300 严重污染 631 | if (levelNum >= 0 && levelNum <= 50) { 632 | return "优秀"; 633 | } else if (levelNum >= 51 && levelNum <= 100) { 634 | return "良好"; 635 | } else if (levelNum >= 101 && levelNum <= 150) { 636 | return "轻度"; 637 | } else if (levelNum >= 151 && levelNum <= 200) { 638 | return "中度"; 639 | } else if (levelNum >= 201 && levelNum <= 300) { 640 | return "重度"; 641 | } else { 642 | return "严重"; 643 | } 644 | } 645 | 646 | /* 647 | ************************************** 648 | * 获取定位 649 | ************************************** 650 | */ 651 | async function getLocation() { 652 | if (!lockLocation) { 653 | locationData = env.getLocation(); 654 | } 655 | 656 | return locationData; 657 | } 658 | 659 | /* 660 | ************************************** 661 | * 日程筛选 662 | ************************************** 663 | */ 664 | function shouldShowSchedule(schedule) { 665 | const currentDate = new Date(); 666 | // 被取消的日程不用显示 667 | if (schedule.title.startsWith("Canceled:")) { 668 | return false; 669 | } 670 | // 与当前时间做比较 671 | let timeInterval = schedule.startDate.getTime() > currentDate.getTime(); 672 | // 返回全天跟还没过去的 673 | return timeInterval || schedule.isAllDay; 674 | } 675 | 676 | /* 677 | ************************************** 678 | * 日程列表 679 | ************************************** 680 | */ 681 | async function getSchedules() { 682 | let showSchedules = []; 683 | const todaySchedules = await CalendarEvent.today([]); 684 | for (const schedule of todaySchedules) { 685 | if (shouldShowSchedule(schedule)) { 686 | // 日程 687 | let scheduleObj = {}; 688 | // 开始时间 689 | const startDate = schedule.startDate; 690 | // 开始小时 691 | const startHour = ("0" + startDate.getHours()).slice(-2); 692 | // 开始分钟 693 | const startMinute = ("0" + startDate.getMinutes()).slice(-2); 694 | 695 | // 结束时间 696 | const endDate = schedule.endDate; 697 | // 结束小时 698 | const endHour = ("0" + endDate.getHours()).slice(-2); 699 | // 结束分钟 700 | const endMinute = ("0" + endDate.getMinutes()).slice(-2); 701 | 702 | // 时间安排展示 703 | let timeText = 704 | "▷" + startHour + ":" + startMinute + "→" + endHour + ":" + endMinute; 705 | if (schedule.isAllDay) { 706 | timeText = "▷全天"; 707 | } 708 | 709 | // 构造格式后的日程 710 | scheduleObj.title = schedule.title; 711 | scheduleObj.timeText = timeText; 712 | log(">>日程:" + scheduleObj.title + "==>" + timeText); 713 | showSchedules.push(scheduleObj); 714 | } 715 | } 716 | 717 | return showSchedules; 718 | } 719 | 720 | /* 721 | ************************************** 722 | * 获取电池信息 723 | ************************************** 724 | */ 725 | function getBatteryLevel() { 726 | const batteryLevel = Device.batteryLevel(); 727 | const batteryAscii = `${Math.round(batteryLevel * 100)}%`; 728 | log("电池==>" + batteryAscii); 729 | return batteryAscii; 730 | } 731 | 732 | /* 733 | ************************************** 734 | * 在线获取农历信息 735 | ************************************** 736 | */ 737 | async function getLunar() { 738 | // 缓存目录 739 | const lunarCachePath = fm.joinPath(fm.documentsDirectory(), "lunar-cache"); 740 | 741 | let dateString = getDateStr(new Date(), "yyyy-MM-dd"); 742 | const url = `http://calendar.netcore.show/api/day/days?day=${dateString}`; 743 | let data = undefined; 744 | 745 | try { 746 | data = await env.getJson(url); 747 | // 缓存数据 748 | fm.writeString(lunarCachePath, JSON.stringify(data)); 749 | log("农历信息请求成功,进行缓存。"); 750 | } catch (e) { 751 | const cache = fm.readString(lunarCachePath); 752 | log("读取农历缓存数据。"); 753 | data = JSON.parse(cache); 754 | } 755 | 756 | return data; 757 | } 758 | 759 | /* 760 | ************************************** 761 | * 在线获取今日诗词 762 | ************************************** 763 | */ 764 | async function getPoetry() { 765 | // 缓存目录 766 | const poetryCachePath = fm.joinPath(fm.documentsDirectory(), "poetry-cache"); 767 | let data = undefined; 768 | 769 | try { 770 | data = await env.getJson("https://v2.jinrishici.com/sentence"); 771 | // 缓存数据 772 | fm.writeString(poetryCachePath, JSON.stringify(data)); 773 | log("今日诗词请求成功,进行缓存。"); 774 | } catch (e) { 775 | const cache = fm.readString(poetryCachePath); 776 | log("读取今日诗词缓存数据。"); 777 | data = JSON.parse(cache); 778 | } 779 | 780 | return data; 781 | } 782 | 783 | /* 784 | ************************************** 785 | * 格式化时间 786 | ************************************** 787 | */ 788 | function getDateStr(date, formatter = "yyyy年MM月d日 EEE") { 789 | let df = new DateFormatter(); 790 | df.locale = locale; 791 | df.dateFormat = formatter; 792 | return df.string(date); 793 | } 794 | 795 | /* 796 | ************************************** 797 | * 添加空格以对齐 798 | ************************************** 799 | */ 800 | function addSpace(str) { 801 | let data = str; 802 | if (data.length == 1) { 803 | data = "\x20\x20\x20\x20" + data; 804 | } 805 | return data; 806 | } 807 | 808 | /* 809 | ************************************** 810 | * 按照时间获取问候语 811 | ************************************** 812 | */ 813 | function provideGreeting(date) { 814 | const hour = date.getHours(); 815 | if (hour < 5) { 816 | return greetingText.nightGreeting; 817 | } 818 | if (hour < 11) { 819 | return greetingText.morningGreeting; 820 | } 821 | if (hour >= 11 && hour - 12 <= 1) { 822 | return greetingText.noonGreeting; 823 | } 824 | if (hour - 12 < 7) { 825 | return greetingText.afternoonGreeting; 826 | } 827 | if (hour - 12 < 10) { 828 | return greetingText.eveningGreeting; 829 | } 830 | return greetingText.nightGreeting; 831 | } 832 | 833 | //------------------------------------------------ 834 | //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑内容区↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 835 | 836 | //------------------------------------------------ 837 | // 运行脚本、预览 838 | await env.run(name, widget); 839 | //------------------------------------------------ -------------------------------------------------------------------------------- /「DJG」彩云天气.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: feather-alt; 4 | /* 5 | // *************************** 6 | // 环境框架 :@ DmYY 7 | // script :原作者Pih、LSP 由DJG修改 8 | */ 9 | 10 | FILE = FileManager.local() 11 | FILEPATH = FILE.joinPath(FILE.libraryDirectory(), "/DJG.js") 12 | const { DJG, Runing } = importModule(FILEPATH); 13 | 14 | // @组件代码开始 15 | class Widget extends DJG { 16 | constructor(arg) { 17 | super(arg); 18 | this.name = "彩云天气"; 19 | this.widget_ID = "DJG-116"; 20 | this.version = "V3.7"; 21 | this.logo = 'https://gitee.com/scriptxx_djg/imgebed/raw/master/menu/Imported_Image.png'; 22 | this.isPhone = Device.model() == "iPhone"; 23 | this.Run(); 24 | this.widgetConfigs = { 25 | greetingText: { 26 | nightGreeting: "该睡觉了~", morningGreeting: "早上好~", noonGreeting: "中午好~", 27 | afternoonGreeting: "下午好~", eveningGreeting: "晚上好~", 28 | }, 29 | anniversaryText: { 30 | "1-1": "年之伊始,万事如意~", "10-1": "国之庆典,普天同庆~", "12-25": "𝔐𝔢𝔯𝔯𝔶 ℭ𝔥𝔯𝔦𝔰𝔱𝔪𝔞𝔰~", 31 | }, 32 | lunarText: { 33 | "正月初一": "金牛贺岁迎新春~", "正月初二": "喜迎财神福满门~", "正月初三": "赤狗小年朝~", 34 | }, 35 | // 自定义天气对应的icon  36 | weather: { 37 | CLEAR_DAY: '晴', CLEAR_NIGHT: '晴', PARTLY_CLOUDY_DAY: '多云', 38 | PARTLY_CLOUDY_NIGHT: '多云', CLOUDY: '阴', CLOUDY_NIGHT: '阴', 39 | LIGHT_HAZE: '轻度雾霾', LIGHT_HAZE_NIGHT: '轻度雾霾', MODERATE_HAZE: '中度雾霾', 40 | MODERATE_HAZE_NIGHT: '中度雾霾', HEAVY_HAZE: '重度雾霾', HEAVY_HAZE_NIGHT: '重度雾霾', 41 | LIGHT_RAIN: '小雨', MODERATE_RAIN: '中雨', HEAVY_RAIN: '大雨', 42 | STORM_RAIN: '暴雨', FOG: '雾', LIGHT_SNOW: '小雪', 43 | MODERATE_SNOW: '中雪', HEAVY_SNOW: '大雪', STORM_SNOW: '暴雪', 44 | DUST: '浮尘', SAND: '沙尘', WIND: '大风' 45 | },// 自定义天气对应的icon  46 | weatherIcos: { 47 | CLEAR_DAY: "https://s3.ax1x.com/2020/12/08/rpVVhD.png", // 晴(白天) 48 | CLEAR_NIGHT: "https://s1.ax1x.com/2020/10/26/BukPhR.png", // 晴(夜间) 49 | PARTLY_CLOUDY_DAY: "https://s1.ax1x.com/2020/10/26/BuQHN6.png", // 多云(白天) 50 | PARTLY_CLOUDY_NIGHT: "https://s1.ax1x.com/2020/10/26/BukcbF.png", // 多云(夜间) 51 | CLOUDY: "https://s3.ax1x.com/2020/12/10/ripz8J.png", // 阴(白天) 52 | CLOUDY_NIGHT: "https://s3.ax1x.com/2020/12/10/ripz8J.png", // 阴(夜间) 53 | LIGHT_HAZE: "https://s3.ax1x.com/2021/01/15/s009Mj.png", // 轻度雾霾 54 | LIGHT_HAZE_NIGHT: "https://s3.ax1x.com/2021/01/15/s00dOA.png", // 轻度雾霾 55 | MODERATE_HAZE: "https://s3.ax1x.com/2021/01/15/s009Mj.png", // 中度雾霾 56 | MODERATE_HAZE_NIGHT: "https://s3.ax1x.com/2021/01/15/s00dOA.png", // 中度雾霾 57 | HEAVY_HAZE: "https://s3.ax1x.com/2021/01/15/s009Mj.png", // 重度雾霾 58 | HEAVY_HAZE_NIGHT: "https://s3.ax1x.com/2021/01/15/s00dOA.png", // 重度雾霾 59 | LIGHT_RAIN: "https://s3.ax1x.com/2020/12/15/rMkQVx.png", // 小雨 60 | MODERATE_RAIN: "https://s3.ax1x.com/2020/12/15/rMkBIf.png", // 中雨 61 | HEAVY_RAIN: "https://s3.ax1x.com/2020/12/15/rMk6zQ.png", // 大雨 62 | STORM_RAIN: "https://s3.ax1x.com/2020/12/15/rMk6zQ.png", // 暴雨 63 | 64 | FOG: "https://s3.ax1x.com/2020/12/15/rMAYkV.png", // 雾 65 | LIGHT_SNOW: "https://s3.ax1x.com/2020/12/15/rMActK.png", // 小雪 66 | MODERATE_SNOW: "https://s3.ax1x.com/2020/12/15/rMActK.png", // 中雪 67 | HEAVY_SNOW: "https://s3.ax1x.com/2020/12/15/rMActK.png", // 大雪 68 | STORM_SNOW: "https://s3.ax1x.com/2020/12/15/rMActK.png", // 暴雪 69 | DUST: "https://s3.ax1x.com/2020/12/08/rpupes.png", // 浮尘 70 | SAND: "https://s3.ax1x.com/2020/12/08/rpupes.png", // 沙尘 71 | WIND: "https://s3.ax1x.com/2020/12/15/rMEeBR.png", // 大风 72 | }, 73 | // 底部的小图标 74 | lovelyImgArr: [ 75 | "https://s3.ax1x.com/2021/01/16/sDrPeJ.png", 76 | "https://s3.ax1x.com/2021/01/16/sDrFoR.png", 77 | "https://s3.ax1x.com/2021/01/16/sDriw9.png", 78 | "https://s3.ax1x.com/2021/01/16/sDr9L4.png", 79 | "https://s3.ax1x.com/2021/01/16/sDrpyF.png", 80 | "https://s3.ax1x.com/2021/01/16/sDrAF1.png", 81 | "https://s3.ax1x.com/2021/01/16/sDrEJx.png", 82 | "https://s3.ax1x.com/2021/01/16/sDrVW6.png", 83 | ], 84 | } 85 | } 86 | /** 87 | * 渲染函数,函数名固定 88 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 89 | */ 90 | async render () { 91 | let widget = this.getWidget(); 92 | await this.getWidgetBackgroundImage(widget); 93 | if (!this.settings.caiyunKEY) { 94 | if(config.runsInWidget) return await this.renderAlert('需要申请彩云天气key'); 95 | return await this.inputKey(); 96 | } 97 | if(!this.settings.choiceAction) { 98 | this.settings.choiceAction = 'a'; 99 | } 100 | try{ 101 | switch (this.widgetFamily) { 102 | case 'small': 103 | await this.renderSmall(widget); 104 | break; 105 | case 'medium': 106 | if(this.settings.choiceAction == 'a'){ 107 | await this.renderMedium(widget); 108 | } else { 109 | await this.renderMedium2(widget); 110 | } 111 | break; 112 | default: 113 | return await this.renderAlert(); 114 | } 115 | }catch(e){ 116 | this.ERROR.push({error:e.toString()}); 117 | } 118 | return widget; 119 | } 120 | 121 | async http_get (url, json = true, useCache = true, options = null, method = 'GET') { 122 | let cacheKey = this.hash('caiyunWeather'+this.settings.caiyunKEY); 123 | let cacheData = null, error = null; 124 | if (this.isUpdate(cacheKey.slice(-8), useCache) || !Keychain.contains(cacheKey)){ 125 | try { 126 | let req = new Request(url) 127 | req.method = method 128 | if(options){ 129 | Object.keys(options).forEach((key) => { 130 | req[key] = options[key]; 131 | }); 132 | } 133 | cacheData = await (json ? req.loadJSON() : req.loadString()); 134 | } catch (e) { 135 | error = {url:url, error:e.toString()}; 136 | this.writeError(error); 137 | }; 138 | } 139 | if(cacheData && useCache) { 140 | this.saveCacheKey(cacheKey); 141 | Keychain.set(cacheKey, json ? JSON.stringify(cacheData) : cacheData) 142 | }else if (!cacheData && Keychain.contains(cacheKey)) { 143 | let cache = Keychain.get(cacheKey) 144 | cacheData = json ? JSON.parse(cache) : cache 145 | }else {this.ERROR.push(error);} 146 | return cacheData; 147 | } 148 | 149 | async setWeather(){ 150 | let key = this.settings.caiyunKEY; 151 | let weather = {}; 152 | const locData = await this.getLocation(); 153 | const lon = locData.location.longitude; 154 | const lat = locData.location.latitude; 155 | const caiyunUrl = `https://api.caiyunapp.com/v2.5/${key}/${lon},${lat}/weather.json`; 156 | const caiyun = await this.http_get(caiyunUrl); 157 | if(caiyun.status == 'ok') { 158 | weather.alertInfo = caiyun.result.minutely.description; // 天气提醒 159 | weather.weatherDesc = caiyun.result.hourly.description; // 天气提醒 160 | weather.dailyTemperature = caiyun.result.daily.temperature; // 未来几天温度 161 | weather.data = caiyun.result.hourly.temperature; // 未来24小时温度 162 | weather.hourlySky = caiyun.result.hourly.skycon; // 未来24小时天气 163 | weather.Mainweather = caiyun.result.daily.skycon; // 未来五天天气 164 | weather.CHNAQI = caiyun.result.realtime.air_quality.aqi.chn; // 当前空气质量数值 165 | weather.feelslikeT = parseInt(caiyun.result.realtime.temperature); // 当前温度 166 | weather.realtimeweather = caiyun.result.realtime.skycon; // 当前天气 167 | weather.comfort = caiyun.result.realtime.life_index.comfort.desc; // 当前指数 168 | } else { 169 | this.ERROR.push({error:caiyun.error}); 170 | } 171 | 172 | let city = locData.postalAddress.city; 173 | let district = locData.postalAddress.subLocality || locData.postalAddress.street; 174 | if (district.indexOf(city) != -1) { 175 | district = district.split(city)[1] || "未知"; 176 | } 177 | 178 | weather.city = [city, district]; // 当前位置 179 | return weather; 180 | } 181 | // 小组件 182 | async renderSmall(w){ 183 | w.setPadding(0, 0, 0, 0) 184 | // 获取天气数据 185 | const wea = await this.setWeather(); 186 | 187 | const Drawing = this.makeCanvas(130, 135); 188 | // 当前天气 189 | this.drawIcon(Drawing, 4, 0, wea.realtimeweather,42) 190 | this.drawText(Drawing, 73, 5, 60, 16, this.widgetConfigs.weather[wea.realtimeweather], "regular",15,"left") 191 | this.drawText(Drawing, 73, 25, 30, 15, wea.feelslikeT.toString()+'°C', "regular",13,"left") 192 | 193 | // 空气质量颜色以及程度 194 | let AQIcolor, ac 195 | // 选择 AQI 数据 USAQI or CHNAQI 196 | let AQIData = wea.CHNAQI 197 | if (AQIData<=50){ ac = ["00e400",'优'] 198 | }else if (AQIData<=100){ ac = ["f8c50a",'良'] 199 | }else if (AQIData<=150){ ac = ["ff7e00",'轻度'] 200 | }else if (AQIData<=200){ ac = ["ff0000",'中度'] 201 | }else if (AQIData<=300){ ac = ["ba0033",'重度'] 202 | }else{ ac = ["7e0023",'严重']} 203 | AQIcolor = new Color(ac[0],1) 204 | // ######右侧底色####### 205 | const x = 10 206 | this.fillRect(Drawing, 73, 45, 45, 12, 4, AQIcolor) 207 | let des = ac[1] 208 | des = des.length > 2 ? des.slice(0, 2) : des 209 | this.drawText(Drawing, 76, 46, 55, 10, AQIData + " - " + des, "semibold",8,"left", new Color("ffffff")); 210 | // 线 211 | this.fillRect(Drawing, 0, 66, 130, 0.4, 1, this._widgetColor(0.7)) 212 | 213 | const dailydata = wea.dailyTemperature 214 | const df = new DateFormatter() 215 | df.locale = "zh_cn" 216 | for(let i = 0; i < 3; i++){ 217 | // 每日日期 218 | const weatherDate = new Date() 219 | weatherDate.setDate(weatherDate.getDate() + i + 1) 220 | df.dateFormat = "E" 221 | this.drawText(Drawing, 30*i+i*16+9, 74, 25,11, df.string(weatherDate),"bold",10,"center",this._widgetColor(0.8)) 222 | // 图标 223 | this.drawIcon(Drawing, 30*i+i*16+8, 88, wea.Mainweather[i+1].value, 20) 224 | // 每日温度+"º" 225 | let dMax = Math.round(dailydata[i+1].max).toString() 226 | let dMin = Math.round(dailydata[i+1].min).toString() 227 | this.drawText(Drawing,30*i+i*16,120, 40,11,`${dMin}°/${dMax}°`,"regular",9,"center",this._widgetColor(0.8)) 228 | } 229 | const contentStack = w.addStack() 230 | contentStack.size = new Size(130, 135) 231 | contentStack.addImage(Drawing.getImage()); 232 | } 233 | 234 | // 中组件2 235 | async renderMedium2(w){ 236 | w.setPadding(0, 0, 0, 0); 237 | // 当前日期 238 | //const currentDate = new Date() 239 | let contentStack = w.addStack() 240 | contentStack.layoutVertically() 241 | // 整体内容居中对齐 242 | contentStack.centerAlignContent() 243 | // 获取天气数据 244 | const weatherInfo = await this.setWeather() 245 | // 获取农历信息 246 | const lunarInfo = this.getLunar(); 247 | //>>>>>1 248 | contentStack.addSpacer(10) 249 | const titleStack = contentStack.addStack() 250 | titleStack.layoutHorizontally() 251 | titleStack.centerAlignContent() 252 | titleStack.addSpacer(); 253 | // 天气Icon 254 | let weatherImg = await this.getImageByUrl(this.widgetConfigs.weatherIcos[weatherInfo.realtimeweather]); 255 | // 显示天气 256 | this.addImage(titleStack, weatherImg, {w:23, h:23}) 257 | titleStack.addSpacer(8) 258 | let temperatureTips = `${weatherInfo.feelslikeT}°` 259 | // 显示温度 260 | this.addText(titleStack, temperatureTips, 17); 261 | titleStack.addSpacer(8) 262 | // 天气描述 263 | const weatherDesc = this.widgetConfigs.weather[weatherInfo.realtimeweather] 264 | // 问候语获取内容 265 | const greeting = await this.provideGreeting(); 266 | // 添加显示 267 | this.addText(titleStack, `${weatherDesc} • ${greeting}`, 15, {font:'IowanOldStyle-Bold', lineLimit:1}) 268 | titleStack.addSpacer() 269 | 270 | // 年月日周 271 | contentStack.addSpacer(8); 272 | const dateStack = contentStack.addStack() 273 | dateStack.layoutHorizontally() 274 | dateStack.centerAlignContent() 275 | dateStack.addSpacer() 276 | const dateStr = this.getDateStr("M月d日 EEE") 277 | // 农历信息 278 | const infoLunarText = lunarInfo.lunarMonthCn + lunarInfo.lunarDayCn 279 | const holidayText = lunarInfo.lunarYearCn; 280 | let dateFullText = `${dateStr} ⊙ ${infoLunarText} ⊙ ${holidayText}`; 281 | // 显示 282 | this.addText(dateStack, dateFullText, 13, {color:'FF7F00', opacity:0.8, lineLimit:1}) 283 | dateStack.addSpacer(); 284 | 285 | contentStack.addSpacer(8) 286 | const weatherTipsStack = contentStack.addStack() 287 | weatherTipsStack.layoutHorizontally() 288 | weatherTipsStack.centerAlignContent() 289 | weatherTipsStack.addSpacer() 290 | 291 | // 天气预警、预告信息 292 | const weatherAlertInfo = weatherInfo.alertInfo 293 | let weatherMessge = weatherInfo.weatherDesc 294 | if (weatherAlertInfo != undefined) { 295 | weatherMessge = weatherAlertInfo 296 | } 297 | // 添加显示天气预告信息 298 | const tipText = `Φ ${weatherMessge} ⊙ ${weatherInfo.comfort} Φ`; 299 | this.addText(weatherTipsStack, tipText, 13, {opacity:0.95, lineLimit:1}) 300 | weatherTipsStack.addSpacer() 301 | //>>>>>4 302 | contentStack.addSpacer(8) 303 | const infoStack = contentStack.addStack(); 304 | infoStack.layoutHorizontally() 305 | infoStack.centerAlignContent() 306 | infoStack.addSpacer() 307 | // 添加背景阴影 308 | infoStack.backgroundColor = new Color('666', 0.2) 309 | infoStack.cornerRadius = 4 310 | infoStack.setPadding(6, 6, 6, 6) 311 | // 一言 312 | let contentInfo = await this.getOneWord() 313 | this.addText(infoStack, contentInfo, 12, {font:'lightMonospaced', opacity:0.9, lineLimit:1}) 314 | infoStack.addSpacer() 315 | // 图标大小 316 | const iconSize = new Size(18, 18) 317 | const spacer = 8 318 | // 图标边距 319 | const iconMargin = 10 320 | contentStack.addSpacer(spacer) 321 | let updateStack = contentStack.addStack() 322 | updateStack.layoutHorizontally() 323 | updateStack.centerAlignContent() 324 | updateStack.addSpacer() 325 | // 显示底部图标栏 326 | const lovelyImgArr = this.widgetConfigs.lovelyImgArr 327 | for(let i = 0; i < 8; i++){ 328 | let lovelyImg = await this.getImageByUrl(lovelyImgArr[i]) 329 | this.addImage(updateStack, lovelyImg, {w:18,h:18}) 330 | if(i == 3){ 331 | updateStack.addSpacer(iconMargin) 332 | this.addText(updateStack, `${this.getDateStr("HH:mm")}更新`, 11, {opacity:0.8, lineLimit:1}) 333 | } 334 | if(i != 7) updateStack.addSpacer(iconMargin) 335 | } 336 | updateStack.addSpacer() 337 | contentStack.addSpacer(10) 338 | } 339 | 340 | // 中组件 341 | async renderMedium(widget){ 342 | widget.setPadding(10, 10, 8, 10) 343 | 344 | const body = widget.addStack() 345 | body.layoutVertically() 346 | 347 | const contentStack = body.addStack() 348 | 349 | const wea = await this.setWeather(); 350 | // 左边画布 351 | const leftDrawing = this.isPhone ? this.makeCanvas(355-10, 255) : this.makeCanvas(355, 255); 352 | await this.setLeftDraw(leftDrawing, wea); 353 | 354 | const leftStack = contentStack.addStack() 355 | leftStack.addImage(leftDrawing.getImage()) 356 | // 右边画布 357 | const rightDrawing = this.isPhone ? this.makeCanvas(642-355, 255-10) : this.makeCanvas(642-345, 255-13.5); 358 | await this.setRightDraw(rightDrawing, wea); 359 | 360 | const rightStack = contentStack.addStack() 361 | rightStack.addImage(rightDrawing.getImage()) 362 | 363 | const bottomDrawing = this.makeCanvas(642, 25); 364 | 365 | // 如果没有预警信息,显示天气描述 366 | var content, alertTextColor 367 | if (wea.alertInfo == undefined){ 368 | content = wea.weatherDesc 369 | }else{ 370 | content = "注意:"+wea.alertInfo 371 | } 372 | this.drawText(bottomDrawing, 0, 3, 642, 25, content,"regular",21,"center") 373 | 374 | const bottomStack = body.addStack() 375 | bottomStack.addImage(bottomDrawing.getImage()) 376 | } 377 | 378 | async setLeftDraw(leftDrawing, data){ 379 | // 位置信息 380 | const city = data.city[0]; 381 | const district = data.city[1]; 382 | this.drawText(leftDrawing, 10, 5, 110, 28, city, "regular",25,"left") 383 | this.drawText(leftDrawing, 10, 36, 110, 28, district, "regular",25,"left") 384 | 385 | // 获取农历信息 386 | let lunarInfo = this.getLunar(); 387 | let date = this.getDateStr("yyyy年M月d日 EEE").split(' '); 388 | const lunar = lunarInfo.lunarMonthCn + lunarInfo.lunarDayCn; 389 | this.drawText(leftDrawing, 120, 5, 280, 28, date[0], "regular",25,"left") 390 | this.drawText(leftDrawing, 120, 36, 300, 28, date[1] + " " + lunar, "regular",25,"left") 391 | 392 | let futureEvents = await this.getSolarTerm(3); 393 | for (let i = 0; i < futureEvents.length; i++) { 394 | let event = futureEvents[i]; 395 | let eventColor = new Color(event.color.hex); 396 | this.fillRect(leftDrawing, 12, 82+i*61, 5, 48, 2, eventColor) 397 | // 标题 398 | const title = event.solarTerm; 399 | this.drawText(leftDrawing, 29, 80+i*62, 305, 26, title, "bold", 23, "left") 400 | 401 | // 格式化时间信息。 402 | let nowTime = this.getTime(); 403 | const timeLeft = Math.ceil((this.getTime(event.startTime)-nowTime)/(1000*60*60*24)); 404 | const eventSeconds = Math.floor(nowTime / 1000) - 978307200 + timeLeft*3600*24; 405 | const duration = Math.ceil((this.getTime(event.endDate)-this.getTime(event.startDate))/(1000*60*60*24)); 406 | 407 | // 事件时间提醒 408 | var timeText, eventTimeColor 409 | if (timeLeft==0||timeLeft<0){ 410 | eventTimeColor = new Color('FF7F00') 411 | timeText = "今天全天" 412 | } 413 | if (timeLeft == 1){ 414 | timeText = "明天全天" 415 | } 416 | if(timeLeft>1){ 417 | let startTime = event.startTime; 418 | let eee = this.getDateStr("EEE MMMd日", new Date(startTime)) 419 | timeText = (eee + " 在" + timeLeft + "天之后") 420 | eventTimeColor = this._widgetColor(0.8) 421 | }; 422 | this.drawText(leftDrawing, 29, 85+i*61+25, 305, 30, timeText, "medium", 20, "left", eventTimeColor); 423 | } 424 | } 425 | 426 | async setRightDraw(rightDrawing, wea){ 427 | // 背景调整 428 | const slipPosition = 370 429 | const daystoShow = 6; 430 | // ######数据设定####### 431 | const weatherDesc = wea.weatherDesc; // 天气提醒 432 | const dailyTemperature = wea.dailyTemperature; // 未来几天温度 433 | const data = wea.data; // 未来24小时温度 434 | const dailydata = dailyTemperature; // 未来几天温度 435 | const Mainweather = wea.Mainweather; // 未来五天天气 436 | const CHNAQI = wea.CHNAQI; // 未来五天空气质量数值 437 | let feelslikeT = wea.feelslikeT; // 未来24小时温度 438 | // 空气质量颜色以及程度 439 | let AQIcolor, ac 440 | // 选择 AQI 数据 USAQI or CHNAQI 441 | let AQIData = CHNAQI 442 | if (AQIData<=50){ ac = ["00e400",'优'] 443 | }else if (AQIData<=100){ ac = ["f8c50a",'良'] 444 | }else if (AQIData<=150){ ac = ["ff7e00",'轻度'] 445 | }else if (AQIData<=200){ ac = ["ff0000",'中度'] 446 | }else if (AQIData<=300){ ac = ["ba0033",'重度'] 447 | }else{ ac = ["7e0023",'严重']} 448 | AQIcolor = new Color(ac[0],1) 449 | 450 | // ######右侧底色####### 451 | const x = 10 452 | this.fillRect(rightDrawing, 522-350+x, 36, 90, 18, 6, AQIcolor) 453 | // 当前天气 454 | this.drawIcon(rightDrawing,x-5, 0, wea.realtimeweather,50) 455 | // 当前温度 456 | this.drawText(rightDrawing,420-355+x, 1, 100, 54, wea.feelslikeT.toString(), "regular",52,"center") 457 | // 空气质量&下雨概率 458 | var textColortoShow = new Color("ffffff", 0.8) 459 | // 显示长度截取 460 | let des = ac[1] 461 | des = des.length > 2 ? des.slice(0, 2) : des 462 | this.drawText(rightDrawing, 522-350+x, 36, 90, 17, AQIData + " - " + des, "semibold",15,"center", textColortoShow) 463 | // 温度条位置 464 | var tempHeight 465 | if (feelslikeT < Math.round(dailyTemperature[0].min)) { tempHeight = 8 } 466 | if (feelslikeT > Math.round(dailyTemperature[0].max)) { tempHeight = 90 } 467 | if (feelslikeT >= Math.round(dailyTemperature[0].min) && feelslikeT <= Math.round(dailyTemperature[0].max)) 468 | { tempHeight = (feelslikeT-Math.round(dailyTemperature[0].min))*82/(Math.round(dailyTemperature[0].max)-Math.round(dailyTemperature[0].min))+8 } 469 | // ######温度条####### 470 | this.fillRect(rightDrawing,522-350+x, 25, 90, 8, 4, this._widgetColor(0.2)) 471 | this.fillRect(rightDrawing,522-350+x, 25, tempHeight, 8, 4, this.widgetColor) 472 | // 今天最高最低温度 +"º" 473 | this.drawText(rightDrawing,522-350+x, 1, 45,18, Math.round(dailyTemperature[0].min).toString(), "semibold",18,"left") 474 | this.drawText(rightDrawing,566-350+x, 1, 45,18, Math.round(dailyTemperature[0].max).toString(), "semibold",18,"right") 475 | 476 | // ######天气预报####### 477 | const weatherDrawing = this.makeCanvas(642-355, (255-98)); 478 | 479 | const deltaX = (610-slipPosition)/(daystoShow*2) 480 | const firstPointtoLeft = slipPosition+deltaX 481 | 482 | const ToTop = (120-98) 483 | var min, max, diff; 484 | for(var i = 0; i max || max == undefined ? temp : max) 488 | } 489 | diff = max-min 490 | if (diff == 0) {diff= diff+1; max=max+0.3} 491 | 492 | for (i=0;i= Math.round(dailyTemperature[0].max)) 504 | { tempHeight = 40 } 505 | if (Math.round(data[i*2].value) >=Math.round(dailyTemperature[0].min) && Math.round(data[i*2].value) < Math.round(dailyTemperature[0].max)) 506 | tempHeight = (Math.round(data[i*2].value)-Math.round(dailyTemperature[0].min))*32/(Math.round(dailyTemperature[0].max)-Math.round(dailyTemperature[0].min))+8 507 | // ######温度条####### 508 | this.fillRect(rightDrawing, firstPointtoLeft-4+(2.24*i)*deltaX-361,93, 8, 40, 4, this._widgetColor(0.2)) 509 | this.fillRect(rightDrawing, firstPointtoLeft-4+(2.24*i)*deltaX-361,150-tempHeight-98+81, 8, tempHeight, 4, temeratureBarcolor) 510 | // 温度+"º" 511 | this.drawText(rightDrawing, firstPointtoLeft+deltaX*i*2.24-381,137, 40,20,Math.round(data[i*2].value).toString(),"regular",17,"center",temperaturetextcolor) 512 | // 时间 513 | let weathertimeText = data[i*2].datetime.slice(11, 13) 514 | let zero = weathertimeText.slice(0, 1) 515 | weathertimeText = zero == 0 ? weathertimeText.replace("0", "") : weathertimeText 516 | if (i==0) {weathertimeText="现在"} 517 | else { weathertimeText = weathertimeText + "时"} 518 | this.drawText(rightDrawing, firstPointtoLeft+deltaX*i*2.28-382, 70, 40, 30, weathertimeText,"regular",17,"center",temperaturetextcolor) 519 | } 520 | 521 | const df = new DateFormatter() 522 | df.locale = "zh_cn" 523 | // ####每日预报######## 524 | for (i=1;i<4;i++){ 525 | // 图标 526 | this.drawIcon(rightDrawing,96*(i-1)+45-3, 162, Mainweather[i].value, 31 ) 527 | // 每日温度+"º" 528 | let dMax = Math.round(dailydata[i].max).toString() 529 | let dMin = Math.round(dailydata[i].min).toString() 530 | this.fillRect(rightDrawing,96*(i-1)+7,208, 73, 4, 2, this._widgetColor(0.8)) 531 | this.drawText(rightDrawing,96*(i-1)+7,216, 40,20,dMin,"regular",19,"left",this._widgetColor(0.8)) 532 | this.drawText(rightDrawing,96*(i-1)+49,216, 30,20,dMax,"regular",19,"right",this._widgetColor(0.8)) 533 | // 每日日期 534 | const weatherDate = new Date() 535 | weatherDate.setDate(weatherDate.getDate() + i) 536 | // log(weatherDate) 537 | df.dateFormat = "E" 538 | this.drawText(rightDrawing, 96*(i-1)-1,176-98+24+68, 50,20,df.string(weatherDate),"bold",17,"center") 539 | } 540 | } 541 | 542 | // 问候语 543 | async provideGreeting() { 544 | let lunar = await this.getLunar(); 545 | lunar = `${lunar.lunarMonthCn}${lunar.lunarDayCn}`; 546 | 547 | let dates = this.getDateStr('M-d H').split(' '); 548 | // 纪念日 549 | const anniversary = this.widgetConfigs.anniversaryText[dates[0]]; 550 | // 小时 551 | const hour = parseInt(dates[1]); 552 | const greetingText = this.widgetConfigs.greetingText; 553 | const lunarText = this.widgetConfigs.lunarText[lunar]; 554 | if (anniversary == undefined) { 555 | if (hour < 5) { return greetingText.nightGreeting } 556 | if (hour < 11) { return greetingText.morningGreeting } 557 | if (hour >= 11 && hour <= 13) { return greetingText.noonGreeting } 558 | if (hour < 19) { return greetingText.afternoonGreeting } 559 | if (hour < 22) { return greetingText.eveningGreeting } 560 | return greetingText.nightGreeting 561 | } else { 562 | if(!!lunarText){ 563 | return lunarText; 564 | }else { 565 | return anniversary; 566 | } 567 | } 568 | } 569 | 570 | // ######绘制主要天气图标####### 571 | async drawIcon(drawing,x1,y1,WeatherCondition,size){ 572 | if (WeatherCondition=="CLOUDY") {y1=y1+8} 573 | if(WeatherCondition=="LIGHT_RAIN"||WeatherCondition=="MODERATE_RAIN"||WeatherCondition=="HEAVY_RAIN"||WeatherCondition=="STORM_RAIN") 574 | {y1=y1+4} 575 | drawing.drawImageAtPoint(this.provideSymbol(WeatherCondition, 0, size), new Point(x1, y1)) 576 | } 577 | 578 | // ######提供天气图标名称####### 579 | provideSymbol(cond,night,size = false) { 580 | let symbols = { 581 | "CLEAR_DAY": function() {return"sun.max.fill"}, 582 | "CLEAR_NIGHT": function() {return"moon.stars.fill"}, 583 | "PARTLY_CLOUDY_DAY": function() {return"cloud.sun.fill"}, 584 | "PARTLY_CLOUDY_NIGHT": function() {return"cloud.moon.fill"}, 585 | "CLOUDY": function() {return"cloud.fill"}, 586 | "LIGHT_HAZE": function() {return night? "cloud.fog.fill":"sun.haze.fill"}, 587 | "MODERATE_HAZE": function() {return night? "cloud.fog.fill":"sun.haze.fill"}, 588 | "HEAVY_HAZE": function() {return night? "cloud.fog.fill":"sun.haze.fill"}, 589 | "LIGHT_RAIN": function() {return"cloud.drizzle.fill"}, 590 | "MODERATE_RAIN": function() {return"cloud.rain.fill"}, 591 | "HEAVY_RAIN": function() {return"cloud.rain.fill"}, 592 | "STORM_RAIN": function() {return"cloud.heavyrain.fill"}, 593 | "RAIN": function() {return"cloud.rain.fill"}, 594 | "FOG": function() {return"cloud.fog.fill"}, 595 | "LIGHT_SNOW": function() {return"cloud.sleet.fill"}, 596 | "MODERATE_SNOW": function() {return"cloud.snow.fill"}, 597 | "HEAVY_SNOW": function() {return"cloud.snow.fill"}, 598 | "STORM_SNOW": function() {return"snow"}, 599 | "DUST": function() {return night? "cloud.fog.fill":"sun.dust.fill"}, 600 | "SAND": function() {return night? "cloud.fog.fill":"sun.dust.fill"}, 601 | "WIND": function() {return"wind"}, 602 | } 603 | let sfs = SFSymbol.named(symbols[cond]()); 604 | if(size) sfs.applyFont(Font.systemFont(size)) 605 | return sfs.image 606 | } 607 | 608 | /** 609 | * 获取一言 610 | */ 611 | async getOneWord() { 612 | const url = 'https://v1.hitokoto.cn/?encode=json' 613 | const data = await this.httpGet(url, true, '一言') 614 | return `“${data.hitokoto}”` 615 | } 616 | 617 | // 彩云天气 618 | async inputKey(){ 619 | const message = '申请彩云天气key'; 620 | const idx = await this.generateAlert(message, ['申请key','输入key']); 621 | if(idx === 0) return await Safari.open('https://dashboard.caiyunapp.com/user/sign_up/',false); 622 | await this.setCustomAction("输入彩云key", "只有输入正确的彩云key\n组件才会生效", { 623 | caiyunKEY: "此处输入彩云key", 624 | }); 625 | } 626 | 627 | // 添加设置信息 628 | Run(){ 629 | if (config.runsInApp) { 630 | if(!this.settings.caiyunKEY) this.notify(this.name, '需申请彩云天气key,点击注册!',{openURL:'https://dashboard.caiyunapp.com/user/sign_up/'}); 631 | this.registerAction("基础设置", this.setWidgetConfig); 632 | this.registerAction("彩云key", async () => { 633 | await this.inputKey() 634 | }, this.logo); 635 | this.registerAction("界面切换", async () => { 636 | await this.setChoiceAction("界面切换", "切换中组件显示方式", [ 637 | '常规版','宝藏版', 638 | ]); 639 | }, { name: 'square.on.square', color: '#87CEEB' }); 640 | } 641 | } 642 | } 643 | 644 | // @组件代码结束 645 | await Runing(Widget) -------------------------------------------------------------------------------- /动森.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: yellow; icon-glyph: feather-alt; 4 | //------------------------------------------------ 5 | const env = importModule('ENV.js') 6 | //------------------------------------------------ 7 | // 配置区 8 | env.configs.previewSize = "Small" // 预览大小【小:Small,中:Medium,大:Large】 9 | env.configs.changePicBg = true // 是否需要更换背景 10 | env.configs.colorMode = false // 是否是纯色背景 11 | env.configs.bgColor = new Color("000000") // 小组件背景色 12 | env.configs.topPadding = 0 // 内容区边距 13 | env.configs.leftPadding = 0 // 内容区边距 14 | env.configs.bottomPadding = 0 // 内容区边距 15 | env.configs.rightPadding = 0 // 内容区边距 16 | env.configs.refreshInterval = 60 * 3 // 刷新间隔,单位分钟,非精准,会有3-5分钟差距 17 | // 18 | const imgStyle = env.imgStyle 19 | const textStyle = env.textStyle 20 | //------------------------------------------------ 21 | // 脚本名字 22 | const name = Script.name() 23 | // 组件 24 | const widget = new ListWidget() 25 | const contentStack = widget.addStack() 26 | //------------------------------------------------ 27 | 28 | 29 | //↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓内容区↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 30 | //------------------------------------------------ 31 | // 获取外部输入的参数 32 | var widgetInputRAW = args.widgetParameter 33 | try { 34 | widgetInputRAW.toString() 35 | } catch(e) { 36 | widgetInputRAW = "" 37 | } 38 | // 指定日期 39 | var assignMonthDay = widgetInputRAW.toString() 40 | 41 | const imgObjs = { 42 | "1月1日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 43 | "1月2日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 44 | "1月3日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 45 | "1月4日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 46 | "1月5日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 47 | "1月6日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 48 | "1月7日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 49 | "1月8日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 50 | "1月9日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 51 | "1月10日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 52 | "1月11日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 53 | "1月12日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 54 | "1月13日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 55 | "1月14日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 56 | "1月15日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 57 | "1月16日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 58 | "1月17日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 59 | "1月18日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 60 | "1月19日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 61 | "1月20日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 62 | "1月21日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 63 | "1月22日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 64 | "1月23日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 65 | "1月24日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 66 | "1月25日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 67 | "1月26日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 68 | "1月27日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 69 | "1月28日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 70 | "1月29日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 71 | "1月30日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 72 | "1月31日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 73 | "2月1日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 74 | "2月2日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 75 | "2月3日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 76 | "2月4日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 77 | "2月5日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 78 | "2月6日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 79 | "2月7日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 80 | "2月8日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 81 | "2月9日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 82 | "2月10日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 83 | "2月11日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 84 | "2月12日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 85 | "2月13日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 86 | "2月14日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 87 | "2月15日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 88 | "2月16日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 89 | "2月17日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 90 | "2月18日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 91 | "2月19日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 92 | "2月20日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 93 | "2月21日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 94 | "2月22日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 95 | "2月23日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 96 | "2月24日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 97 | "2月25日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 98 | "2月26日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 99 | "2月27日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 100 | "2月28日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 101 | "3月1日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 102 | "3月2日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 103 | "3月3日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 104 | "3月4日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 105 | "3月5日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 106 | "3月6日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 107 | "3月7日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 108 | "3月8日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 109 | "3月9日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 110 | "3月10日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 111 | "3月11日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 112 | "3月12日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 113 | "3月13日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 114 | "3月14日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 115 | "3月15日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 116 | "3月16日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 117 | "3月17日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 118 | "3月18日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 119 | "3月19日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 120 | "3月20日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 121 | "3月21日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 122 | "3月22日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 123 | "3月23日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 124 | "3月24日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 125 | "3月25日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 126 | "3月26日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 127 | "3月27日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 128 | "3月28日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 129 | "3月29日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 130 | "3月30日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 131 | "3月31日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 132 | "4月2日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 133 | "4月3日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 134 | "4月4日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 135 | "4月5日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 136 | "4月6日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 137 | "4月7日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 138 | "4月8日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 139 | "4月9日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 140 | "4月10日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 141 | "4月11日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 142 | "4月12日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 143 | "4月13日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 144 | "4月14日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 145 | "4月16日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 146 | "4月17日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 147 | "4月18日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 148 | "4月19日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 149 | "4月20日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 150 | "4月21日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 151 | "4月22日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 152 | "4月23日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 153 | "4月24日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 154 | "4月25日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 155 | "4月26日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 156 | "4月27日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 157 | "4月28日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 158 | "4月29日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 159 | "4月30日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 160 | "5月1日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 161 | "5月2日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 162 | "5月3日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 163 | "5月4日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 164 | "5月5日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 165 | "5月6日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 166 | "5月7日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 167 | "5月8日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 168 | "5月9日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 169 | "5月10日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 170 | "5月11日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 171 | "5月12日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 172 | "5月13日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 173 | "5月14日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 174 | "5月15日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 175 | "5月16日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 176 | "5月17日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 177 | "5月18日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 178 | "5月19日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 179 | "5月20日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 180 | "5月21日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 181 | "5月22日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 182 | "5月23日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 183 | "5月24日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 184 | "5月25日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 185 | "5月26日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 186 | "5月27日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 187 | "5月28日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 188 | "5月29日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 189 | "5月30日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 190 | "5月31日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 191 | "6月1日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 192 | "6月2日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 193 | "6月3日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 194 | "6月4日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 195 | "6月5日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 196 | "6月6日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 197 | "6月7日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 198 | "6月8日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 199 | "6月9日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 200 | "6月10日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 201 | "6月11日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 202 | "6月12日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 203 | "6月13日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 204 | "6月14日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 205 | "6月15日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 206 | "6月16日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 207 | "6月17日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 208 | "6月18日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 209 | "6月19日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 210 | "6月20日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 211 | "6月21日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 212 | "6月22日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 213 | "6月23日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 214 | "6月24日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 215 | "6月25日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 216 | "6月26日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 217 | "6月27日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 218 | "6月28日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 219 | "6月29日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 220 | "6月30日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 221 | "7月1日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 222 | "7月2日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 223 | "7月3日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 224 | "7月4日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 225 | "7月5日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 226 | "7月6日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 227 | "7月7日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 228 | "7月8日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 229 | "7月9日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 230 | "7月10日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 231 | "7月11日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 232 | "7月12日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 233 | "7月13日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 234 | "7月14日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 235 | "7月15日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 236 | "7月16日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 237 | "7月17日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 238 | "7月18日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 239 | "7月19日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 240 | "7月20日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 241 | "7月21日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 242 | "7月22日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 243 | "7月23日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 244 | "7月24日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 245 | "7月25日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 246 | "7月26日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 247 | "7月27日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 248 | "7月28日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 249 | "7月29日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 250 | "7月30日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 251 | "7月31日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 252 | "8月1日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 253 | "8月2日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 254 | "8月3日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 255 | "8月4日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 256 | "8月5日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 257 | "8月6日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 258 | "8月7日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 259 | "8月8日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 260 | "8月9日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 261 | "8月10日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 262 | "8月11日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 263 | "8月12日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 264 | "8月13日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 265 | "8月14日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 266 | "8月15日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 267 | "8月16日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 268 | "8月17日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 269 | "8月18日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 270 | "8月19日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 271 | "8月20日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 272 | "8月21日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 273 | "8月22日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 274 | "8月23日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 275 | "8月24日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 276 | "8月25日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 277 | "8月26日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 278 | "8月27日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 279 | "8月28日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 280 | "8月29日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 281 | "8月30日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 282 | "8月31日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 283 | "9月1日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 284 | "9月2日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 285 | "9月3日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 286 | "9月4日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 287 | "9月5日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 288 | "9月6日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 289 | "9月7日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 290 | "9月8日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 291 | "9月9日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 292 | "9月10日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 293 | "9月11日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 294 | "9月12日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 295 | "9月13日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 296 | "9月14日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 297 | "9月15日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 298 | "9月16日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 299 | "9月17日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 300 | "9月18日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 301 | "9月19日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 302 | "9月20日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 303 | "9月21日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 304 | "9月22日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 305 | "9月23日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 306 | "9月24日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 307 | "9月25日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 308 | "9月26日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 309 | "9月27日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 310 | "9月28日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 311 | "9月29日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 312 | "9月30日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 313 | "10月1日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 314 | "10月2日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 315 | "10月3日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 316 | "10月4日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 317 | "10月5日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 318 | "10月6日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 319 | "10月7日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 320 | "10月8日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 321 | "10月9日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 322 | "10月10日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 323 | "10月11日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 324 | "10月12日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 325 | "10月13日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 326 | "10月14日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 327 | "10月15日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 328 | "10月16日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 329 | "10月17日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 330 | "10月18日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 331 | "10月19日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 332 | "10月20日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 333 | "10月21日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 334 | "10月22日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 335 | "10月23日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 336 | "10月24日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 337 | "10月25日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 338 | "10月26日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 339 | "10月27日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 340 | "10月28日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 341 | "10月29日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 342 | "10月30日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 343 | "11月1日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 344 | "11月2日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 345 | "11月3日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 346 | "11月4日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 347 | "11月5日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 348 | "11月6日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 349 | "11月7日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 350 | "11月8日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 351 | "11月9日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 352 | "11月10日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 353 | "11月11日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 354 | "11月12日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 355 | "11月13日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 356 | "11月14日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 357 | "11月15日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 358 | "11月16日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 359 | "11月17日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 360 | "11月18日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 361 | "11月19日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 362 | "11月20日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 363 | "11月21日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 364 | "11月22日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 365 | "11月23日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 366 | "11月24日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 367 | "11月25日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 368 | "11月26日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 369 | "11月27日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 370 | "11月28日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 371 | "11月29日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 372 | "11月30日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 373 | "12月1日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 374 | "12月2日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 375 | "12月3日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 376 | "12月4日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 377 | "12月5日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 378 | "12月6日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 379 | "12月7日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 380 | "12月8日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 381 | "12月9日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 382 | "12月10日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 383 | "12月11日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 384 | "12月12日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 385 | "12月13日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 386 | "12月14日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 387 | "12月15日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 388 | "12月16日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 389 | "12月17日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 390 | "12月18日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 391 | "12月19日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 392 | "12月20日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png", 393 | "12月21日": "https://patchwiki.biligame.com/images/dongsen/b/bc/p9aoiubbogoydvba1lnyftutmkf2ck7.png", 394 | "12月22日": "https://patchwiki.biligame.com/images/dongsen/d/d7/orr001b5scgm12acutroxom53ypwy4t.png", 395 | "12月23日": "https://patchwiki.biligame.com/images/dongsen/1/16/ctor1h8rfpfrt78v0ul8ouni7al339l.png", 396 | "12月25日": "https://patchwiki.biligame.com/images/dongsen/4/49/59o34z38jeoh43oocsdifo0kgm7mvne.png", 397 | "12月26日": "https://patchwiki.biligame.com/images/dongsen/a/a9/4txtpw7gqq1yu34iukiv8s5hsodpe0b.png", 398 | "12月27日": "https://patchwiki.biligame.com/images/dongsen/c/c3/7d99fi1m321r7uqh4ynjemfvmcdhn5f.png", 399 | "12月28日": "https://patchwiki.biligame.com/images/dongsen/9/9a/5pxemeyv8o2mywwf6k6aresy6f9oec1.png", 400 | "12月29日": "https://patchwiki.biligame.com/images/dongsen/c/c7/pq9pp4ikfbba4qe54rbappa6xy29hvs.png", 401 | "12月30日": "https://patchwiki.biligame.com/images/dongsen/d/dc/hjv0riyqct10xo1fieojpnscxkerhhh.png", 402 | "12月31日": "https://patchwiki.biligame.com/images/dongsen/6/6e/g6cocxxla3629wil6gucwpl6ojtzmt7.png" 403 | } 404 | 405 | const date = new Date() 406 | const month = date.getMonth() + 1 407 | const day = date.getDate() 408 | let monthDayStr = `${month}月${day}日` 409 | if(assignMonthDay.length > 0) { 410 | monthDayStr = assignMonthDay 411 | } 412 | 413 | log(`>>日期:${monthDayStr}`) 414 | 415 | let imgUrl = imgObjs[monthDayStr] 416 | if(imgUrl == undefined) { 417 | imgUrl = imgObjs["10月1日"] 418 | } 419 | let img = await env.getImage(imgUrl) 420 | imgStyle.stack = contentStack 421 | imgStyle.width = 138 422 | imgStyle.height = 138 423 | imgStyle.img = img 424 | env.addStyleImg() 425 | //------------------------------------------------ 426 | //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑内容区↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 427 | 428 | //------------------------------------------------ 429 | // 运行脚本、预览 430 | await env.run(name, widget) 431 | //------------------------------------------------ --------------------------------------------------------------------------------