-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathEnv.scriptable
More file actions
12 lines (11 loc) · 17.5 KB
/
Copy pathEnv.scriptable
File metadata and controls
12 lines (11 loc) · 17.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
{
"always_run_in_app" : false,
"icon" : {
"color" : "blue",
"glyph" : "database"
},
"name" : "Env",
"script" : "\/**\n * Author: GideonSenku\n * Github: https:\/\/github.com\/GideonSenku\n *\/\n\nvar locationData, sunData\nconst currentDate = new Date()\nconst request = new Request('')\nconst files = FileManager.iCloud()\nconst dict = files.documentsDirectory()\nfiles.isDirectory(`${dict}\/Env`) ? `` : files.createDirectory(`${dict}\/Env`)\nconst defaultHeaders = {\n \"Accept\": \"*\/*\",\n \"Content-Type\": \"application\/json\"\n}\nconst textFormat = {\n defaultText: { size: 14, color: \"ffffff\", font: \"regular\" },\n battery: { size: 10, color: \"\", font: \"bold\" },\n title: { size: 16, color: \"\", font: \"semibold\" },\n SFMono: { size: 12, color: \"ffffff\", font: \"SF Mono\" }\n}\n\/**\n * @description GET,返回String数据\n * @param {*} param0 request信息\n * @param {*} callback 回调返回response和JSON对象\n *\/\nconst get = async ({ url, headers = {} }, callback = () => {} ) => {\n request.url = url\n request.method = 'GET'\n request.headers = {\n ...headers,\n ...defaultHeaders\n }\n const data = await request.loadJSON()\n callback(request.response, data)\n return data\n}\n\n\/**\n * @description GET,返回String数据\n * @param {*} param0 request信息\n * @param {*} callback 回调返回response和String对象\n *\/\nconst getStr = async ({ url, headers = {} }, callback = () => {} ) => {\n request.url = url\n request.method = 'GET'\n request.headers = {\n ...headers,\n ...defaultHeaders\n }\n const data = await request.loadString()\n callback(request.response, data)\n return data\n}\n\n\/**\n * @description POST,返回String数据\n * @param {*} param0 request信息\n * @param {*} callback 回调返回response和String\n *\/\nconst post = async ({ url, body, headers = {} }, callback = () => {} ) => {\n request.url = url\n request.body = body\n request.method = 'POST'\n request.headers = {\n ...defaultHeaders,\n ...headers\n }\n const data = await request.loadString()\n callback(request.response, data)\n return data\n}\n\n\/**\n * @description POST,返回JSON数据\n * @param {*} param0 request信息\n * @param {*} callback 回调返回response和JSON\n *\/\nconst _post = async ({ url, body, headers = {} }, callback = () => {} ) => {\n request.url = url\n request.body = body\n request.method = 'POST'\n request.headers = {\n ...defaultHeaders,\n ...headers\n }\nconst data = await request.loadJSON()\ncallback(request.response, data)\nreturn data\n}\n\n\/**\n * @description 下载文件\n * @param {*} param0 \n *\/\nconst getFile = async ({moduleName, url}) => {\n log(`开始下载文件: 🌝 ${moduleName}`)\n const header = `\/\/ Variables used by Scriptable.\n\/\/ These must be at the very top of the file. Do not edit.\n\/\/ icon-color: deep-gray; icon-glyph: file-code;\\n`;\n const content = await getStr({url})\n const fileHeader = content.includes('icon-color') ? `` : header\n write(`${moduleName}`, `${fileHeader}${content}`)\n log(`文件下载完成: 🌚 ${moduleName}`)\n}\n\n\/**\n * \n * @description 导入模块,不存在即下载模块,也可传入forceDownload: true 强制更新模块\n * @param {*} param0 \n *\/\nconst require = ({\n moduleName,\n url = '',\n forceDownload = false\n}) => {\n if (isFileExists(moduleName) && !forceDownload) {\n log(`导入模块: 🪐${moduleName}`)\n return importModule(moduleName)\n } else {\n getFile({ moduleName, url })\n log(`导入模块: 🪐${moduleName}`)\n return importModule(moduleName)\n }\n}\n\/**\n * \n * @description 将数据写入文件\n * @param {*} fileName 要写入的文件名,默认JS文件,可选其他,加上文件名后缀即可\n * @param {*} content 要写入的文件内容\n *\/\nconst write = (fileName, content) => {\n let file = initFile(fileName)\n const filePath = `${dict}\/${file}`\n FileManager.iCloud().writeString(filePath, content)\n return true\n}\n\n\/**\n * \n * @description 判断文件是否存在\n * @param {*} fileName \n *\/\nconst isFileExists = (fileName) => {\n let file = initFile(fileName)\n return FileManager.iCloud().fileExists(`${dict}\/${file}`)\n}\n\nconst initFile = (fileName) => {\n const hasSuffix = fileName.lastIndexOf('.') + 1\n return !hasSuffix ? `${fileName}.js` : fileName\n}\n\n\/**\n * \n * @description 读取文件内容\n * @param {*} fileName 要读取的文件名,默认JS文件,可选其他,加上文件名后缀即可\n * @return 返回文件内容,字符串形式\n *\/\nconst read = (fileName) => {\n const file = initFile(fileName)\n return FileManager.iCloud().readString(`${dict}\/${file}`)\n}\n\n\/**\n * \n * @description 提示框\n * @param {*} title 提示框标题\n * @param {*} message 提示框内容\n * @param {*} btnMes 提示框按钮标题,默认Cancel\n *\/\nconst msg = (title, message, btnMes = 'Cancel') => {\n if (!config.runsInWidget) {\n const alert = new Alert()\n alert.title = title\n alert.message = message\n alert.addAction(btnMes)\n alert.present()\n }\n}\n\nconst setdata = (Val, Key) => {\n Keychain.set(Val, Key)\n return true\n}\n\nconst getdata = (Key) => {\n return Keychain.get(Key)\n}\n\nconst hasdata = (Key) => {\n return Keychain.contains(Key)\n}\n\nconst rmdata = (Key) => {\n Keychain.remove(Key)\n return true\n}\n\n\/\/ Presents an alert where the user can enter a value in a text field.\n\/\/ Returns the entered value.\nconst input = async(title, message, placeholder, value = null) => {\n if (!config.runsInWidget) {\n let alert = new Alert()\n alert.title = title\n alert.message = message\n alert.addTextField(placeholder, value)\n alert.addAction(\"OK\")\n alert.addCancelAction(\"Cancel\")\n let idx = await alert.present()\n if (idx != -1) {\n return alert.textFieldValue(0)\n } else {\n throw new Error(\"Cancelled entering value\")\n }\n }\n}\n\n\/**\n *\n * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S')\n * :$.time('yyyyMMddHHmmssS')\n * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒\n * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符\n * @param {*} fmt 格式化参数\n * @param {*} ts 时间戳 13位\n *\/\nconst time = (fmt, ts = null) => {\n const date = ts ? new Date(ts) : new Date()\n let o = {\n 'M+': date.getMonth() + 1,\n 'd+': date.getDate(),\n 'H+': date.getHours(),\n 'm+': date.getMinutes(),\n 's+': date.getSeconds(),\n 'q+': Math.floor((date.getMonth() + 3) \/ 3),\n 'S': date.getMilliseconds()\n }\n if (\/(y+)\/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))\n for (let k in o)\n if (new RegExp('(' + k + ')').test(fmt))\n fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))\n return fmt\n}\n\n\/**\n * @description create wiget\n * @param {*} title required\n * @param {*} texts required\n * @param {*} preview option\n *\/\nconst createWidget = async({ title, texts = { },spacing = 5, preview = '' }) => {\n let w = new ListWidget()\n w.spacing = spacing\n \n let gradient = new LinearGradient()\n let gradientSettings = await setupGradient()\n \n gradient.colors = gradientSettings.color()\n gradient.locations = gradientSettings.position()\n \n w.backgroundGradient = gradient\n texts['battery'] ? battery(w, title) : provideText(title, w, textFormat.title)\n for (const text in texts) {\n if (text != 'battery' && text != 'updateTime' && texts.hasOwnProperty(text) && texts[text]) {\n const element = texts[text]\n provideText(element, w, textFormat.SFMono)\n }\n }\n texts['updateTime'] ? provideText(`[更新] ${time('MM-dd HH:mm')}`, w, textFormat.SFMono) : `` \n \n widgetPreview = preview ? preview: 'small'\n \n if(widgetPreview == \"small\") { w.presentSmall() }\n else if (widgetPreview == \"medium\") { w.presentMedium() }\n else if (widgetPreview == \"large\") { w.presentLarge() }\n return w\n}\n\n\n\/**\n * @description Provide a font based on the input.\n * @param {*} fontName \n * @param {*} fontSize \n *\/\nconst provideFont = (fontName, fontSize) => {\n const fontGenerator = {\n \"ultralight\": function() { return Font.ultraLightSystemFont(fontSize) },\n \"light\": function() { return Font.lightSystemFont(fontSize) },\n \"regular\": function() { return Font.regularSystemFont(fontSize) },\n \"medium\": function() { return Font.mediumSystemFont(fontSize) },\n \"semibold\": function() { return Font.semiboldSystemFont(fontSize) },\n \"bold\": function() { return Font.boldSystemFont(fontSize) },\n \"heavy\": function() { return Font.heavySystemFont(fontSize) },\n \"black\": function() { return Font.blackSystemFont(fontSize) },\n \"italic\": function() { return Font.italicSystemFont(fontSize) }\n }\n \n const systemFont = fontGenerator[fontName]\n if (systemFont) { return systemFont() }\n return new Font(fontName, fontSize)\n}\n \n\n\/**\n * @description Add formatted text to a container.\n * @param {*} string \n * @param {*} container widget container\n * @param {*} format Object: size, color, font\n *\/\n\nconst provideText = (string, container, format) => {\n let url\n if (typeof string !== 'string') {\n url = string.url\n string = string.text\n }\n const stackItem = container.addStack()\n\n if (url) {\n stackItem.url = url\n }\n\n const textItem = stackItem.addText(string)\n const textFont = format.font || textFormat.defaultText.font\n const textSize = format.size || textFormat.defaultText.size\n const textColor = format.color || textFormat.defaultText.color\n \n textItem.font = provideFont(textFont, textSize)\n textItem.textColor = new Color(textColor)\n return stackItem\n}\n\n\/\/ Set up the gradient for the widget background.\nconst setupGradient = async() => {\n \n \/\/ Requirements: sunrise\n if (!sunData) { await setupSunrise() }\n\n let gradient = {\n dawn: {\n color() { return [new Color(\"142C52\"), new Color(\"1B416F\"), new Color(\"62668B\")] },\n position() { return [0, 0.5, 1] },\n },\n\n sunrise: {\n color() { return [new Color(\"274875\"), new Color(\"766f8d\"), new Color(\"f0b35e\")] },\n position() { return [0, 0.8, 1.5] },\n },\n\n midday: {\n color() { return [new Color(\"3a8cc1\"), new Color(\"90c0df\")] },\n position() { return [0, 1] },\n },\n\n noon: {\n color() { return [new Color(\"b2d0e1\"), new Color(\"80B5DB\"), new Color(\"3a8cc1\")] },\n position() { return [-0.2, 0.2, 1.5] },\n },\n\n sunset: {\n color() { return [new Color(\"32327A\"), new Color(\"662E55\"), new Color(\"7C2F43\")] },\n position() { return [0.1, 0.9, 1.2] },\n },\n\n twilight: {\n color() { return [new Color(\"021033\"), new Color(\"16296b\"), new Color(\"414791\")] },\n position() { return [0, 0.5, 1] },\n },\n\n night: {\n color() { return [new Color(\"16296b\"), new Color(\"021033\"), new Color(\"021033\"), new Color(\"113245\")] },\n position() { return [-0.5, 0.2, 0.5, 1] },\n },\n }\n\n const sunrise = sunData.sunrise\n const sunset = sunData.sunset\n const utcTime = currentDate.getTime()\n\n function closeTo(time,mins) {\n return Math.abs(utcTime - time) < (mins * 60000)\n }\n\n \/\/ Use sunrise or sunset if we're within 30min of it.\n if (closeTo(sunrise,15)) { return gradient.sunrise }\n if (closeTo(sunset,15)) { return gradient.sunset }\n\n \/\/ In the 30min before\/after, use dawn\/twilight.\n if (closeTo(sunrise,45) && utcTime < sunrise) { return gradient.dawn }\n if (closeTo(sunset,45) && utcTime > sunset) { return gradient.twilight }\n\n \/\/ Otherwise, if it's night, return night.\n if (isNight(currentDate)) { return gradient.night }\n\n \/\/ If it's around noon, the sun is high in the sky.\n if (currentDate.getHours() == 12) { return gradient.noon }\n \/\/ Otherwise, return the \"typical\" theme.\n return gradient.midday\n}\n\n\/\/ Set up the sunData object.\nconst setupSunrise = async () => {\n\n \/\/ Requirements: location\n if (!locationData) { await setupLocation() }\n\n \/\/ Set up the sunrise\/sunset cache.\n const sunCachePath = files.joinPath(dict, \"Env\/Env-sun\")\n const sunCacheExists = files.fileExists(sunCachePath)\n const sunCacheDate = sunCacheExists ? files.modificationDate(sunCachePath) : 0\n var sunDataRaw\n\n \/\/ If cache exists and it was created today, use cached data.\n if (sunCacheExists && sameDay(currentDate, sunCacheDate)) {\n const sunCache = files.readString(sunCachePath)\n sunDataRaw = JSON.parse(sunCache)\n\n \/\/ Otherwise, use the API to get sunrise and sunset times.\n } else {\n const sunReq = \"https:\/\/api.sunrise-sunset.org\/json?lat=\" + locationData.latitude + \"&lng=\" + locationData.longitude + \"&formatted=0&date=\" + currentDate.getFullYear() + \"-\" + (currentDate.getMonth()+1) + \"-\" + currentDate.getDate()\n sunDataRaw = await new Request(sunReq).loadJSON()\n files.writeString(sunCachePath, JSON.stringify(sunDataRaw))\n }\n\n \/\/ Store the timing values.\n sunData = {}\n sunData.sunrise = new Date(sunDataRaw.results.sunrise).getTime()\n sunData.sunset = new Date(sunDataRaw.results.sunset).getTime()\n}\n\nconst setupLocation = async (lockLocation = true) => {\n\n locationData = {}\n const locationPath = files.joinPath(dict, \"Env\/Env-location\")\n\n \/\/ If our location is unlocked or cache doesn't exist, ask iOS for location.\n var readLocationFromFile = false\n if (!lockLocation || !files.fileExists(locationPath)) {\n try {\n const location = await Location.current()\n locationData.latitude = location.latitude\n locationData.longitude = location.longitude\n files.writeString(locationPath, location.latitude + \",\" + location.longitude)\n \n } catch(e) {\n \/\/ If we fail in unlocked mode, read it from the cache.\n if (!lockLocation) { readLocationFromFile = true }\n \n \/\/ We can't recover if we fail on first run in locked mode.\n else { return }\n }\n }\n \n \/\/ If our location is locked or we need to read from file, do it.\n if (lockLocation || readLocationFromFile) {\n const locationStr = files.readString(locationPath).split(\",\")\n locationData.latitude = locationStr[0]\n locationData.longitude = locationStr[1]\n }\n return locationData\n}\n\n\/\/ Determines if the provided date is at night.\nconst isNight = (dateInput) => {\n const timeValue = dateInput.getTime()\n return (timeValue < sunData.sunrise) || (timeValue > sunData.sunset)\n}\n\/\/ Determines if two dates occur on the same day\nconst sameDay = (d1, d2) => {\n return d1.getFullYear() === d2.getFullYear() &&\n d1.getMonth() === d2.getMonth() &&\n d1.getDate() === d2.getDate()\n}\n\/**\n * @description 返回电池百分比\n *\/\nconst renderBattery = () => {\n const batteryLevel = Device.batteryLevel()\n const batteryAscii = `${Math.round(batteryLevel * 100)}%`\n return batteryAscii\n}\n\n\n\/\/ Add a battery element to the widget; consisting of a battery icon and percentage.\nfunction battery(column,title) {\n const batteryLevel = Device.batteryLevel()\n \/\/ Set up the battery level item\n let batteryStack = column.addStack()\n provideText(title, batteryStack, textFormat.title)\n \n batteryStack.centerAlignContent()\n \n batteryStack.addSpacer()\n \n let batteryIcon = batteryStack.addImage(provideBatteryIcon())\n batteryIcon.imageSize = new Size(20,20)\n \n\n \/\/ Change the battery icon to red if battery level is <= 20 to match system behavior\n if ( Math.round(batteryLevel * 100) > 20 || Device.isCharging() ) {\n\n batteryIcon.tintColor = Color.white()\n\n } else {\n\n batteryIcon.tintColor = Color.red()\n\n }\n\n \/\/ Display the battery status\n let batteryInfo = provideText(' '+renderBattery(), batteryStack, textFormat.battery)\n\n\n}\n\n\n\/\/ Provide a battery SFSymbol with accurate level drawn on top of it.\nfunction provideBatteryIcon() {\n \n if (Device.isCharging()) { return SFSymbol.named(\"battery.100.bolt\").image }\n \n \/\/ Set the size of the battery icon.\n const batteryWidth = 87\n const batteryHeight = 41\n \n \/\/ Start our draw context.\n let draw = new DrawContext()\n draw.opaque = false\n draw.respectScreenScale = true\n draw.size = new Size(batteryWidth, batteryHeight)\n \n \/\/ Draw the battery.\n draw.drawImageInRect(SFSymbol.named(\"battery.0\").image, new Rect(0, 0, batteryWidth, batteryHeight))\n \n \/\/ Match the battery level values to the SFSymbol.\n const x = batteryWidth*0.1525\n const y = batteryHeight*0.247\n const width = batteryWidth*0.602\n const height = batteryHeight*0.505\n \n \/\/ Prevent unreadable icons.\n let level = Device.batteryLevel()\n if (level < 0.05) { level = 0.05 }\n \n \/\/ Determine the width and radius of the battery level.\n const current = width * level\n let radius = height\/6.5\n \n \/\/ When it gets low, adjust the radius to match.\n if (current < (radius * 2)) { radius = current \/ 2 }\n\n \/\/ Make the path for the battery level.\n let barPath = new Path()\n barPath.addRoundedRect(new Rect(x, y, current, height), radius, radius)\n draw.addPath(barPath)\n draw.setFillColor(Color.black())\n draw.fillPath()\n return draw.getImage()\n}\n\nconst logErr = (e, messsage) => {\n console.error(e)\n}\n\n\nmodule.exports = {\n dict,\n get,\n getStr,\n post,\n _post,\n getFile,\n require,\n write,\n isFileExists,\n initFile,\n read,\n setdata,\n getdata,\n hasdata,\n rmdata,\n msg,\n input,\n time,\n createWidget,\n provideText,\n setupLocation,\n renderBattery,\n logErr\n}",
"share_sheet_inputs" : [
]
}