├── LICENSE ├── .gitignore ├── covid.js ├── Updates.js ├── LotteryTicket.js └── AQI.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chaitanya K 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /covid.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: user-md; 4 | 5 | const countriesList = ['IN', 'US'] 6 | 7 | const formatter = new Intl.NumberFormat('en-US') 8 | const rtf = new Intl.RelativeTimeFormat('en-US'); 9 | 10 | 11 | const formatDate = (d) => { 12 | let month = '' + (d.getMonth() + 1), 13 | day = '' + d.getDate(), 14 | year = d.getFullYear(); 15 | 16 | if (month.length < 2) { 17 | month = '0' + month 18 | } 19 | if (day.length < 2) { 20 | day = '0' + day 21 | } 22 | 23 | return [year, month, day].join('-'); 24 | } 25 | const getDateWithAddedDays = (days) => { 26 | const date = new Date() 27 | date.setDate(date.getDate() + days) 28 | return date; 29 | } 30 | const relativeTime = (str) => { 31 | const date = new Date(str) 32 | const now = new Date() 33 | return rtf.format(Math.ceil((date.valueOf() - now) / (1000 * 60)), 'minutes') 34 | } 35 | if (config.runsInWidget) { 36 | // create and show widget 37 | let widget = new ListWidget() 38 | let preTxt = widget.addText("COVID 19") 39 | preTxt.textColor = Color.white() 40 | preTxt.textOpacity = 0.8 41 | preTxt.textSize = 28 42 | const aDayAfter = getDateWithAddedDays(1) 43 | const yesterday = getDateWithAddedDays(-1) 44 | 45 | for(let index = 0; index < countriesList.length; index += 1) { 46 | const cn = countriesList[index]; 47 | // const req = new Request(`https://coronavirus-19-api.herokuapp.com/countries/${countriesList[index]}`) 48 | const req1 = new Request(`https://api.coronatracker.com/v3/analytics/newcases/country?countryCode=${cn}&startDate=${formatDate(yesterday)}&endDate=${formatDate(aDayAfter)}`) 49 | const res1 = (await req1.loadJSON())[0]; 50 | const req2 = new Request(`https://api.coronatracker.com/v3/stats/worldometer/country?countryCode=${cn}`) 51 | const res2 = (await req2.loadJSON())[0]; 52 | renderCountryStats(widget, res1, res2) 53 | } 54 | 55 | let lastWidgetUpdate = widget.addText(`Widget updated ${relativeTime(new Date())}`) 56 | lastWidgetUpdate.textColor = Color.lightGray() 57 | lastWidgetUpdate.textSize = 8 58 | 59 | widget.backgroundColor = Color.black() 60 | widget.centerAlignContent() 61 | 62 | Script.setWidget(widget) 63 | Script.complete() 64 | } 65 | 66 | function renderCountryStats(widget, res1, res2) { 67 | 68 | let titleTxt = widget.addText(res2.country) 69 | titleTxt.textColor = new Color('#039BE5') 70 | titleTxt.textSize = 22 71 | 72 | let todayTxt = widget.addText(`Today ${formatter.format(res1.new_infections)}`) 73 | todayTxt.textColor = Color.red() 74 | todayTxt.textOpacity = 0.8 75 | todayTxt.textSize = 18 76 | 77 | let totalTxt = widget.addText(`Total ${formatter.format(res2.totalConfirmed)}`) 78 | totalTxt.textColor = Color.white() 79 | totalTxt.textOpacity = 0.8 80 | totalTxt.textSize = 18 81 | 82 | let deathTxt = widget.addText(`☠️ ${formatter.format(res2.totalDeaths)}`) 83 | deathTxt.textColor = Color.white() 84 | deathTxt.textOpacity = 0.8 85 | deathTxt.textSize = 16 86 | 87 | let lastWidgetUpdate = widget.addText(`updated ${relativeTime(res2.lastUpdated)}`) 88 | lastWidgetUpdate.textColor = Color.lightGray() 89 | lastWidgetUpdate.textSize = 8 90 | } 91 | -------------------------------------------------------------------------------- /Updates.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: magic; 4 | function batteryLevel() { 5 | let output = "Battery is running low please charge the device."; 6 | if(Device.batteryLevel() < 0.35) { 7 | return output; 8 | } 9 | } 10 | async function parseLevisStadiumEventsXML(content) { 11 | return new Promise(async function(resolve, reject) { 12 | try { 13 | const today = new Date(); 14 | const thisWeek = new Date(); 15 | thisWeek.setDate(thisWeek.getDate() + 8); 16 | const eventsInThisWeek = []; 17 | const xmlParser = new XMLParser(content); 18 | const items = []; 19 | let currentItem = null; 20 | let currentValue = ''; 21 | xmlParser.didStartElement = name => { 22 | if (name == 'item') { 23 | currentItem = { 24 | title: '', 25 | link: '', 26 | }; 27 | } else if (currentItem && name == "title") { 28 | currentValue = ''; 29 | } else if (currentItem && name == "link") { 30 | currentValue = ''; 31 | } 32 | } 33 | xmlParser.didEndElement = name => { 34 | const hasItem = currentItem != null 35 | if (currentItem && name == 'item') { 36 | items.push(currentItem); 37 | currentItem = null; 38 | } else if (currentItem && name == "title") { 39 | currentItem.title = currentValue; 40 | } else if (currentItem && name == "link") { 41 | currentItem.link = currentValue; 42 | } 43 | } 44 | xmlParser.foundCharacters = str => { 45 | currentValue += str; 46 | } 47 | 48 | xmlParser.didEndDocument = () => { 49 | for (let index = 0; index < items.length; index += 1) { 50 | const { title, link } = items[index]; 51 | const dateStringMatches = link.match(/\d{4}-\d{1,2}-\d{1,2}/); 52 | if (dateStringMatches && dateStringMatches.length > 0) { 53 | const dateValues = dateStringMatches[0].split('-'); 54 | const newDate = new Date(...dateValues); 55 | if (newDate.toDateString() === today.toDateString()) { 56 | eventsInThisWeek.push(`There is an event today, Try to avoid the route. Event: ${title}`) 57 | } else if (thisWeek.valueOf() >= newDate.valueOf()) { 58 | eventsInThisWeek.push(`${title} on ${newDate.toDateString()}`); 59 | } 60 | } 61 | } 62 | resolve(eventsInThisWeek); 63 | }; 64 | xmlParser.parse(); 65 | } catch(e) { 66 | log(e); 67 | resolve([]); 68 | }; 69 | }); 70 | } 71 | 72 | async function levisStadiumEvents() { 73 | const eventsUrl = 'http://www.levisstadium.com/events/category/tickets/feed/'; 74 | const req = new Request(eventsUrl); 75 | const resp = await req.loadString(); 76 | return (await parseLevisStadiumEventsXML(resp)).join('\n'); 77 | } 78 | 79 | function calendarUpdates() { 80 | 81 | } 82 | 83 | async function run() { 84 | const text = []; 85 | text.push(batteryLevel()); 86 | text.push(calendarUpdates()); 87 | text.push(await levisStadiumEvents()); 88 | return text.filter(text => text && text.length > 0).join('\n'); 89 | } 90 | 91 | let siriText = await run(); 92 | if(!siriText) { 93 | siriText = "There are no updates for today"; 94 | } 95 | if (config.runsWithSiri) { 96 | Speech.speak(siriText) 97 | } else { 98 | QuickLook.present(siriText) 99 | } 100 | 101 | -------------------------------------------------------------------------------- /LotteryTicket.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: light-gray; icon-glyph: magic; 4 | let table = new UITable() 5 | 6 | function createArray(length) { 7 | const arr = []; 8 | for(let index = 0; index < length; index += 1) { 9 | arr[index] = index + 1; 10 | } 11 | return arr; 12 | } 13 | 14 | // Lottery picker generates numbers, special number to pick and 15 | // then picks 5 numbers and 1 special number 16 | function lotteryPicker(mainNumbers, specialNumbers) { 17 | let picks = createArray(mainNumbers); 18 | let specialPicks = createArray(specialNumbers); 19 | let lottery = []; 20 | for( let index = 0; index < 5; index += 1) { 21 | const id = Math.round(Math.random() * 1000) % picks.length; 22 | lottery.push(picks[id]); 23 | picks.splice(id, 1); 24 | } 25 | lottery = lottery.sort((a, b) => (a - b)); 26 | const special = Math.round(Math.random() * 1000) % specialPicks.length; 27 | lottery.push(specialPicks[special]); 28 | return lottery.join(" "); 29 | } 30 | // Mega Million has numbers from 1-70, special nubers from 1-25 31 | function megaMillion() { 32 | return lotteryPicker(70, 25); 33 | } 34 | 35 | // Powerball has numbers from 1-69, special nubers from 1-26 36 | function powerBall() { 37 | return lotteryPicker(69, 26); 38 | } 39 | 40 | // Picks up next Mega Million Lottery pick date Tuesday, Friday 41 | function nextMegaMillionDate() { 42 | const today = new Date(); 43 | const future = new Date(); 44 | future.setHours(19); 45 | future.setMinutes(30); 46 | while((future.valueOf() - today.valueOf()) < 0) { 47 | future.setDate(future.getDate() + 1); 48 | } 49 | let day = future.getDay(); 50 | let count = 0; 51 | while(count < 7) { 52 | if(day == 2 || day == 5) { 53 | break; 54 | } 55 | future.setDate(future.getDate() + 1); 56 | day = future.getDay(); 57 | count += 1; 58 | } 59 | return future; 60 | } 61 | 62 | // Picks up next Powerball Lottery pick date Wednesday, Thursday 63 | function nextPowerBallDate() { 64 | const today = new Date(); 65 | const future = new Date(); 66 | future.setHours(19); 67 | future.setMinutes(30); 68 | while((future.valueOf() - today.valueOf()) < 0) { 69 | future.setDate(future.getDate() + 1); 70 | } 71 | let day = future.getDay(); 72 | let count = 0; 73 | 74 | while(count < 7) { 75 | if(day == 3 || day == 6) { 76 | break; 77 | } 78 | future.setDate(future.getDate() + 1); 79 | day = future.getDay(); 80 | count += 1; 81 | } 82 | return future; 83 | } 84 | 85 | // Asks for what kind of lottery to pick and how many lotteries to pick 86 | // And then presents in a table view 87 | // And saves in default Reminders 88 | async function showLotteryPicker() { 89 | const alert = new Alert(); 90 | alert.title = 'Lottery Generator'; 91 | alert.addAction('MegaMillion'); 92 | alert.addAction('PowerBall'); 93 | alert.addTextField('No of tickets to generate', '1'); 94 | function alertSelection(selection) { 95 | const tickets = parseInt(alert.textFieldValue(0)); 96 | const generator = selection === 0 ? megaMillion : powerBall; 97 | const generatorText = selection === 0 ? 'Mega Million' : 'Power Ball'; 98 | const header = new UITableRow(); 99 | header.addText(generatorText); 100 | header.isHeader = true; 101 | table.addRow(header); 102 | const numbers = []; 103 | for(let index = 0; index < tickets; index += 1) { 104 | const row = new UITableRow(); 105 | const number = generator(); 106 | row.addText(number); 107 | numbers.push(number); 108 | table.addRow(row); 109 | } 110 | table.present(); 111 | try { 112 | const reminder = new Reminder(); 113 | const reminderDate = selection === 0 ? nextMegaMillionDate() : nextPowerBallDate(); 114 | reminder.dueDate = reminderDate; 115 | reminder.priority = 1; 116 | reminder.notes = numbers.join('\n'); 117 | reminder.title = `${generatorText} - ${reminderDate.toDateString()}`; 118 | reminder.save(); 119 | } catch(ex) { 120 | console.log(ex); 121 | } 122 | } 123 | alertSelection(await alert.present()) 124 | } 125 | 126 | showLotteryPicker(); 127 | -------------------------------------------------------------------------------- /AQI.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: magic; 4 | // Variables used by Scriptable. 5 | // These must be at the very top of the file. Do not edit. 6 | // icon-color: deep-brown; icon-glyph: magic; 7 | const API_URL = "https://www.purpleair.com/json?show="; 8 | // const CACHE_FILE = "aqi_data.json" 9 | // Find a nearby PurpleAir sensor ID via https://fire.airnow.gov/ 10 | // Click a sensor near your location: the ID is the trailing integers 11 | // https://www.purpleair.com/json has all sensors by location & ID. 12 | let SENSOR_ID = args.widgetParameter || "4896"; 13 | 14 | async function getSensorData(url, id) { 15 | let req = new Request(`${url}${id}`); 16 | let json = await req.loadJSON(); 17 | 18 | return { 19 | val: json.results[0].PM2_5Value, 20 | ts: json.results[0].LastSeen, 21 | loc: json.results[0].Label, 22 | }; 23 | } 24 | 25 | // Widget attributes: AQI level threshold, text label, gradient start and end colors 26 | const levelAttributes = [ 27 | { 28 | threshold: 300, 29 | label: "Hazardous", 30 | startColor: "421434", 31 | endColor: "581845", 32 | }, 33 | { 34 | threshold: 200, 35 | label: "Very Unhealthy", 36 | startColor: "A20F9D", 37 | endColor: "6D0F5E", 38 | }, 39 | { 40 | threshold: 150, 41 | label: "Unhealthy", 42 | startColor: "FF3D3D", 43 | endColor: "D00D0D", 44 | }, 45 | { 46 | threshold: 100, 47 | label: "Unhealthy (S.G.)", 48 | startColor: "F78C06", 49 | endColor: "B96904", 50 | }, 51 | { 52 | threshold: 50, 53 | label: "Moderate", 54 | startColor: "F0D40F", 55 | endColor: "B09B0B", 56 | }, 57 | { 58 | threshold: 0, 59 | label: "Good", 60 | startColor: "116D31", 61 | endColor: "0C4E23", 62 | }, 63 | ]; 64 | 65 | // Get level attributes for AQI 66 | const getLevelAttributes = (level, attributes) => 67 | attributes 68 | .filter((c) => level > c.threshold) 69 | .sort((a, b) => b.threshold - a.threshold)[0]; 70 | 71 | // Calculates the AQI level based on 72 | // https://cfpub.epa.gov/airnow/index.cfm?action=aqibasics.aqi#unh 73 | function calculateLevel(aqi) { 74 | let res = { 75 | level: "OK", 76 | label: "fine", 77 | startColor: "white", 78 | endColor: "white", 79 | }; 80 | 81 | let level = parseInt(aqi, 10) || 0; 82 | 83 | // Set attributes 84 | res = getLevelAttributes(level, levelAttributes); 85 | // Set level 86 | res.level = level; 87 | return res; 88 | } 89 | 90 | //Function to get AQI number from PPM reading 91 | function aqiFromPM(pm) { 92 | if (pm > 350.5) { 93 | return calcAQI(pm, 500.0, 401.0, 500.0, 350.5); 94 | } else if (pm > 250.5) { 95 | return calcAQI(pm, 400.0, 301.0, 350.4, 250.5); 96 | } else if (pm > 150.5) { 97 | return calcAQI(pm, 300.0, 201.0, 250.4, 150.5); 98 | } else if (pm > 55.5) { 99 | return calcAQI(pm, 200.0, 151.0, 150.4, 55.5); 100 | } else if (pm > 35.5) { 101 | return calcAQI(pm, 150.0, 101.0, 55.4, 35.5); 102 | } else if (pm > 12.1) { 103 | return calcAQI(pm, 100.0, 51.0, 35.4, 12.1); 104 | } else if (pm >= 0.0) { 105 | return calcAQI(pm, 50.0, 0.0, 12.0, 0.0); 106 | } else { 107 | return "-"; 108 | } 109 | } 110 | 111 | //Function that actually calculates the AQI number 112 | function calcAQI(Cp, Ih, Il, BPh, BPl) { 113 | let a = Ih - Il; 114 | let b = BPh - BPl; 115 | let c = Cp - BPl; 116 | return Math.round((a / b) * c + Il); 117 | } 118 | 119 | async function run() { 120 | let wg = new ListWidget(); 121 | 122 | try { 123 | console.log(`Using sensor ID: ${SENSOR_ID}`); 124 | let data = await getSensorData(API_URL, SENSOR_ID); 125 | console.log(data); 126 | 127 | let header = wg.addText("Air Quality"); 128 | header.textSize = 15; 129 | header.textColor = Color.black(); 130 | 131 | let aqi = aqiFromPM(data.val); 132 | let level = calculateLevel(aqi); 133 | let aqitext = aqi.toString(); 134 | console.log(aqi); 135 | console.log(level.level); 136 | let startColor = new Color(level.startColor); 137 | let endColor = new Color(level.endColor); 138 | let gradient = new LinearGradient(); 139 | gradient.colors = [startColor, endColor]; 140 | gradient.locations = [0.0, 1]; 141 | console.log(gradient); 142 | 143 | wg.backgroundGradient = gradient; 144 | 145 | let content = wg.addText(aqitext); 146 | content.textSize = 50; 147 | content.textColor = Color.black(); 148 | 149 | let wordLevel = wg.addText(level.label); 150 | wordLevel.textSize = 15; 151 | wordLevel.textColor = Color.black(); 152 | 153 | let id = wg.addText(data.loc); 154 | id.textSize = 10; 155 | id.textColor = Color.black(); 156 | 157 | let updatedAt = new Date(data.ts * 1000).toLocaleTimeString("en-US", { 158 | timeZone: "PST", 159 | }); 160 | let ts = wg.addText(`Updated ${updatedAt}`); 161 | ts.textSize = 10; 162 | ts.textColor = Color.black(); 163 | } catch (e) { 164 | console.log(e); 165 | let err = wg.addText(`error: ${e}`); 166 | err.textSize = 10; 167 | err.textColor = Color.red(); 168 | err.textOpacity = 30; 169 | } 170 | 171 | Script.setWidget(wg); 172 | Script.complete(); 173 | } 174 | await run(); 175 | --------------------------------------------------------------------------------