Guest User

Untitled

a guest
Oct 15th, 2020
222
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.44 KB | None | 0 0
  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: cloud;
  4.  
  5. // Widget Params
  6. // Don't edit this, those are default values for debugging (location for Cupertino).
  7. // You need to give your locations parameters through the widget params, more info below.
  8. const widgetParams = JSON.parse((args.widgetParameter != null) ? args.widgetParameter : '{ "LAT" : "40.7143" , "LON" : "-74.006" , "LOC_NAME" : "New York, US" }')
  9.  
  10. // WEATHER API PARAMETERS !important
  11. // API KEY, you need an Open Weather API Key
  12. // You can get one for free at: https://home.openweathermap.org/api_keys (account needed).
  13. const API_KEY = ""
  14.  
  15. // Latitude and Longitude of the location where you get the weather of.
  16. // You can get those from the Open Weather website while searching for a city, etc.
  17. // This values are getted from the widget parameters, the widget parameters is a JSON string that looks like this:
  18. // { "LAT" : "<latitude>" , "LON" : "<longitude>" , "LOC_NAME" : "<name to display>" }
  19. // This to allow multiple instances of the widget with different locations, if you will only use one instance (1 widget), you can "hardcode" the values here.
  20. // Note: To debug the widget you need to place the values here, because when playing the script in-app the widget parameters are null (= crash).
  21. const LAT = widgetParams.LAT
  22. const LON = widgetParams.LON
  23. const LOCATION_NAME = widgetParams.LOC_NAME
  24.  
  25. // Looking settings
  26. // This are settings to customize the looking of the widgets, because this was made an iPhone SE (2016) screen, I can't test for bigger screens.
  27. // So feel free to modify this to your taste.
  28.  
  29. // units : string > Defines the unit used to measure the temps, for temperatures in Fahrenheit use "imperial", "metric" for Celcius and "standard" for Kelvin (Default: "metric").
  30. const units = "imperial"
  31. // roundedGraph : true|false > true (Use rounded values to draw the graph) | false (Draws the graph using decimal values, this can be used to draw an smoother line).
  32. const roundedGraph = true
  33. // roundedTemp : true|false > true (Displays the temps rounding the values (29.8 = 30 | 29.3 = 29).
  34. const roundedTemp = true
  35. // hoursToShow : number > Number of predicted hours to show, Eg: 3 = a total of 4 hours in the widget (Default: 3 for the small widget and 11 for the medium one).
  36. const hoursToShow = (config.widgetFamily == "small") ? 3 : 11;
  37. // spaceBetweenDays : number > Size of the space between the temps in the graph in pixels. (Default: 60 for the small widget and 44 for the medium one).
  38. const spaceBetweenDays = (config.widgetFamily == "small") ? 60 : 44;
  39.  
  40. // Widget Size !important.
  41. // Since the widget works "making" an image and displaying it as the widget background, you need to specify the exact size of the widget to
  42. // get an 1:1 display ratio, if you specify an smaller size than the widget itself it will be displayed blurry.
  43. // You can get the size simply taking an screenshot of your widgets on the home screen and measuring them in an image-proccessing software.
  44. // contextSize : number > Height of the widget in screen pixels, this depends on you screen size (for an 4 inch display the small widget is 282 * 282 pixels on the home screen)
  45. const contextSize = 282
  46. // mediumWidgetWidth : number > Width of the medium widget in pixels, this depends on you screen size (for an 4 inch display the medium widget is 584 pixels long on the home screen)
  47. const mediumWidgetWidth = 584
  48.  
  49. // accentColor : Color > Accent color of some elements (Graph lines and the location label).
  50. const accentColor = new Color("#6EB9D9", 1)
  51. // backgroundColor : Color > Background color of the widgets.
  52. const backgroundColor = new Color("#2A2929", 1)
  53.  
  54. // Position and size of the elements on the widget.
  55. // All coordinates make reference to the top-left of the element.
  56. // locationNameCoords : Point > Define the position in pixels of the location label.
  57. const locationNameCoords = new Point(30, 30)
  58. // locationNameFontSize : number > Size in pixels of the font of the location label.
  59. const locationNameFontSize = 24
  60. // weatherDescriptionCoords : Point > Position of the weather description label in pixels.
  61. const weatherDescriptionCoords = new Point(30, 52)
  62. // weatherDescriptionFontSize : number > Font size of the weather description label.
  63. const weatherDescriptionFontSize = 18
  64. //footerFontSize : number > Font size of the footer labels (feels like... and last update time).
  65. const footerFontSize = 20
  66. //feelsLikeCoords : Point > Coordinates of the "feels like" label.
  67. const feelsLikeCoords = new Point(30, 230)
  68. //lastUpdateTimePosAndSize : Rect > Defines the coordinates and size of the last updated time label.
  69. const lastUpdateTimePosAndSize = new Rect((config.widgetFamily == "small") ? 150 : 450, 230, 100, footerFontSize+1)
  70.  
  71. // Prepare for the SFSymbol request by getting sunset/sunrise times.
  72. const date = new Date()
  73. const sunData = await new Request("https://api.sunrise-sunset.org/json?lat=" + LAT + "&lng=" + LON + "&formatted=0&date=" + date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()).loadJSON();
  74.  
  75. //From here proceed with caution.
  76. let fm = FileManager.iCloud();
  77. let cachePath = fm.joinPath(fm.documentsDirectory(), "weatherCache");
  78. if(!fm.fileExists(cachePath)){
  79. fm.createDirectory(cachePath)
  80. }
  81.  
  82.  
  83.  
  84. let weatherData;
  85. let usingCachedData = false;
  86. let drawContext = new DrawContext();
  87.  
  88. let dir = fm.documentsDirectory()
  89. let path = fm.joinPath(dir, 'image1.PNG')
  90. let imageFile = fm.readImage(path)
  91.  
  92.  
  93. drawContext.size = new Size((config.widgetFamily == "small") ? contextSize : mediumWidgetWidth, contextSize)
  94. drawContext.opaque = true
  95. drawContext.setTextAlignedCenter()
  96. drawContext.drawImageInRect(imageFile, new Rect(0, 0, 585, 280))
  97.  
  98.  
  99.  
  100. try {
  101. weatherData = await new Request("https://api.openweathermap.org/data/2.5/onecall?lat=" + LAT + "&lon=" + LON + "&exclude=daily,minutely,alerts&units=" + units + "&lang=en&appid=" + API_KEY).loadJSON();
  102. fm.writeString(fm.joinPath(cachePath, "lastread"), JSON.stringify(weatherData));
  103. }catch(e){
  104. console.log("Offline mode")
  105. try{
  106. let raw = fm.readString(fm.joinPath(cachePath, "lastread"));
  107. weatherData = JSON.parse(raw);
  108. usingCachedData = true;
  109. }catch(e2){
  110. console.log("Error: No offline data cached")
  111. }
  112. }
  113.  
  114. let widget = new ListWidget();
  115. widget.setPadding(0, 0, 0, 0);
  116. widget.backgroundColor = backgroundColor;
  117.  
  118. drawText(LOCATION_NAME, locationNameFontSize, locationNameCoords.x, locationNameCoords.y, accentColor);
  119. drawText(weatherData.current.weather[0].description, weatherDescriptionFontSize, weatherDescriptionCoords.x, weatherDescriptionCoords.y, Color.white())
  120.  
  121. let min, max, diff;
  122. for(let i = 0; i<=hoursToShow ;i++){
  123. let temp = shouldRound(roundedGraph, weatherData.hourly[i].temp);
  124. min = (temp < min || min == undefined ? temp : min)
  125. max = (temp > max || max == undefined ? temp : max)
  126. }
  127. diff = max -min;
  128.  
  129. for(let i = 0; i<=hoursToShow ;i++){
  130. let hourData = weatherData.hourly[i];
  131. let nextHourTemp = shouldRound(roundedGraph, weatherData.hourly[i+1].temp);
  132. let hour = epochToDate(hourData.dt).getHours();
  133. hour = (hour > 12 ? hour - 12 : (hour == 0 ? "12a" : ((hour == 12) ? "12p" : hour)))
  134. let temp = i==0?weatherData.current.temp : hourData.temp
  135. let delta = (diff>0)?(shouldRound(roundedGraph, temp) - min) / diff:0.5;
  136. let nextDelta = (diff>0)?(nextHourTemp - min) / diff:0.5
  137.  
  138. if(i < hoursToShow)
  139. drawLine(spaceBetweenDays * (i) + 50, 175 - (50 * delta),spaceBetweenDays * (i+1) + 50 , 175 - (50 * nextDelta), 4, (hourData.dt > weatherData.current.sunset? Color.gray():accentColor))
  140.  
  141. drawTextC(shouldRound(roundedTemp, temp)+"°", 20, spaceBetweenDays*i+30, 135 - (50*delta), 50, 21, Color.white())
  142.  
  143. // The next three lines were modified for SFSymbol support.
  144. const condition = i==0?weatherData.current.weather[0].id:hourData.weather[0].id
  145. const condDate = i==0?weatherData.current.dt:hourData.dt
  146. drawImage(symbolForCondition(condition,condDate), spaceBetweenDays * i + 40, 165 - (50*delta));
  147.  
  148. drawTextC((i==0?"Now":hour), 18, spaceBetweenDays*i+25, 200,50, 21, Color.gray())
  149.  
  150. previousDelta = delta;
  151. }
  152.  
  153. drawText("feels like " + Math.round(weatherData.current.feels_like) + "°", footerFontSize, feelsLikeCoords.x, feelsLikeCoords.y, Color.gray())
  154.  
  155. drawContext.setTextAlignedRight();
  156. drawTextC(epochToDate(weatherData.current.dt).toLocaleTimeString(), footerFontSize, lastUpdateTimePosAndSize.x, lastUpdateTimePosAndSize.y, lastUpdateTimePosAndSize.width, lastUpdateTimePosAndSize.height, Color.gray())
  157.  
  158. widget.backgroundImage = (drawContext.getImage())
  159. widget.presentMedium()
  160.  
  161. async function loadImage(imgName){
  162. if(fm.fileExists(fm.joinPath(cachePath, imgName))){
  163. return Image.fromData(Data.fromFile(fm.joinPath(cachePath, imgName)))
  164. }else{
  165. let imgdata = await new Request("https://openweathermap.org/img/wn/"+imgName+".png").load();
  166. let img = Image.fromData(imgdata);
  167. fm.write(fm.joinPath(cachePath, imgName), imgdata);
  168. return img;
  169. }
  170. }
  171.  
  172. function epochToDate(epoch){
  173. return new Date(epoch * 1000)
  174. }
  175.  
  176. function drawText(text, fontSize, x, y, color = Color.black()){
  177. drawContext.setFont(Font.boldSystemFont(fontSize))
  178. drawContext.setTextColor(color)
  179. drawContext.drawText(new String(text).toString(), new Point(x, y))
  180. }
  181.  
  182. function drawImage(image, x, y){
  183. drawContext.drawImageAtPoint(image, new Point(x, y))
  184. }
  185.  
  186. function drawTextC(text, fontSize, x, y, w, h, color = Color.black()){
  187. drawContext.setFont(Font.boldSystemFont(fontSize))
  188. drawContext.setTextColor(color)
  189. drawContext.drawTextInRect(new String(text).toString(), new Rect(x, y, w, h))
  190. }
  191.  
  192. function drawLine(x1, y1, x2, y2, width, color){
  193. const path = new Path()
  194. path.move(new Point(x1, y1))
  195. path.addLine(new Point(x2, y2))
  196. drawContext.addPath(path)
  197. drawContext.setStrokeColor(color)
  198. drawContext.setLineWidth(width)
  199. drawContext.strokePath()
  200. }
  201.  
  202. function shouldRound(should, value){
  203. return ((should) ? Math.round(value) : value)
  204. }
  205.  
  206. // This function returns an SFSymbol image for a weather condition.
  207. function symbolForCondition(cond,condDate) {
  208.  
  209. const sunrise = new Date(sunData.results.sunrise).getTime()
  210. const sunset = new Date(sunData.results.sunset).getTime()
  211. const timeValue = condDate * 1000
  212.  
  213. // Is it night at the provided date?
  214. const night = (timeValue < sunrise) || (timeValue > sunset)
  215.  
  216. // Define our symbol equivalencies.
  217. let symbols = {
  218.  
  219. // Thunderstorm
  220. "2": function() {
  221. return "cloud.bolt.rain.fill"
  222. },
  223.  
  224. // Drizzle
  225. "3": function() {
  226. return "cloud.drizzle.fill"
  227. },
  228.  
  229. // Rain
  230. "5": function() {
  231. return (cond == 511) ? "cloud.sleet.fill" : "cloud.rain.fill"
  232. },
  233.  
  234. // Snow
  235. "6": function() {
  236. return (cond >= 611 && cond <= 613) ? "cloud.snow.fill" : "snow"
  237. },
  238.  
  239. // Atmosphere
  240. "7": function() {
  241. if (cond == 781) { return "tornado" }
  242. if (cond == 701 || cond == 741) { return "cloud.fog.fill" }
  243. return night ? "cloud.fog.fill" : "sun.haze.fill"
  244. },
  245.  
  246. // Clear and clouds
  247. "8": function() {
  248. if (cond == 800) { return night ? "moon.stars.fill" : "sun.max.fill" }
  249. if (cond == 802 || cond == 803) { return night ? "cloud.moon.fill" : "cloud.sun.fill" }
  250. return "cloud.fill"
  251. }
  252. }
  253.  
  254. // Find out the first digit.
  255. let conditionDigit = Math.floor(cond / 100)
  256.  
  257. // Get the symbol.
  258. return SFSymbol.named(symbols[conditionDigit]()).image
  259.  
  260. }
  261.  
  262. Script.complete()
Add Comment
Please, Sign In to add comment