Skip to content

Instantly share code, notes, and snippets.

@mzeryck
Last active August 29, 2021 14:40
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mzeryck/4f9255224fe707ee74d86dc6465feea2 to your computer and use it in GitHub Desktop.
Save mzeryck/4f9255224fe707ee74d86dc6465feea2 to your computer and use it in GitHub Desktop.
A Scriptable widget that shows upcoming calendar events to mimic the built-in Calendar widget, with some modifications.
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: red; icon-glyph: calendar-alt;
// This widget was created by Max Zeryck @mzeryck
// Note: before using this script in a widget, change const TEST_MODE to true and run it in the Scriptable app.
// The app will prompt you to give calendar access, and then show a preview of the widget.
// Make sure to change it back to const TEST_MODE = false prior to adding it to a widget. Happy coding!
const IMAGE_SOURCE = "Unsplash"
const TERMS = "nature,water"
const FORCE_IMAGE_UPDATE = false
const TEST_MODE = false
// Store current datetime
const date = new Date()
// If we're running the script normally, go to the Calendar.
if (!config.runsInWidget && !TEST_MODE) {
const appleDate = new Date('2001/01/01')
const timestamp = (date.getTime() - appleDate.getTime()) / 1000
const callback = new CallbackURL("calshow:"+timestamp)
callback.open()
Script.complete()
// Otherwise, create the widget.
} else {
let widget = new ListWidget()
// Format the date info
let df = new DateFormatter()
df.dateFormat = "EEEE"
let dayOfWeek = widget.addText(df.string(date).toUpperCase())
let dateNumber = widget.addText(date.getDate().toString())
dayOfWeek.font = Font.semiboldSystemFont(13)
dateNumber.font = Font.lightSystemFont(34)
// Find future events that aren't all day and aren't canceled
const events = await CalendarEvent.today([])
let futureEvents = []
for (const event of events) {
if (event.startDate.getTime() > date.getTime() && !event.isAllDay && !event.title.startsWith("Canceled:")) {
futureEvents.push(event)
}
}
// If there is at least one future event today
if (futureEvents.length != 0) {
dayOfWeek.textColor = Color.red()
widget.addSpacer()
let titleOne = widget.addText(futureEvents[0].title)
titleOne.font = Font.mediumSystemFont(14)
widget.addSpacer(7)
let timeOne = widget.addText(formatTime(futureEvents[0].startDate))
timeOne.font = Font.regularSystemFont(14)
timeOne.textColor = Color.gray()
// If we have multiple future events, show the following one
if (futureEvents.length > 1) {
// We only have room for single-line event names
titleOne.lineLimit = 1
widget.addSpacer(12)
let titleTwo = widget.addText(futureEvents[1].title)
titleTwo.font = Font.mediumSystemFont(14)
titleTwo.lineLimit = 1
widget.addSpacer(7)
let timeTwo = widget.addText(formatTime(futureEvents[1].startDate))
timeTwo.font = Font.regularSystemFont(14)
timeTwo.textColor = Color.gray()
}
// If there are no future events today
} else {
dayOfWeek.textColor = Color.white()
dateNumber.textColor = Color.white()
let files = FileManager.local()
const path = files.joinPath(files.documentsDirectory(), "mz_calendar_widget.jpg")
const modificationDate = files.modificationDate(path)
// Download image if it doesn't exist, wasn't created today, or update is forced
if (!modificationDate || !sameDay(modificationDate,date) || FORCE_IMAGE_UPDATE) {
try {
let img = await provideImage(IMAGE_SOURCE,TERMS)
files.writeImage(path,img)
widget.backgroundImage = img
} catch {
widget.backgroundImage = files.readImage(path)
}
} else {
widget.backgroundImage = files.readImage(path)
}
// Add overlay to image
let gradient = new LinearGradient()
gradient.colors = [new Color("#000000",0.5), new Color("#000000",0)]
gradient.locations = [0, 0.5]
widget.backgroundGradient = gradient
widget.addSpacer()
}
// Finalize widget settings
widget.setPadding(16,16,16,0)
widget.spacing = -3
Script.setWidget(widget)
widget.presentSmall()
Script.complete()
}
// Helper function to interpret sources and terms
async function provideImage(source,terms) {
if (source == "Bing") {
const url = "http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-US"
const req = new Request(url)
const json = await req.loadJSON()
const imgURL = "http://bing.com" + json.images[0].url
const img = await downloadImage(imgURL)
const rect = new Rect(-78,0,356,200)
return cropImage(img, rect)
} else if (source == "Unsplash") {
const img = await downloadImage("https://source.unsplash.com/featured/500x500/?"+terms)
return img
}
}
// Helper function to download images
async function downloadImage(url) {
const req = new Request(url)
return await req.loadImage()
}
// Crop an image into a rect
function cropImage(img,rect) {
let draw = new DrawContext()
draw.respectScreenScale = true
draw.drawImageInRect(img,rect)
return draw.getImage()
}
// Formats the times under each event
function formatTime(date) {
let df = new DateFormatter()
df.useNoDateStyle()
df.useShortTimeStyle()
return df.string(date)
}
// Determines if two dates occur on the same day
function sameDay(d1, d2) {
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate()
}
@Johnnyt12
Copy link

How do I have to modify line 42 to incluce future events, like for the next few days?

@mzeryck
Copy link
Author

mzeryck commented Oct 26, 2020

@Johnnyt12 You could change it to const events = await CalendarEvent.thisWeek([])

@italoboy
Copy link

italoboy commented Nov 2, 2020

Possible to make it localized?!
Add locale code.

Copy link

ghost commented Nov 8, 2020

@ mzeryck

Hey Max, your widget is really good!!! However, I have problems at "weather calendar widget builder” and always get the following hint:

2020-11-08 17:09:55: Error on line 769:51: TypeError: undefined is not an object (evaluating 'weatherDataRaw.current.temp')

Can you help meines me?

I’m looking forward to a feedback.

@FijianCoconut
Copy link

@ mzeryck

Hey Max, your widget is really good!!! However, I have problems at "weather calendar widget builder” and always get the following hint:

2020-11-08 17:09:55: Error on line 769:51: TypeError: undefined is not an object (evaluating 'weatherDataRaw.current.temp')

Can you help meines me?

I’m looking forward to a feedback.

Ditto.

@mzeryck
Copy link
Author

mzeryck commented Nov 21, 2020

@Augustus88 @FijianCoconut In order for the widget to work, you need an API key from OpenWeather. You just need to sign up for a free account using this link. Then, you'll get the API key (it looks like a bunch of random letters and numbers). Depending on which version of the script you're using, you'll see apiKey = "" at the top - just paste the key in between the quotation marks, like this: apiKey = "abcdefgh"

@baelfire33
Copy link

Hi @mzerck, thanks for this code but do you know if we can change the temperature units and go from ° F to ° C? And how not to put a background to see that the wallpaper ? thank you so much

@mzeryck
Copy link
Author

mzeryck commented Nov 25, 2020

@baelfire33 I'm guessing you are using Weather Cal, rather than this calendar script? In Weather Cal, you can change the units to "metric" (instead of imperial) to get ° C. For a transparent background, use Widget Blur to export an image, and then use that image as the background of the widget.

@baelfire33
Copy link

thank you so much ! Do you have a solution for switching from light mode to dark mode with the image behind, even without dynamic image change?

@mzeryck
Copy link
Author

mzeryck commented Dec 21, 2020

@baelfire33 Sorry I always forget to check comments on my Gists! I don't have a solution yet, but I'm hoping that during my vacation this week I can work on it.

@Luk141888
Copy link

Hi, how to show TODAY all day events?

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