-
-
Save marco79cgn/fa9cd9a3423be4500a20a54cb783f4c0 to your computer and use it in GitHub Desktop.
// this Scriptable Widget is coded by Slowlydev (aka r/Sl0wly-edits, r/Slowlydev) and adapted by @marco79 | |
const DEV_MODE = false //for developer only | |
const DEV_PREVIEW = "medium" //for developer only (this script is specialy made for a medium sized widget) | |
const API_KEY = "" // enter your openweathermap.com api key | |
const FORECAST_HOURS = "3" | |
const UNITS = "metric" //metric for celsius and imperial for Fahrenheit | |
const CALENDAR_URL = "calshow://" //Apple Calendar App, if your favorite app does have a URL scheme feel free to change it | |
const WEATHER_URL = "" //there is no URL for the Apple Weather App, if your favorite app does feel free to add it | |
const widgetBackground = new Color("#D6D6D6") //Widget Background | |
const stackBackground = new Color("#FFFFFF") //Smaller Container Background | |
const calendarColor = new Color("#EA3323") //Calendar Color | |
const stackSize = new Size(0, 65) //0 means its automatic | |
if (config.runsInWidget || DEV_MODE) { | |
const date = new Date() | |
const dateNow = Date.now() | |
let df_Name = new DateFormatter() | |
let df_Month = new DateFormatter() | |
df_Name.dateFormat = "EEEE" | |
df_Month.dateFormat = "MMMM" | |
const dayName = df_Name.string(date) | |
const dayNumber = date.getDate().toString() | |
const monthName = df_Month.string(date) | |
// Option 1: uncomment this to use let the script locate you each time (which takes longer and needs more battery) | |
// let loc = await Location.current() | |
// let lat = loc["latitude"] | |
// let lon = loc["longitude"] | |
// Option 2: hard coded longitude/latitude | |
let lat = "50.95330938278102" | |
let lon = "6.915087611808545" | |
const weatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=current,minutely,daily,alerts&units=${UNITS}&appid=${API_KEY}` | |
const weatherRequest = new Request(weatherURL) | |
const weaterData = await weatherRequest.loadJSON() | |
const hourlyForecasts = weaterData.hourly | |
let nextForecasts = [] | |
for (const hourlyForecast of hourlyForecasts) { | |
if (nextForecasts.length == FORECAST_HOURS) { break } | |
let dt = removeDigits(dateNow, 3) | |
if (hourlyForecast.dt > dt) { | |
nextForecasts.push(hourlyForecast) | |
} | |
} | |
const events = await CalendarEvent.today([]) | |
let futureEvents = [] | |
for (const event of events) { | |
if (futureEvents.length == 2) { break } | |
if (event.startDate.getTime() > date.getTime() && !event.isAllDay) { | |
futureEvents.push(event) | |
} | |
} | |
let widget = new ListWidget() | |
widget.backgroundColor = widgetBackground | |
widget.setPadding(10, 10, 10, 10) | |
//Top Row (Date & Weather) | |
let topRow = widget.addStack() | |
topRow.layoutHorizontally() | |
//Top Row Date | |
let dateStack = topRow.addStack() | |
dateStack.layoutHorizontally() | |
dateStack.centerAlignContent() | |
dateStack.setPadding(7, 7, 7, 7) | |
dateStack.backgroundColor = stackBackground | |
dateStack.cornerRadius = 12 | |
dateStack.size = stackSize | |
dateStack.addSpacer() | |
let dayNumberTxt = dateStack.addText(dayNumber + ".") | |
dayNumberTxt.font = Font.semiboldSystemFont(32) | |
dayNumberTxt.textColor = Color.black() | |
dateStack.addSpacer(7) | |
let dateTextStack = dateStack.addStack() | |
dateTextStack.layoutVertically() | |
let monthNameTxt = dateTextStack.addText(monthName.toUpperCase()) | |
monthNameTxt.font = Font.boldSystemFont(10) | |
monthNameTxt.textColor = Color.black() | |
let dayNameTxt = dateTextStack.addText(dayName) | |
dayNameTxt.font = Font.boldSystemFont(12) | |
dayNameTxt.textColor = calendarColor | |
dateStack.addSpacer() | |
topRow.addSpacer() | |
widget.addSpacer() | |
//Top Row Weather | |
let weatherStack = topRow.addStack() | |
weatherStack.layoutHorizontally() | |
weatherStack.centerAlignContent() | |
weatherStack.setPadding(7, 7, 7, 7) | |
weatherStack.backgroundColor = stackBackground | |
weatherStack.cornerRadius = 12 | |
weatherStack.size = stackSize | |
weatherStack.url = WEATHER_URL | |
for (const nextForecast of nextForecasts) { | |
const iconURL = "https://openweathermap.org/img/wn/" + nextForecast.weather[0].icon + "@2x.png" | |
let iconRequest = new Request(iconURL); | |
let icon = await iconRequest.loadImage(); | |
weatherStack.addSpacer() | |
//Hour Forecast Stack | |
let hourStack = weatherStack.addStack() | |
hourStack.layoutVertically() | |
let hourTxt = hourStack.addText(formatAMPM(nextForecast.dt)) | |
hourTxt.centerAlignText() | |
hourTxt.font = Font.systemFont(10) | |
hourTxt.textColor = Color.black() | |
hourTxt.textOpacity = 0.5 | |
let weatherIcon = hourStack.addImage(icon) | |
weatherIcon.centerAlignImage() | |
weatherIcon.size = new Size(25, 25) | |
let tempTxt = hourStack.addText(" " + Math.round(nextForecast.temp) + "°") | |
tempTxt.centerAlignText() | |
tempTxt.font = Font.systemFont(10) | |
tempTxt.textColor = Color.black() | |
} | |
weatherStack.addSpacer() | |
//Bottom Row Events | |
let eventStack = widget.addStack() | |
eventStack.layoutHorizontally() | |
eventStack.centerAlignContent() | |
eventStack.setPadding(7, 7, 7, 7) | |
eventStack.backgroundColor = stackBackground | |
eventStack.cornerRadius = 12 | |
eventStack.size = stackSize | |
let eventInfoStack | |
const font = Font.lightSystemFont(20) | |
let calendarSymbol = SFSymbol.named("calendar") | |
calendarSymbol.applyFont(font) | |
eventStack.addSpacer(8) | |
let eventIcon = eventStack.addImage(calendarSymbol.image) | |
eventIcon.imageSize = new Size(20, 20) | |
eventIcon.resizable = false | |
eventIcon.centerAlignImage() | |
eventStack.addSpacer(14) | |
eventStack.url = CALENDAR_URL | |
let eventItemsStack = eventStack.addStack() | |
eventItemsStack.layoutVertically() | |
if (futureEvents.length != 0) { | |
for (let i = 0; i < futureEvents.length; i++) { | |
let futureEvent = futureEvents[i] | |
const time = formatTime(futureEvent.startDate) + " - " + formatTime(futureEvent.endDate) | |
const eventColor = new Color("#" + futureEvent.calendar.color.hex) | |
eventInfoStack = eventItemsStack.addStack() | |
eventInfoStack.layoutVertically() | |
let eventTitle = eventItemsStack.addText(futureEvent.title) | |
eventTitle.font = Font.semiboldSystemFont(12) | |
eventTitle.textColor = eventColor | |
eventTitle.lineLimit = 1 | |
let eventTime = eventItemsStack.addText(time + " Uhr") | |
eventTime.font = Font.semiboldMonospacedSystemFont(10) | |
eventTime.textColor = Color.black() | |
eventTime.textOpacity = 0.5 | |
if (i == 0) { | |
eventItemsStack.addSpacer(3) | |
} | |
} | |
} else { | |
let nothingText = eventStack.addText("Heute hast du keine Termine!") | |
nothingText.font = Font.semiboldMonospacedSystemFont(12) | |
nothingText.textColor = Color.black() | |
nothingText.textOpacity = 0.5 | |
} | |
eventStack.addSpacer() | |
Script.setWidget(widget) | |
if (DEV_MODE) { | |
if (DEV_PREVIEW == "small") { widget.presentSmall() } | |
if (DEV_PREVIEW == "medium") { widget.presentMedium() } | |
if (DEV_PREVIEW == "large") { widget.presentLarge() } | |
} | |
Script.complete() | |
} | |
function removeDigits(x, n) { return (x - (x % Math.pow(10, n))) / Math.pow(10, n) } | |
function formatAMPM(UNIX_timestamp) { | |
var date = new Date(UNIX_timestamp * 1000) | |
var hours = date.getHours() | |
// Option 1: uncomment this for am/pm time with hours from 0-12 | |
// var ampm = hours >= 12 ? 'PM' : 'AM' | |
// hours = hours % 12 | |
// hours = hours ? hours : 12 | |
// var strTime = hours.toString() + ampm | |
// Option 2: german localisation | |
var strTime = hours.toString() + ":00" | |
return strTime | |
} | |
function formatTime(date) { | |
let df = new DateFormatter() | |
df.useNoDateStyle() | |
df.useShortTimeStyle() | |
return df.string(date) | |
} |
seh ich das richtig das ich den Link zum icon einfach austauschen kann gegen ein anderes?
Ja, einfach eine andere URL angeben. Wenn du ein besseres findest, gib Bescheid.
seh ich das richtig das ich den Link zum icon einfach austauschen kann gegen ein anderes?
Ja, einfach eine andere URL angeben. Wenn du ein besseres findest, gib Bescheid.
Nochmal was anderes :D
da ich nur ein Leihe bin und nur ein bisschen die zusammenhänge verstehe aber nicht in .js Programmieren kann.. könntest du mir das Widget noch so umbauen das Rechts neben dem Kalender noch der zugriff auf meine Erinnerung angezeigt wird? also selber Aufbau wie oben mit wetter und Datum in der unten zeile: Links Kalender und Rechts Erinnerungen :)
und kannst du mir sagen was die URL zur Wetter App ist? oder wie ich die selbst zu einer App Herausfinde?
@scripomac
Ich habe deine Version mal etwas weiter optimiert. Zum einen kann man jetzt oben konfigurieren, ob ganztägige Events angezeigt werden oder nicht (SHOW_ALLDAY_EVENTS). Falls ja, steht dort als Zeit "ganztägig". Das Icon habe ich ausgetauscht. Die SFSymbols sind irgendwie immer dunkel. Wie findest du's?// Variables used by Scriptable. // These must be at the very top of the file. Do not edit. // icon-color: deep-gray; icon-glyph: magic; // this Scriptable Widget is coded by Slowlydev (aka r/Sl0wly-edits, r/Slowlydev) and adapted by @Marco79 const DEV_MODE = true //for developer only const DEV_PREVIEW = "medium" //for developer only (this script is specialy made for a medium sized widget) const SHOW_ALLDAY_EVENTS = true // if all day events should be shown or not const API_KEY = "*******" // enter your openweathermap.com api key const FORECAST_HOURS = "3" const UNITS = "metric" //metric for celsius and imperial for Fahrenheit const CALENDAR_URL = "calshow://" //Apple Calendar App, if your favorite app does have a URL scheme feel free to change it const WEATHER_URL = "" //there is no URL for the Apple Weather App, if your favorite app does feel free to add it const widgetBackground = new Color("gray") //Widget Background const stackBackground = new Color("#1D1D1D") //Smaller Container Background const calendarColor = new Color("#EA3323") //Calendar Color const stackSize = new Size(0, 65) //0 means its automatic if (config.runsInWidget || DEV_MODE) { const date = new Date() const dateNow = Date.now() let df_Name = new DateFormatter() let df_Month = new DateFormatter() df_Name.dateFormat = "EEEE" df_Month.dateFormat = "MMMM" const dayName = df_Name.string(date) const dayNumber = date.getDate().toString() const monthName = df_Month.string(date) // Option 1: uncomment this to use let the script locate you each time (which takes longer and needs more battery) // let loc = await Location.current() // let lat = loc["latitude"] // let lon = loc["longitude"] // Option 2: hard coded longitude/latitude let lat = "48,289247" let lon = "11,044963" const weatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=current,minutely,daily,alerts&units=${UNITS}&appid=${API_KEY}` const weatherRequest = new Request(weatherURL) const weaterData = await weatherRequest.loadJSON() const hourlyForecasts = weaterData.hourly let nextForecasts = [] for (const hourlyForecast of hourlyForecasts) { if (nextForecasts.length == FORECAST_HOURS) { break } let dt = removeDigits(dateNow, 3) if (hourlyForecast.dt > dt) { nextForecasts.push(hourlyForecast) } } const events = await CalendarEvent.today([]) let futureEvents = [] for (const event of events) { if (futureEvents.length == 2) { break } if(event.isAllDay && SHOW_ALLDAY_EVENTS) {futureEvents.push(event)} else if(event.startDate.getTime() >= date.getTime()) { futureEvents.push(event) } } let widget = new ListWidget() widget.backgroundColor = widgetBackground widget.setPadding(10, 10, 10, 10) //Top Row (Date & Weather) let topRow = widget.addStack() topRow.layoutHorizontally() //Top Row Date let dateStack = topRow.addStack() dateStack.layoutHorizontally() dateStack.centerAlignContent() dateStack.setPadding(7, 7, 7, 7) dateStack.backgroundColor = stackBackground dateStack.cornerRadius = 12 dateStack.size = stackSize dateStack.addSpacer() let dayNumberTxt = dateStack.addText(dayNumber + ".") dayNumberTxt.font = Font.semiboldSystemFont(32) dayNumberTxt.textColor = Color.white() dateStack.addSpacer(7) let dateTextStack = dateStack.addStack() dateTextStack.layoutVertically() let monthNameTxt = dateTextStack.addText(monthName.toUpperCase()) monthNameTxt.font = Font.boldSystemFont(10) monthNameTxt.textColor = Color.white() let dayNameTxt = dateTextStack.addText(dayName) dayNameTxt.font = Font.boldSystemFont(12) dayNameTxt.textColor = calendarColor dateStack.addSpacer() topRow.addSpacer() widget.addSpacer() //Top Row Weather let weatherStack = topRow.addStack() weatherStack.layoutHorizontally() weatherStack.centerAlignContent() weatherStack.setPadding(7, 7, 7, 7) weatherStack.backgroundColor = stackBackground weatherStack.cornerRadius = 12 weatherStack.size = stackSize weatherStack.url = WEATHER_URL for (const nextForecast of nextForecasts) { const iconURL = "https://openweathermap.org/img/wn/" + nextForecast.weather[0].icon + "@2x.png" let iconRequest = new Request(iconURL); let icon = await iconRequest.loadImage(); weatherStack.addSpacer() //Hour Forecast Stack let hourStack = weatherStack.addStack() hourStack.layoutVertically() let hourTxt = hourStack.addText(formatAMPM(nextForecast.dt)) hourTxt.centerAlignText() hourTxt.font = Font.systemFont(10) hourTxt.textColor = Color.white() hourTxt.textOpacity = 1 let weatherIcon = hourStack.addImage(icon) weatherIcon.centerAlignImage() weatherIcon.size = new Size(25, 25) let tempTxt = hourStack.addText(" " + Math.round(nextForecast.temp) + "°") tempTxt.centerAlignText() tempTxt.font = Font.systemFont(10) tempTxt.textColor = Color.white() } weatherStack.addSpacer() //Bottom Row Events let eventStack = widget.addStack() eventStack.layoutHorizontally() eventStack.centerAlignContent() eventStack.setPadding(7, 7, 7, 7) eventStack.backgroundColor = stackBackground eventStack.cornerRadius = 12 eventStack.size = stackSize let eventInfoStack eventStack.addSpacer(8) let eventIcon = eventStack.addImage(await loadImage("https://i.imgur.com/tcYSptZ.png")) eventIcon.imageSize = new Size(40,40) eventIcon.centerAlignImage() eventStack.addSpacer(14) eventStack.url = CALENDAR_URL let eventItemsStack = eventStack.addStack() eventItemsStack.layoutVertically() if (futureEvents.length != 0) { for (let i = 0; i < futureEvents.length; i++) { let futureEvent = futureEvents[i] const time = formatTime(futureEvent.startDate) + " - " + formatTime(futureEvent.endDate) const eventColor = new Color("#" + futureEvent.calendar.color.hex) eventInfoStack = eventItemsStack.addStack() eventInfoStack.layoutVertically() let eventTitle = eventItemsStack.addText(futureEvent.title) eventTitle.font = Font.semiboldSystemFont(12) eventTitle.textColor = eventColor eventTitle.lineLimit = 1 let eventTime if(futureEvent.isAllDay) { eventTime = "ganztägig" } else { eventTime = time + " Uhr" } let eventTimeText = eventItemsStack.addText(eventTime) eventTimeText.font = Font.semiboldMonospacedSystemFont(10) eventTimeText.textColor = Color.white() eventTimeText.textOpacity = 1 if (i == 0) { eventItemsStack.addSpacer(3) } } } else { let nothingText = eventStack.addText("Heute hast du keine Termine!") nothingText.font = Font.semiboldMonospacedSystemFont(12) nothingText.textColor = Color.white() nothingText.textOpacity = 1 } eventStack.addSpacer() Script.setWidget(widget) if (DEV_MODE) { if (DEV_PREVIEW == "small") { widget.presentSmall() } if (DEV_PREVIEW == "medium") { widget.presentMedium() } if (DEV_PREVIEW == "large") { widget.presentLarge() } } Script.complete() } function removeDigits(x, n) { return (x - (x % Math.pow(10, n))) / Math.pow(10, n) } function formatAMPM(UNIX_timestamp) { var date = new Date(UNIX_timestamp * 1000) var hours = date.getHours() // Option 1: uncomment this for am/pm time with hours from 0-12 // var ampm = hours >= 12 ? 'PM' : 'AM' // hours = hours % 12 // hours = hours ? hours : 12 // var strTime = hours.toString() + ampm // Option 2: german localisation var strTime = hours.toString() + ":00" return strTime } function formatTime(date) { let df = new DateFormatter() df.useNoDateStyle() df.useShortTimeStyle() return df.string(date) } // helper function to download an image from a given url async function loadImage(imgUrl) { const req = new Request(imgUrl) return await req.loadImage() }
Sieht gut aus, Danke.
Siehst du eine Möglichkeit die Wetter icons größer darzustellen?
Wäre es möglich statt des Kaleders die Zeit darzustellen?
Danke hab ich :D
selbst gemacht und selbst gehostet ;) klappt Gut :D
Stellst du es zur Verfügung?
Danke hab ich :D
selbst gemacht und selbst gehostet ;) klappt Gut :DStellst du es zur Verfügung?
hier der eigene Link:
https://robin-sedlmeir.de/Icons/kalender_3.png
unter /kalender_2 &
unter /kalender
gibt es noch zwei weitere Versionen.
Bitte Downloaden und selbst hosten damit ich nicht zu viel Traffic bei mir habe ;) falls das mal zu viel werden sollte muss ich den Link ändern!
Viel Spass damit :)
Vielen Dank!
hi Marco, is it possible to remove the dot after the day number?
Tks
hi Marco, is it possible to remove the dot after the day number?
Tks
Sure, it's this line here:
let dayNumberTxt = dateStack.addText(dayNumber + ".")
Just remove the + "."
hi Marco, is it possible to remove the dot after the day number?
TksSure, it's this line here:
let dayNumberTxt = dateStack.addText(dayNumber + ".")
Just remove the
+ "."
Tks a lot... Marco !!
Ist es möglich ein großes Widget zu bekommen mit Kalendereinträgen (auch ganztägige) für die nächsten 10 Tage?
Wird es einen automatischen Wechsel zum Dark Mode geben?
@marco79cgn würdest du dir meine Frage von oben nochmal anschauen? mit den Erinnerungen unter der Wetter anzeige?
würde den selben Aufbau wie vom Kalender nehmen, Links wechselbares icon und Rechts max 2 Erinnerungen. und Klickbar zum öffnen der Erinnerungsapp oder die app seiner Wahl :D
ich bin leider gescheitert das irgendwie zu basteln...
Danke und Grüße
Cooles Widget. Aber ich bekomme immer die Fehlermeldung TypeError: undefined is not an object (evaluating 'weaterData.hourly')
Bin leider kein Entwickler. habt ihr da eine Idee?
Cooles Widget. Aber ich bekomme immer die Fehlermeldung TypeError: undefined is not an object (evaluating 'weaterData.hourly')
Bin leider kein Entwickler. habt ihr da eine Idee?
Nachdem du einen API Key bei Openweathermap angefordert hast, dauert es i.d.R. noch mehrere Stunden, bis der dann auch wirklich aktiv ist und die API Ergebnisse liefert. Probier es später nochmal.
Wenn ich das Telefon entsperre bekomme ich oft diese error message: Operation couldn’t be completed. KCLErrorDomain error 1. Und wenn ich aufs Wetter gehe sagt er das die Domain nicht supported ist.
Wenn ich das Telefon entsperre bekomme ich oft diese error message: Operation couldn’t be completed. KCLErrorDomain error 1. Und wenn ich aufs Wetter gehe sagt er das die Domain nicht supported ist.
Du hast vermutlich die Location auf Option 1 umgestellt. Die Fehlermeldung besagt, dass er die Location nicht abrufen konnte.
Mit Option 2 (und deinen eigenen Koordinaten) ist der Fehler vermutlich weg.
@scripomac
Ich habe deine Version mal etwas weiter optimiert. Zum einen kann man jetzt oben konfigurieren, ob ganztägige Events angezeigt werden oder nicht (SHOW_ALLDAY_EVENTS). Falls ja, steht dort als Zeit "ganztägig". Das Icon habe ich ausgetauscht. Die SFSymbols sind irgendwie immer dunkel. Wie findest du's?