Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save marco79cgn/fa9cd9a3423be4500a20a54cb783f4c0 to your computer and use it in GitHub Desktop.
Save marco79cgn/fa9cd9a3423be4500a20a54cb783f4c0 to your computer and use it in GitHub Desktop.
Date & Agenda & Weather Scriptable widget (german localization)
// 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)
}
@gunti-2020
Copy link

habe die App Scritable deinstalliert und installiert.
Bei dem Corana Widget hat er gefragt, ab der Standort abfragt werden kann.
Bei deinem Widget gab es keine Abfrage nach dem Kalender. Muss ich in dem Script noch etwas bei der CONST CALENDER_URL eintragen?

@Leibinger015
Copy link

habe die App Scritable deinstalliert und installiert.
Bei dem Corana Widget hat er gefragt, ab der Standort abfragt werden kann.
Bei deinem Widget gab es keine Abfrage nach dem Kalender. Muss ich in dem Script noch etwas bei der CONST CALENDER_URL eintragen?

Wie bei mir, dass Widget will partout keine Kalender Einträge anzeigen.

@gunti-2020
Copy link

ich habe keine Möglichkeit den Kalender für Scritable frei zu geben

@Leibinger015
Copy link

BF1AD318-6A24-4133-925C-770FC97C012C

Auch nicht bei Einstellungen/Scriptable ?

@gunti-2020
Copy link

nein, ich habe dort "nur" Standort und Siri
Welches IOS hast Du? Ich habe 14.2 beta

@Leibinger015
Copy link

nein, ich habe dort "nur" Standort und Siri
Welches IOS hast Du? Ich habe 14.2 beta

14.1 (18A8395) Eventuell liegt das an der BETA?

@gunti-2020
Copy link

kann durchaus sein. Habe aber kein Gerät, ohne eine Beta, also muss ich wohl warten.
Vielleicht gibt es heute eine neue Beta und dann geht alles (grins)

@gunti-2020
Copy link

Jetzt geht es

Ich habe folgendes gemacht
const DEV_MODE = True
Und das Script ausgeführt, dann kam die Abfrage, ob ich den Zugriff auf den Kalender gewähren möchte.

Einträge aus dem Kalender sehe ich jetzt auch

@marco79cgn
Copy link
Author

marco79cgn commented Oct 28, 2020

Jetzt geht es

Ich habe folgendes gemacht
const DEV_MODE = True
Und das Script ausgeführt, dann kam die Abfrage, ob ich den Zugriff auf den Kalender gewähren möchte.

Einträge aus dem Kalender sehe ich jetzt auch

Ja Sorry, hätte ich dazu schreiben müssen. Das Skript stammt ja wie beschrieben nicht von mir, hat sich ein anderer so ausgedacht.

Also einmal den DEV_MODE auf true setzen, play drücken, Berechtigung erteilen und danach wieder auf false setzen. Wenn man das nicht macht, macht das Skript einfach gar nichts, wenn man es manuell in Scriptable startet, da die gesamte if-Bedingung nicht erfüllt ist und übersprungen wird.

@Leibinger015
Copy link

Trotz „true“ oder „false“ wird bei mir leider nichts angezeigt im Widget beim Kalender Bereich. Nur der Satz „Heute hast du keine Termine!“ Obwohl Termine und die Berechtigung vorhanden ist.

Hast du da noch eine Idee?

@scripomac
Copy link

Guten Morgen :)

ist an einem Darkmode irgendwie gedacht? würde mich freuen :D

@scripomac
Copy link

scripomac commented Oct 30, 2020

47700CBB-D947-45A5-9C1A-E5FBBC6C90F7

Ich hab jetzt das ganze mal selbst angepasst :D nur bekomme ich das Symbol vom Kalender nicht in weiß... kann mir da jemand helfen?

// 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 = "37f03135774979aa59c909211acc418c" // 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.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.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

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()
eventIcon.textColor = Color.white()

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.white()
        eventTime.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)
}

@marco79cgn
Copy link
Author

marco79cgn commented Nov 1, 2020

@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()
}

@scripomac
Copy link

scripomac commented Nov 1, 2020

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.

@scripomac
Copy link

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?

@laspecas
Copy link

laspecas commented Nov 2, 2020

@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?

@scripomac
Copy link

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.

C61D8BCD-F52B-4116-B68B-3427257C2F3A

Danke hab ich :D

selbst gemacht und selbst gehostet ;) klappt Gut :D

@marco79cgn
Copy link
Author

Danke hab ich :D

selbst gemacht und selbst gehostet ;) klappt Gut :D

Stellst du es zur Verfügung?

@scripomac
Copy link

scripomac commented Nov 5, 2020

Danke hab ich :D
selbst gemacht und selbst gehostet ;) klappt Gut :D

Stellst 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 :)

@marco79cgn
Copy link
Author

Vielen Dank!

@p4rzival0ne
Copy link

hi Marco, is it possible to remove the dot after the day number?

Tks

@marco79cgn
Copy link
Author

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 + "."

@p4rzival0ne
Copy link

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 + "."

Tks a lot... Marco !!

@sleepyhellow
Copy link

Ist es möglich ein großes Widget zu bekommen mit Kalendereinträgen (auch ganztägige) für die nächsten 10 Tage?

@laspecas
Copy link

Wird es einen automatischen Wechsel zum Dark Mode geben?

@scripomac
Copy link

@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

@steiale
Copy link

steiale commented Jan 20, 2021

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?

@marco79cgn
Copy link
Author

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.

@steiale
Copy link

steiale commented Jan 21, 2021

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.

@toby17780
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment