Skip to content

Instantly share code, notes, and snippets.

@brenoprata10
Last active August 19, 2021 08:37
Show Gist options
  • Save brenoprata10/a7bfb8df46a547fdb6215bc12d147be5 to your computer and use it in GitHub Desktop.
Save brenoprata10/a7bfb8df46a547fdb6215bc12d147be5 to your computer and use it in GitHub Desktop.
Youtube videos on a widget
const YOUTUBE_API = "https://youtube.googleapis.com/youtube/v3"
const API_KEY = "YOUR_API_KEY_HERE"
const channelIdArray = [
"UCT6iAerLNE-0J1S_E97UAuQ", // YongYea
"UC9PBzalIcEQCsiIkq36PyUA", // Digital Foundry
"UCNvzD7Z-g64bPXxGzaQaa4g", // gameranx
"UCcGL_0yoZTskvlgAixaEjEg", // whatoplay
"UCawsJGDMV6IOm6z9yiOyIsQ", // Cronosfera
"UC-zfTtp6tir7yJIrpsgS0dA", // Intoxi Anime
"UCBJycsmduvYEL83R_U4JriQ", // Marques Brownlee
"UCsBjURrPoezykLs9EqgamOA", // Fireship
"UCWFKCr40YwOZQx8FHU_ZqqQ", // JerryRigEverything
"UCr6JcgG9eskEzL-k6TtL9EQ", // ZONEofTECH
"UCZ7AeeVbyslLM_8-nVy2B8Q", // Skill Up
"UCsvn_Po0SmunchJYOWpOxMg", // videogamedunkey
"UCVYamHliCI9rw1tHR1xbkfw", // Dave2D
]
// 120 minutes interval
const refreshInterval = (120*60*1000)
try {
const videos = await getRecentYoutubeVideos(channelIdArray, getMaxQuantityAllowed())
log(videos)
const widget = videos.length > 0 ? await createVideoWidget(videos)
: getWidgetWithMessage({
message: "Nothing new here",
secondaryMessage: "No video published in the last 24 hours",
image: SFSymbol.named("video.bubble.left").image
})
widget.refreshAfterDate = new Date(Date.now() + (90*60*1000))
renderWidget(widget)
} catch(error) {
const widget = await createErrorWidget(error)
renderWidget(widget)
}
async function createVideoWidget(videos) {
const widget = new ListWidget()
setBackground(widget)
setTitleStack(widget)
widget.addSpacer(5)
await createVideoGrid(widget, videos)
widget.addSpacer()
widget.url = "https://www.youtube.com/feed/subscriptions"
return widget
}
async function createVideoGrid(widget, videos) {
const gridStack = widget.addStack()
gridStack.layoutVertically()
for (const video of videos) {
await addVideoToGridStack(gridStack, video)
}
}
async function addVideoToGridStack(gridStack, video) {
const {snippet, id} = video
const {title, thumbnails, publishedAt, channelTitle} = snippet
const videoStack = gridStack.addStack()
videoStack.layoutHorizontally()
videoStack.addSpacer()
videoStack.url = `https://www.youtube.com/watch?v=${id.videoId}`
const coverElement = videoStack.addImage(await loadImageFromUrl(thumbnails.medium.url))
coverElement.imageSize = new Size(90, 60)
videoStack.addSpacer(10)
const contentStack = videoStack.addStack()
contentStack.layoutVertically()
contentStack.size = new Size(210, 0)
contentStack.addSpacer(3)
const titleStack = contentStack.addStack()
titleStack.size = new Size(0, 32)
const titleElement = titleStack.addText(
title.replace(/'/g, "'")
.replace(/&/g, '&')
.replace(/"/g, '\\"')
)
titleElement.lineLimit = 2
titleElement.font = Font.boldSystemFont(13)
contentStack.addSpacer(7)
const additionalInfoStack = contentStack.addStack()
const channelNameElement = additionalInfoStack.addText(channelTitle)
channelNameElement.font = Font.semiboldSystemFont(11)
channelNameElement.lineLimit = 1
additionalInfoStack.addSpacer()
const hourDifference = getHourDifference(new Date(publishedAt))
const publishedTimeElement = additionalInfoStack.addText(
hourDifference > 1
? `${hourDifference} hours ago`
: `Just now`
)
publishedTimeElement.font = Font.semiboldSystemFont(11)
videoStack.addSpacer()
}
async function getRecentYoutubeVideos(channelIdArray, limit) {
const fileManager = FileManager.local()
const cacheFilePath = `${fileManager.cacheDirectory()}/youtube-cache.json`
const cachedVideos = fileManager.fileExists(cacheFilePath)
? JSON.parse(fileManager.readString(cacheFilePath))
: []
log(cachedVideos)
let videoArray = [
...cachedVideos.filter((video) => getHourDifference(new Date(video.snippet.publishedAt)) < 20)
]
const today = new Date()
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
for(const channelId of channelIdArray) {
const isChannelIdNotCached = cachedVideos.every((video) => video.snippet.channelId !== channelId)
if (videoArray.length < limit && isChannelIdNotCached) {
const request = new Request(`${YOUTUBE_API}/search?part=snippet&key=${API_KEY}&channelId=${channelId}&publishedAfter=${formatDateToRFC(yesterday)}&publishedBefore=${formatDateToRFC(today)}`)
const response = await request.loadJSON()
log(channelId)
if (response.error) {
throw new Error(response.error.message)
}
if (response.items) {
videoArray = [...videoArray, ...response.items]
}
}
}
videoArray.sort((v1, v2) => new Date(v2.snippet.publishedAt) - new Date(v1.snippet.publishedAt))
const recentVideos = videoArray.length > limit
? videoArray.slice(0, limit)
: videoArray
fileManager.writeString(cacheFilePath, JSON.stringify(recentVideos))
return recentVideos
}
async function setTitleStack(widget) {
const titleStack = widget.addStack()
titleStack.size = new Size(330, 15)
const dateFormatter = new DateFormatter()
dateFormatter.dateFormat = "HH:mm"
titleStack.addSpacer(10)
const lastUpdate = titleStack.addText(`Last update: ${dateFormatter.string(new Date())}`)
lastUpdate.font = Font.boldSystemFont(13)
}
function createErrorWidget(error) {
return getWidgetWithMessage({
message: "Cannot load videos",
secondaryMessage: error.toString().replace("Error: ", ""),
image: SFSymbol.named("wifi.exclamationmark").image,
})
}
function getWidgetWithMessage({message, secondaryMessage, image}) {
const widget = new ListWidget()
setBackground(widget, {})
widget.addSpacer()
const contentStack = widget.addStack()
contentStack.layoutHorizontally()
contentStack.addSpacer()
const imageElement = contentStack.addImage(image)
imageElement.tintColor = Color.white()
imageElement.imageSize = new Size(27, 27)
contentStack.addSpacer()
const textElement = contentStack.addText(message)
textElement.font = Font.systemFont(23)
contentStack.addSpacer()
widget.addSpacer(10)
const errorDetailStack = widget.addStack()
errorDetailStack.layoutHorizontally()
errorDetailStack.addSpacer()
const errorDetailElement = errorDetailStack.addText(secondaryMessage)
errorDetailElement.font = Font.systemFont(13)
errorDetailStack.addSpacer()
widget.addSpacer()
return widget
}
async function setBackground(widget) {
widget.backgroundColor = new Color("ad0303")
}
function renderWidget(widget) {
if (config.runsInWidget) {
Script.setWidget(widget)
} else {
widget.presentMedium()
}
Script.complete()
}
function getMaxQuantityAllowed() {
switch(config.widgetFamily) {
case 'small':
return 1
case 'medium':
return 2
case 'large':
return 5
default:
return 2
}
}
async function loadImageFromUrl(url) {
const req = new Request(url)
return req.loadImage()
}
function formatDateToRFC(date) {
const dateFormatter = new DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
const timeFormatter = new DateFormatter()
timeFormatter.dateFormat = "HH:mm:ss"
return `${dateFormatter.string(date)}T${timeFormatter.string(date)}Z`
}
function getHourDifference(date) {
return Math.floor(Math.abs(new Date() - date) / 3.6e6)
}
@brenoprata10
Copy link
Author

background

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