├── .gitignore ├── README.md ├── scriptable-CaschysBlog.js ├── widget-config.jpeg ├── widget-examples.jpeg ├── widgetConfig.jpeg └── widgetExamples.jpeg /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DIESES SKRIPT WIRD NICHT MEHR LÄNGER VON MIR UNTERSTÜTZT. 2 | # BITTE NUTZT DAS NEUE SKRIPT: 3 | # https://github.com/Saudumm/scriptable-News-Widget 4 | 5 | ## Alte Beschreibung: 6 | 7 | ## scriptable-CaschysBlog 8 | iOS Scriptable Widget für News von Caschys Blog (stadt-bremerhaven.de) 9 | 10 | Das Skript sollte auch für die meisten anderen WordPress Seiten funktionieren. 11 | 12 | ## Voraussetzung: 13 | 14 | Scriptable für iOS: [Link](https://apps.apple.com/de/app/scriptable/id1405459188) 15 | 16 | ## Changelog 17 | 18 | - v1.0 - Erster Upload 19 | - v1.1 - Fix für Anzeige von Datum und Uhrzeit je nach Systemsprache 20 | - v1.2 - Widget kann jetzt mit Parametern angepasst werden, sogar mit Hintergrundbildern! (Siehe Code Kommentare für weitere Infos) 21 | - v1.3 - Bugfix für URLs mit Sonderzeichen 22 | - v1.4 - Eigene Hintergrundbilder für alle Widget Größen 23 | - v1.5 - Code gewaltig aufgeräut, neue Beschreibungen für die Anpassung des Widgets 24 | - v1.5.1 - Fix für die Anzeige des Datums 25 | - v1.5.2 - Twitter Link hinzugefügt 26 | - v1.6.0 - Unschärfeeffekt für Hintergrundbilder (oder Artikelbilder bei kleinen Widgets) 27 | - v1.6.1 - Korrekte Anzeige für Datum/Uhrzeit 28 | 29 | --- 30 | 31 | _Falls mir jemand einen Kaffee ausgeben möchte 😊: https://ko-fi.com/saudumm_ 32 | 33 | --- 34 | 35 | ## Konfiguration: 36 | 37 | 1. Skript zu Scriptable hinzufügen 38 | 2. Widget auf Homescreen erstellen und Script auswählen 39 | 3. Wer möchte, kann die Hintergrundfarbe (oder sogar einen Farbverlauf) direkt im Code anpassen. Der entprechende Teil wurde im Code kommentiert/beschrieben. Hintergrundbilder sind auch möglich. 40 | 41 | Man kann auch das Layout und den Hintergrund des Widgets anpassen. Einfach lange auf das Widget drücken und "Widget bearbeiten" auswählen. 42 | 43 | ![widget-config](https://github.com/Saudumm/scriptable-CaschysBlog/blob/main/widgetConfig.jpeg) 44 | 45 | ### Widget Parameters 46 | - Beispiel: small|https://www.stadt-bremerhaven.de|Caschys Blog|image-name.jpg 47 | - Parameter Reihenfolge: widget size, site url, site name, background image 48 | - Parameter müssen durch | getrennt sein 49 | - Man kann auch Parameter weglassen, zum Beispiel das Hintergrundbild: small|https://www.stadt-bremerhaven.de|Caschys Blog 50 | - Man kann auch nur "small", "medium" oder "large" als Parameter setzen 51 | - Nicht gesetzte Parameter werden im Code durch die Standard Config genutzt 52 | 53 | ## Beispiele: 54 | 55 | ![widget-examples](https://github.com/Saudumm/scriptable-CaschysBlog/blob/main/widgetExamples.jpeg) 56 | 57 | - Oben Links: Kleines Widget mit Standard Parameter 58 | - Oben Mitte: Kleines Widget mit Standard Parameter und Hintergrundbild 59 | - Oben Rechts: Mittleres Widget mit Parameter "medium" 60 | - Mitte Links: Mittleres Widget mit Standard Parameter "small" 61 | - Unten Links: Mittleres Widget mit Parameter "medium" und Hintergrundbild 62 | - Unten Rechts: Großes Widget mit Parameter "large" und Hintergrundbild 63 | -------------------------------------------------------------------------------- /scriptable-CaschysBlog.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: city; 4 | // v1.6.1 coded by Saudumm (https://twitter.com/saudumm) 5 | // GitHub: https://github.com/Saudumm/scriptable-CaschysBlog 6 | 7 | /* WIDGET PARAMETERS: you can long press on the widget on your homescreen and edit parameters 8 | - example: small|https://www.stadt-bremerhaven.de|Caschys Blog|background.jpg|false|true 9 | - parameter order has to be: widget size, site url, site name, background image, blur background image, background image gradient 10 | - parameters have to be separated by | 11 | - You can omit parameters, for example background image: small|https://www.stadt-bremerhaven.de|Caschys Blog 12 | - you can just set "small", "medium" or "large" as a parameter 13 | - parameters that are not set will be set by the standard widget config 14 | */ 15 | 16 | /* STANDARD WIDGET CONFIG: standard config below can be overwritten by widget parameters 17 | WIDGET_SIZE: small, medium, large 18 | SITE_URL: address (URL) of the website you want to fetch posts from 19 | SITE_NAME: name of the website to display in the widget 20 | BG_IMAGE_NAME: CASE SENSITIVE! filename of the custom background image, set to none if you don't want a custom image 21 | - Note: custom background image files have to be in the Scriptable iCloud Files directory (same as the script js file) 22 | BG_IMAGE_BLUR: true if you want background images to be blurred, false if not 23 | BG_IMAGE_GRADIENT: true = gradient over the background image, false = no gradient 24 | */ 25 | var WIDGET_SIZE = "small"; 26 | var SITE_URL = "https://www.stadt-bremerhaven.de"; 27 | var SITE_NAME = "Caschys Blog"; 28 | var BG_IMAGE_NAME = "none"; 29 | var BG_IMAGE_BLUR = "true"; 30 | var BG_IMAGE_GRADIENT = "true"; 31 | 32 | /* COLOR CONFIG: You can edit almost all colors of your widget 33 | SHOW_POST_IMAGES: true = display images next to the post headlines; set to false if you don't want images next to posts 34 | - Note: combining SHOW_POST_IMAGES = true + WIDGET_SIZE = small will ignore BG_GRADIENT_COLOR values in small config widgets 35 | BG_GRADIENT: widget background; true = use gradient; false = single background color 36 | BG_COLOR: background color value if BG_GRADIENT = false 37 | BG_GRADIENT_COLOR_TOP: gradient color at the top 38 | BG_GRADIENT_COLOR_BTM: gradient color at the bottom 39 | BG_GRADIENT_OVERLAY_TOP: gradient background image overlay color top 40 | BG_GRADIENT_OVERLAY_BTM: gradient background image overlay color bottom 41 | FONT_COLOR_SITENAME: font color of the website name (SITE_NAME) 42 | FONT_COLOR_POST_DATE: font color of the date/time label 43 | FONT_COLOR_HEADLINE: font color of the post title 44 | */ 45 | const SHOW_POST_IMAGES = true; 46 | var BG_GRADIENT = false; 47 | const BG_COLOR = new Color("#1c1c1e"); 48 | var BG_GRADIENT_COLOR_TOP = new Color("#222222"); 49 | var BG_GRADIENT_COLOR_BTM = new Color("#444444"); 50 | const BG_GRADIENT_OVERLAY_TOP = new Color("#1c1c1e", 0.3); 51 | const BG_GRADIENT_OVERLAY_BTM = new Color("#1c1c1e", 1.0); 52 | const FONT_COLOR_SITENAME = Color.white(); 53 | const FONT_COLOR_POST_DATE = Color.gray(); 54 | const FONT_COLOR_HEADLINE = Color.white(); 55 | 56 | // DO NOT CHANGE ANYTHING BELOW! 57 | // Unless you know what you're doing. 58 | // Unlike me, I don't know what I'm doing. 59 | if (args.widgetParameter) { 60 | let param = args.widgetParameter.split("|"); 61 | if (param.length >= 1) {WIDGET_SIZE = param[0];} 62 | if (param.length >= 2) {SITE_URL = param[1];} 63 | if (param.length >= 3) {SITE_NAME = param[2];} 64 | if (param.length >= 4) {BG_IMAGE_NAME = param[3];} 65 | if (param.length >= 5) {BG_IMAGE_BLUR = param[4];} 66 | if (param.length >= 6) {BG_IMAGE_GRADIENT = param[5];} 67 | } 68 | 69 | // set the number of posts depending on WIDGET_SIZE 70 | var POST_COUNT = 1; 71 | switch (WIDGET_SIZE) { 72 | case "small": 73 | POST_COUNT = 1; 74 | break; 75 | case "medium": 76 | POST_COUNT = 2; 77 | break; 78 | case "large": 79 | POST_COUNT = 5; 80 | break; 81 | } 82 | 83 | // JSON URL of WP Posts (WordPress Standard) 84 | const JSON_API_URL = SITE_URL+"/wp-json/wp/v2/posts"; 85 | 86 | // check directories 87 | checkFileDirs() 88 | 89 | // Create Widget 90 | let widget = await createWidget(); 91 | 92 | if (!config.runsInWidget) { 93 | switch (WIDGET_SIZE) { 94 | case "small": 95 | await widget.presentSmall(); 96 | break; 97 | case "medium": 98 | await widget.presentMedium(); 99 | break; 100 | case "large": 101 | await widget.presentLarge(); 102 | break; 103 | } 104 | } 105 | 106 | Script.setWidget(widget); 107 | Script.complete(); 108 | 109 | // create the widget 110 | // parameter: none 111 | // result: an awesome widget 112 | async function createWidget() { 113 | const postData = await getData(); 114 | const list = new ListWidget(); 115 | 116 | // display name of the website 117 | const siteName = list.addText(SITE_NAME.toUpperCase()); 118 | siteName.font = Font.heavySystemFont(13); 119 | siteName.textColor = FONT_COLOR_SITENAME; 120 | 121 | list.addSpacer(); 122 | 123 | if (postData) { 124 | if (POST_COUNT == 1) { 125 | // load widget background image (if SHOW_POST_IMAGES = true or BG_IMAGE_NAME is set) 126 | if (SHOW_POST_IMAGES == true && BG_IMAGE_NAME == "none") { 127 | let postBGImage = await loadPostImage(postData.arrPostIMGPaths[0]); 128 | if (BG_IMAGE_BLUR == "true") {postBGImage = await blurImage(postBGImage, 2, 2);} 129 | list.backgroundImage = postBGImage; 130 | 131 | // draw gradient over background image for better readability 132 | BG_GRADIENT = true; 133 | BG_GRADIENT_COLOR_TOP = BG_GRADIENT_OVERLAY_TOP; 134 | BG_GRADIENT_COLOR_BTM = BG_GRADIENT_OVERLAY_BTM; 135 | 136 | // small shadow outline on SITE_NAME for better readability 137 | siteName.shadowRadius = 1; 138 | siteName.shadowColor = Color.black(); 139 | } 140 | 141 | const postStack = list.addStack(); 142 | postStack.layoutVertically(); 143 | 144 | const labelDateTime = postStack.addText(convertJSONDateString(postData.arrPostDates[0])); 145 | labelDateTime.font = Font.heavySystemFont(12); 146 | labelDateTime.textColor = FONT_COLOR_POST_DATE; 147 | labelDateTime.lineLimit = 1; 148 | labelDateTime.minimumScaleFactor = 0.5; 149 | 150 | const labelHeadline = postStack.addText(postData.arrPostTitles[0]); 151 | labelHeadline.font = Font.heavySystemFont(12); 152 | labelHeadline.textColor = FONT_COLOR_HEADLINE; 153 | labelHeadline.lineLimit = 3; 154 | 155 | list.url = postData.arrPostURLs[0]; 156 | } else { 157 | let arrStackRow = []; 158 | arrStackRow.length = POST_COUNT; 159 | let arrStackCol = []; 160 | arrStackCol.length = POST_COUNT; 161 | let arrLblPostDate = []; 162 | arrLblPostDate.length = POST_COUNT; 163 | let arrLblPostTitle = []; 164 | arrLblPostTitle.length = POST_COUNT; 165 | let arrLblPostIMG = []; 166 | arrLblPostIMG.length = POST_COUNT; 167 | 168 | let i; 169 | for (i = 0; i < POST_COUNT; i++) { 170 | arrStackRow[i] = list.addStack(); 171 | arrStackRow[i].layoutHorizontally(); 172 | arrStackRow[i].url = postData.arrPostURLs[i]; 173 | 174 | arrStackCol[i] = arrStackRow[i].addStack(); 175 | arrStackCol[i].layoutVertically(); 176 | 177 | arrLblPostDate[i] = arrStackCol[i].addText(convertJSONDateString(postData.arrPostDates[i])); 178 | arrLblPostDate[i].font = Font.heavySystemFont(12); 179 | arrLblPostDate[i].textColor = FONT_COLOR_POST_DATE; 180 | arrLblPostDate[i].lineLimit = 1; 181 | arrLblPostDate[i].minimumScaleFactor = 0.5; 182 | 183 | arrLblPostTitle[i] = arrStackCol[i].addText(postData.arrPostTitles[i]); 184 | arrLblPostTitle[i].font = Font.heavySystemFont(12); 185 | arrLblPostTitle[i].textColor = FONT_COLOR_HEADLINE; 186 | arrLblPostTitle[i].lineLimit = 2; 187 | 188 | if (SHOW_POST_IMAGES == true) { 189 | arrStackRow[i].addSpacer(); 190 | arrLblPostIMG[i] = await loadSmallPostImage(postData.arrPostIMGPaths[i]); 191 | arrLblPostIMG[i] = await cropImage(arrLblPostIMG[i]); 192 | arrLblPostIMG[i] = arrStackRow[i].addImage(arrLblPostIMG[i]); 193 | arrLblPostIMG[i].imageSize = new Size(45,45); 194 | arrLblPostIMG[i].cornerRadius = 8; 195 | arrLblPostIMG[i].rightAlignImage(); 196 | } 197 | 198 | if (i < POST_COUNT-1) {list.addSpacer();} 199 | } 200 | } 201 | } else { 202 | const err_msg = list.addText("Couldn't load data"); 203 | err_msg.font = Font.regularSystemFont(12); 204 | err_msg.textColor = FONT_COLOR_HEADLINE; 205 | } 206 | 207 | // widget background (single color or gradient) 208 | if (BG_IMAGE_NAME != "none") { 209 | let bgImagePath = await loadBGImage(BG_IMAGE_NAME); 210 | if (bgImagePath != "not found") { 211 | let customBGImage 212 | if (BG_IMAGE_BLUR == "true") { 213 | let fm = await newFileManager(); 214 | customBGImage = await fm.readImage(bgImagePath); 215 | customBGImage = await blurImage(customBGImage, 2, 4); 216 | } else { 217 | customBGImage = await Image.fromFile(bgImagePath); 218 | } 219 | list.backgroundImage = customBGImage; 220 | 221 | if (BG_IMAGE_GRADIENT == "true") { 222 | // draw gradient over background image for better readability 223 | const gradient = new LinearGradient(); 224 | gradient.locations = [0, 1]; 225 | gradient.colors = [BG_GRADIENT_OVERLAY_TOP, BG_GRADIENT_OVERLAY_BTM]; 226 | list.backgroundGradient = gradient; 227 | } 228 | } else { 229 | list.backgroundColor = BG_COLOR; 230 | } 231 | } else if (BG_GRADIENT == true) { 232 | const gradient = new LinearGradient(); 233 | gradient.locations = [0, 1]; 234 | gradient.colors = [BG_GRADIENT_COLOR_TOP, BG_GRADIENT_COLOR_BTM]; 235 | list.backgroundGradient = gradient; 236 | } else { 237 | list.backgroundColor = BG_COLOR; 238 | } 239 | 240 | return list; 241 | } 242 | 243 | // get all the data for the widget - this is where the magic happens 244 | // parameter: nothing at all 245 | // result: a buttload of post data 246 | async function getData() { 247 | try { 248 | let loadedJSON = await new Request(JSON_API_URL).loadJSON(); 249 | 250 | let POSTS_TO_LOAD = 5; 251 | 252 | let arrPostDates = []; 253 | arrPostDates.length = POSTS_TO_LOAD; 254 | let arrPostTitles = []; 255 | arrPostTitles.length = POSTS_TO_LOAD; 256 | let arrPostURLs = []; 257 | arrPostURLs.length = POSTS_TO_LOAD; 258 | let arrPostIMGURLs = []; 259 | arrPostIMGURLs.length = POSTS_TO_LOAD; 260 | let arrPostIMGPaths = []; 261 | arrPostIMGPaths.length = POSTS_TO_LOAD; 262 | let arrPostFileNames = []; 263 | arrPostFileNames.length = POSTS_TO_LOAD; 264 | 265 | let i; 266 | for (i = 0; i < POSTS_TO_LOAD; i++) { 267 | arrPostDates[i] = loadedJSON[i].date; 268 | 269 | arrPostTitles[i] = loadedJSON[i].title.rendered; 270 | arrPostTitles[i] = formatPostTitle(arrPostTitles[i]); 271 | 272 | arrPostURLs[i] = loadedJSON[i].guid.rendered; 273 | 274 | if (SHOW_POST_IMAGES == true) { 275 | arrPostIMGURLs[i] = await getMediaURL(loadedJSON[i].featured_media); 276 | arrPostIMGPaths[i] = await getImagePath(arrPostIMGURLs[i], loadedJSON[i].id); 277 | arrPostFileNames[i] = await getFileName(arrPostIMGURLs[i], loadedJSON[i].id); 278 | await downloadPostImage(arrPostIMGPaths[i], arrPostIMGURLs[i]); 279 | } 280 | } 281 | 282 | if (SHOW_POST_IMAGES == true) {await cleanUpImages(arrPostFileNames);} 283 | 284 | const result = { 285 | arrPostDates: arrPostDates, 286 | arrPostTitles: arrPostTitles, 287 | arrPostURLs: arrPostURLs, 288 | arrPostIMGPaths: arrPostIMGPaths 289 | }; 290 | 291 | return result; 292 | } catch (e) { 293 | return null; 294 | } 295 | } 296 | 297 | // format the post title and replace all html entities with characters 298 | // parameter: string of the title 299 | // result: string with the title, readable by a human 300 | function formatPostTitle(strHeadline) { 301 | strHeadline = strHeadline.replaceAll(""", '"'); 302 | strHeadline = strHeadline.replaceAll("&", "&"); 303 | strHeadline = strHeadline.replaceAll("<", "<"); 304 | strHeadline = strHeadline.replaceAll(">", ">"); 305 | strHeadline = strHeadline.replaceAll(""", '"'); 306 | strHeadline = strHeadline.replaceAll("&", "&"); 307 | strHeadline = strHeadline.replaceAll("<", "<"); 308 | strHeadline = strHeadline.replaceAll(">", ">"); 309 | strHeadline = strHeadline.replaceAll("Œ", "Œ"); 310 | strHeadline = strHeadline.replaceAll("œ", "œ"); 311 | strHeadline = strHeadline.replaceAll("Š", "Š"); 312 | strHeadline = strHeadline.replaceAll("š", "š"); 313 | strHeadline = strHeadline.replaceAll("Ÿ", "Ÿ"); 314 | strHeadline = strHeadline.replaceAll("ˆ", "ˆ"); 315 | strHeadline = strHeadline.replaceAll("˜", "˜"); 316 | strHeadline = strHeadline.replaceAll("–", "–"); 317 | strHeadline = strHeadline.replaceAll("—", "—"); 318 | strHeadline = strHeadline.replaceAll("‘", "‘"); 319 | strHeadline = strHeadline.replaceAll("’", "’"); 320 | strHeadline = strHeadline.replaceAll("‚", "‚"); 321 | strHeadline = strHeadline.replaceAll("“", "“"); 322 | strHeadline = strHeadline.replaceAll("”", "”"); 323 | strHeadline = strHeadline.replaceAll("„", "„"); 324 | strHeadline = strHeadline.replaceAll("†", "†"); 325 | strHeadline = strHeadline.replaceAll("‡", "‡"); 326 | strHeadline = strHeadline.replaceAll("…", "…"); 327 | strHeadline = strHeadline.replaceAll("‰", "‰"); 328 | strHeadline = strHeadline.replaceAll("‹", "‹"); 329 | strHeadline = strHeadline.replaceAll("›", "›"); 330 | strHeadline = strHeadline.replaceAll("€", "€"); 331 | 332 | return strHeadline; 333 | } 334 | 335 | // convert the WordPress date and time to something a human can read 336 | // parameter: string with date and time (from WordPress) 337 | // result: a nicely formatted date and time 338 | function convertJSONDateString(strDate) { 339 | let date_conv = new Date(strDate); 340 | let dateTimeLocal = date_conv.toLocaleString([], {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"}) 341 | return dateTimeLocal 342 | } 343 | 344 | // get the featuredMedia image URL 345 | // parameter: featureMedia ID 346 | // result: encoded URL to the image file on the server 347 | async function getMediaURL(featuredMedia) { 348 | let featuredMediaJSONURL = SITE_URL+"/wp-json/wp/v2/media/"+featuredMedia; 349 | let loadedMediaJSON = await new Request(featuredMediaJSONURL).loadJSON(); 350 | let mediaURL = loadedMediaJSON.source_url; 351 | mediaURL = await encodeURI(mediaURL); 352 | return mediaURL; 353 | } 354 | 355 | // set the filename of the post image (site name + image id + file extension) 356 | // parameter: url to the image, id of the image 357 | // result: filename of the image 358 | function getFileName(url, id) { 359 | let extension = /(?:\.([^.]+))?$/.exec(url)[0]; 360 | let site_id = SITE_NAME.replace(/[^a-zA-Z]+/g, "").toLowerCase(); 361 | return site_id+"-"+id+extension; 362 | } 363 | 364 | // set the complete file path for the image 365 | // parameter: url to the image, id of the image 366 | // result: local filepath of the image 367 | function getImagePath(url, id) { 368 | let fm = newFileManager(); 369 | 370 | let docDir = fm.documentsDirectory(); 371 | let postImageDir = docDir+"/wordpress-widget-data/post-images"; 372 | 373 | // set fileName 374 | let fileName = getFileName(url, id); 375 | // join path 376 | let path = fm.joinPath(postImageDir, fileName); 377 | 378 | return path; 379 | } 380 | 381 | // download the post image (if it doesn't already exist) 382 | // parameter: path to the image, url to the image on the interwebs 383 | // result: a nice and warm feeling 384 | async function downloadPostImage(path, url) { 385 | let fm = newFileManager(); 386 | 387 | let image; 388 | 389 | // check if file already exists 390 | if (fm.fileExists(path)) { 391 | await fm.downloadFileFromiCloud(path); 392 | } else { 393 | // download and store image 394 | let req = await new Request(url); 395 | let loadedImage = await req.load(); 396 | 397 | await fm.write(path, loadedImage); 398 | } 399 | 400 | return; 401 | } 402 | 403 | // load post image from file path 404 | // parameter: path to the image 405 | // result: image 406 | function loadPostImage(path) { 407 | let fm = newFileManager(); 408 | if (fm.fileExists(path)) { 409 | fm.downloadFileFromiCloud(path); 410 | let image = fm.readImage(path); 411 | 412 | return image; 413 | } 414 | } 415 | 416 | // load post image from file path and resize it 417 | // parameter: path to the image 418 | // result: image 419 | function loadSmallPostImage(path) { 420 | let fm = newFileManager(); 421 | if (fm.fileExists(path)) { 422 | fm.downloadFileFromiCloud(path); 423 | let image = fm.readImage(path); 424 | image = resizeImage(image, 4); 425 | 426 | return image; 427 | } 428 | } 429 | 430 | // search for and load a local (or iCloud) background image 431 | // parameter: filename of the image (case sensitive!) 432 | // result: path to the image or "not found" if image doesn't exist 433 | async function loadBGImage(imageName) { 434 | let fm = newFileManager(); 435 | let docDir = fm.documentsDirectory(); 436 | let bgImageDir = docDir+"/wordpress-widget-data/background-images"; 437 | 438 | let bgImagePathDocDir = fm.joinPath(docDir, imageName) 439 | let bgImagePathBGDir = fm.joinPath(bgImageDir, imageName) 440 | 441 | if (fm.fileExists(bgImagePathDocDir)) { 442 | return bgImagePathDocDir; 443 | } else if (fm.fileExists(bgImagePathBGDir)) { 444 | return bgImagePathBGDir; 445 | } else { 446 | return "not found"; 447 | } 448 | } 449 | 450 | // create a new FileManager (iCloud or local) 451 | // parameter: nothing 452 | // result: FileManager 453 | function newFileManager() { 454 | let fm; 455 | try { 456 | fm = FileManager.iCloud(); 457 | } catch(e) { 458 | fm = FileManager.local(); 459 | } 460 | return fm; 461 | } 462 | 463 | // check if all folders are available and create them if needed 464 | // parameter: none 465 | // result: new directories if needed 466 | function checkFileDirs() { 467 | // Create new FileManager and set data dir 468 | let fm = newFileManager(); 469 | let docDir = fm.documentsDirectory(); 470 | let postImageDir = docDir+"/wordpress-widget-data/post-images"; 471 | let bgImageDir = docDir+"/wordpress-widget-data/background-images"; 472 | 473 | if (!fm.fileExists(postImageDir)) {fm.createDirectory(postImageDir, true);} 474 | if (!fm.fileExists(bgImageDir)) {fm.createDirectory(bgImageDir, true);} 475 | } 476 | 477 | // cleanup post image files 478 | // parameter: array with image filenames that are needed at the moment 479 | // result: a nice and clean data folder 480 | function cleanUpImages(arrFileNames) { 481 | let fm = newFileManager(); 482 | let docDir = fm.documentsDirectory(); 483 | let postImageDir = docDir+"/wordpress-widget-data/post-images"; 484 | 485 | let arrFiles = fm.listContents(postImageDir); 486 | 487 | let site_id = SITE_NAME.replace(/[^a-zA-Z]+/g, "").toLowerCase(); 488 | 489 | let arrFilesSite = []; 490 | 491 | for (i = 0; i < arrFiles.length; i++) { 492 | if (arrFiles[i].substring(0, site_id.length) === site_id) {arrFilesSite.push(arrFiles[i]);} 493 | } 494 | 495 | for (i = 0; i < arrFilesSite.length; i++) { 496 | if (!arrFileNames.includes(arrFilesSite[i])) { 497 | let path = fm.joinPath(postImageDir, arrFilesSite[i]); 498 | fm.remove(path); 499 | } 500 | } 501 | } 502 | 503 | // blur the background image 504 | // parameter: image, downscale factor, strength of blur effect 505 | // result: blurry image (well, it's better than nothing) 506 | async function blurImage(img, resFactor, blurStrength) { 507 | /* 508 | A big THANK YOU to Mario Klingemann for the Blur Code and Max Zeryck for the WebView Code 509 | code taken and modified from: https://github.com/mzeryck/Widget-Blur 510 | Follow @mzeryck on Twitter: https://twitter.com/mzeryck 511 | */ 512 | 513 | const js = ` 514 | /* 515 | StackBlur - a fast almost Gaussian Blur For Canvas 516 | Version: 0.5 517 | Author: Mario Klingemann 518 | Contact: mario@quasimondo.com 519 | Website: http://quasimondo.com/StackBlurForCanvas/StackBlurDemo.html 520 | Twitter: @quasimondo 521 | In case you find this class useful - especially in commercial projects - 522 | I am not totally unhappy for a small donation to my PayPal account 523 | mario@quasimondo.de 524 | Or support me on flattr: 525 | https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript 526 | Copyright (c) 2010 Mario Klingemann 527 | Permission is hereby granted, free of charge, to any person 528 | obtaining a copy of this software and associated documentation 529 | files (the "Software"), to deal in the Software without 530 | restriction, including without limitation the rights to use, 531 | copy, modify, merge, publish, distribute, sublicense, and/or sell 532 | copies of the Software, and to permit persons to whom the 533 | Software is furnished to do so, subject to the following 534 | conditions: 535 | The above copyright notice and this permission notice shall be 536 | included in all copies or substantial portions of the Software. 537 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 538 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 539 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 540 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 541 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 542 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 543 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 544 | OTHER DEALINGS IN THE SOFTWARE. 545 | */ 546 | var mul_table = [512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, 547 | 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, 548 | 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, 549 | 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, 550 | 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, 551 | 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, 552 | 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, 553 | 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, 554 | 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, 555 | 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, 556 | 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, 557 | 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, 558 | 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, 559 | 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, 560 | 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, 561 | 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259]; 562 | 563 | var shg_table = [ 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 564 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 565 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 566 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 567 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 568 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 569 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 570 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 571 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 572 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 573 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 574 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 575 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 576 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 577 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 578 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ]; 579 | 580 | function stackBlurCanvasRGB(id, top_x, top_y, width, height, radius) { 581 | if (isNaN(radius) || radius < 1) {return;} 582 | radius |= 0; 583 | 584 | var canvas = document.getElementById(id); 585 | var context = canvas.getContext("2d"); 586 | var imageData; 587 | 588 | try { 589 | imageData = context.getImageData(top_x, top_y, width, height); 590 | } catch(e) { 591 | alert("Cannot access image"); 592 | throw new Error("unable to access image data: " + e); 593 | } 594 | 595 | var pixels = imageData.data; 596 | 597 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, 598 | r_out_sum, g_out_sum, b_out_sum, 599 | r_in_sum, g_in_sum, b_in_sum, 600 | pr, pg, pb, rbs; 601 | 602 | var div = radius + radius + 1; 603 | var w4 = width << 2; 604 | var widthMinus1 = width - 1; 605 | var heightMinus1 = height - 1; 606 | var radiusPlus1 = radius + 1; 607 | var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2; 608 | 609 | var stackStart = new BlurStack(); 610 | var stack = stackStart; 611 | for (i = 1; i < div; i++) { 612 | stack = stack.next = new BlurStack(); 613 | if (i == radiusPlus1) var stackEnd = stack; 614 | } 615 | stack.next = stackStart; 616 | var stackIn = null; 617 | var stackOut = null; 618 | 619 | yw = yi = 0; 620 | 621 | var mul_sum = mul_table[radius]; 622 | var shg_sum = shg_table[radius]; 623 | 624 | for (y = 0; y < height; y++) { 625 | r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0; 626 | 627 | r_out_sum = radiusPlus1 * (pr = pixels[yi]); 628 | g_out_sum = radiusPlus1 * (pg = pixels[yi+1]); 629 | b_out_sum = radiusPlus1 * (pb = pixels[yi+2]); 630 | 631 | r_sum += sumFactor * pr; 632 | g_sum += sumFactor * pg; 633 | b_sum += sumFactor * pb; 634 | 635 | stack = stackStart; 636 | 637 | for (i = 0; i < radiusPlus1; i++) { 638 | stack.r = pr; 639 | stack.g = pg; 640 | stack.b = pb; 641 | stack = stack.next; 642 | } 643 | 644 | for (i = 1; i < radiusPlus1; i++) { 645 | p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); 646 | r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i); 647 | g_sum += (stack.g = (pg = pixels[p+1])) * rbs; 648 | b_sum += (stack.b = (pb = pixels[p+2])) * rbs; 649 | 650 | r_in_sum += pr; 651 | g_in_sum += pg; 652 | b_in_sum += pb; 653 | 654 | stack = stack.next; 655 | } 656 | 657 | 658 | stackIn = stackStart; 659 | stackOut = stackEnd; 660 | for (x = 0; x < width; x++) { 661 | pixels[yi] = (r_sum * mul_sum) >> shg_sum; 662 | pixels[yi+1] = (g_sum * mul_sum) >> shg_sum; 663 | pixels[yi+2] = (b_sum * mul_sum) >> shg_sum; 664 | 665 | r_sum -= r_out_sum; 666 | g_sum -= g_out_sum; 667 | b_sum -= b_out_sum; 668 | 669 | r_out_sum -= stackIn.r; 670 | g_out_sum -= stackIn.g; 671 | b_out_sum -= stackIn.b; 672 | 673 | p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2; 674 | 675 | r_in_sum += (stackIn.r = pixels[p]); 676 | g_in_sum += (stackIn.g = pixels[p+1]); 677 | b_in_sum += (stackIn.b = pixels[p+2]); 678 | 679 | r_sum += r_in_sum; 680 | g_sum += g_in_sum; 681 | b_sum += b_in_sum; 682 | 683 | stackIn = stackIn.next; 684 | 685 | r_out_sum += (pr = stackOut.r); 686 | g_out_sum += (pg = stackOut.g); 687 | b_out_sum += (pb = stackOut.b); 688 | 689 | r_in_sum -= pr; 690 | g_in_sum -= pg; 691 | b_in_sum -= pb; 692 | 693 | stackOut = stackOut.next; 694 | yi += 4; 695 | } 696 | yw += width; 697 | } 698 | 699 | for (x = 0; x < width; x++) { 700 | g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0; 701 | 702 | yi = x << 2; 703 | r_out_sum = radiusPlus1 * (pr = pixels[yi]); 704 | g_out_sum = radiusPlus1 * (pg = pixels[yi+1]); 705 | b_out_sum = radiusPlus1 * (pb = pixels[yi+2]); 706 | 707 | r_sum += sumFactor * pr; 708 | g_sum += sumFactor * pg; 709 | b_sum += sumFactor * pb; 710 | 711 | stack = stackStart; 712 | 713 | for (i = 0; i < radiusPlus1; i++) { 714 | stack.r = pr; 715 | stack.g = pg; 716 | stack.b = pb; 717 | stack = stack.next; 718 | } 719 | 720 | yp = width; 721 | 722 | for (i = 1; i <= radius; i++) { 723 | yi = (yp + x) << 2; 724 | 725 | r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i); 726 | g_sum += (stack.g = (pg = pixels[yi+1])) * rbs; 727 | b_sum += (stack.b = (pb = pixels[yi+2])) * rbs; 728 | 729 | r_in_sum += pr; 730 | g_in_sum += pg; 731 | b_in_sum += pb; 732 | 733 | stack = stack.next; 734 | 735 | if (i < heightMinus1) {yp += width;} 736 | } 737 | 738 | yi = x; 739 | stackIn = stackStart; 740 | stackOut = stackEnd; 741 | for (y = 0; y < height; y++) { 742 | p = yi << 2; 743 | pixels[p] = (r_sum * mul_sum) >> shg_sum; 744 | pixels[p+1] = (g_sum * mul_sum) >> shg_sum; 745 | pixels[p+2] = (b_sum * mul_sum) >> shg_sum; 746 | 747 | r_sum -= r_out_sum; 748 | g_sum -= g_out_sum; 749 | b_sum -= b_out_sum; 750 | 751 | r_out_sum -= stackIn.r; 752 | g_out_sum -= stackIn.g; 753 | b_out_sum -= stackIn.b; 754 | 755 | p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2; 756 | 757 | r_sum += (r_in_sum += (stackIn.r = pixels[p])); 758 | g_sum += (g_in_sum += (stackIn.g = pixels[p+1])); 759 | b_sum += (b_in_sum += (stackIn.b = pixels[p+2])); 760 | 761 | stackIn = stackIn.next; 762 | 763 | r_out_sum += (pr = stackOut.r); 764 | g_out_sum += (pg = stackOut.g); 765 | b_out_sum += (pb = stackOut.b); 766 | 767 | r_in_sum -= pr; 768 | g_in_sum -= pg; 769 | b_in_sum -= pb; 770 | 771 | stackOut = stackOut.next; 772 | 773 | yi += width; 774 | } 775 | } 776 | 777 | context.putImageData(imageData, top_x, top_y); 778 | } 779 | 780 | function BlurStack() { 781 | this.r = 0; 782 | this.g = 0; 783 | this.b = 0; 784 | this.a = 0; 785 | this.next = null; 786 | } 787 | 788 | // Set up the canvas 789 | const img = document.getElementById("blurImg"); 790 | const canvas = document.getElementById("mainCanvas"); 791 | const w = img.naturalWidth; 792 | const h = img.naturalHeight; 793 | const maxW = w / ${resFactor}; 794 | const maxH = h / ${resFactor}; 795 | canvas.style.width = w + "px"; 796 | canvas.style.height = h + "px"; 797 | canvas.width = maxW; 798 | canvas.height = maxH; 799 | const context = canvas.getContext("2d"); 800 | context.clearRect(0, 0, w, h); 801 | context.drawImage(img, 0, 0, maxW, maxH); 802 | 803 | // Get the image data from the context 804 | var imageData = context.getImageData(0,0,w,h); 805 | // Draw over the old image 806 | context.putImageData(imageData,0,0); 807 | // Blur the image 808 | stackBlurCanvasRGB("mainCanvas", 0, 0, w, h, ${blurStrength}); 809 | // Return a base64 representation 810 | canvas.toDataURL(); 811 | `; 812 | 813 | // Convert the images and create the HTML 814 | let blurImgData = Data.fromPNG(img).toBase64String(); 815 | let html = ``; 816 | 817 | // Make the web view and get its return value 818 | let view = new WebView(); 819 | await view.loadHTML(html); 820 | let returnValue = await view.evaluateJavaScript(js); 821 | 822 | // Remove the data type from the string and convert to data 823 | let imageDataString = returnValue.slice(22); 824 | let imageData = Data.fromBase64String(imageDataString); 825 | 826 | // Convert to image before returning 827 | let imageFromData = Image.fromData(imageData); 828 | return imageFromData; 829 | } 830 | 831 | // resize the background image 832 | // parameter: image, downscale factor 833 | // result: resized image (duh) 834 | async function resizeImage(img, resFactor) { 835 | const js = ` 836 | // Set up the canvas 837 | const img = document.getElementById("resImg"); 838 | const canvas = document.getElementById("mainCanvas"); 839 | const w = img.naturalWidth; 840 | const h = img.naturalHeight; 841 | const maxW = w / ${resFactor}; 842 | const maxH = h / ${resFactor}; 843 | canvas.style.width = w + "px"; 844 | canvas.style.height = h + "px"; 845 | canvas.width = maxW; 846 | canvas.height = maxH; 847 | const context = canvas.getContext("2d"); 848 | context.clearRect(0, 0, w, h); 849 | context.drawImage(img, 0, 0, maxW, maxH); 850 | 851 | // Get the image data from the context 852 | var imageData = context.getImageData(0,0,w,h); 853 | // Draw over the old image 854 | context.putImageData(imageData,0,0); 855 | // Return a base64 representation 856 | canvas.toDataURL(); 857 | `; 858 | 859 | // Convert the images and create the HTML 860 | let resImgData = Data.fromPNG(img).toBase64String(); 861 | let html = ``; 862 | 863 | // Make the web view and get its return value 864 | let view = new WebView(); 865 | await view.loadHTML(html); 866 | let returnValue = await view.evaluateJavaScript(js); 867 | 868 | // Remove the data type from the string and convert to data 869 | let imageDataString = returnValue.slice(22); 870 | let imageData = Data.fromBase64String(imageDataString); 871 | 872 | // Convert to image before returning 873 | let imageFromData = Image.fromData(imageData); 874 | 875 | return imageFromData; 876 | } 877 | 878 | // crop the image to a rectangle (to be honest, it's a square) 879 | // parameter: image 880 | // result: square image 881 | function cropImage(img) { 882 | let height = img.size.height; 883 | let width = img.size.width; 884 | 885 | let imgShortSide = Math.min(height, width); 886 | let imgLongSide = Math.max(height, width); 887 | 888 | if (imgShortSide != imgLongSide) { 889 | let imgCropTotal = imgLongSide - imgShortSide; 890 | let imgCropSide = Math.floor(imgCropTotal / 2); 891 | 892 | let rect; 893 | switch (imgShortSide) { 894 | case height: 895 | rect = new Rect(imgCropSide, 0, imgShortSide, imgShortSide); 896 | break; 897 | case width: 898 | rect = new Rect(0, imgCropSide, imgShortSide, imgShortSide); 899 | break; 900 | } 901 | 902 | let draw = new DrawContext(); 903 | draw.size = new Size(rect.width, rect.height); 904 | 905 | draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y)); 906 | img = draw.getImage(); 907 | } 908 | 909 | return img; 910 | } 911 | -------------------------------------------------------------------------------- /widget-config.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saudumm/scriptable-CaschysBlog/e0bbcf82812cc00aeb1b2b1067ef9c4a41e9b82e/widget-config.jpeg -------------------------------------------------------------------------------- /widget-examples.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saudumm/scriptable-CaschysBlog/e0bbcf82812cc00aeb1b2b1067ef9c4a41e9b82e/widget-examples.jpeg -------------------------------------------------------------------------------- /widgetConfig.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saudumm/scriptable-CaschysBlog/e0bbcf82812cc00aeb1b2b1067ef9c4a41e9b82e/widgetConfig.jpeg -------------------------------------------------------------------------------- /widgetExamples.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saudumm/scriptable-CaschysBlog/e0bbcf82812cc00aeb1b2b1067ef9c4a41e9b82e/widgetExamples.jpeg --------------------------------------------------------------------------------