Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hawken/ics everything #711

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dotenv": "16.4.5",
"get-urls": "12.1.0",
"html-entities": "2.5.2",
"ical.js": "^2.0.1",
"is-absolute-url": "4.0.1",
"jsdom": "24.0.0",
"koa": "2.15.3",
Expand Down
13 changes: 6 additions & 7 deletions source/calendar-google/index.js → source/calendar/google.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import {get} from '../ccc-lib/http.js'
import moment from 'moment'
import getUrls from 'get-urls'
import {JSDOM} from 'jsdom'
import {Event} from './types.js'

function convertGoogleEvents(data, now = moment()) {
let events = data.map((event) => {
return data.map((event) => {
const startTime = moment(event.start.date || event.start.dateTime)
const endTime = moment(event.end.date || event.end.dateTime)
let description = (event.description || '').replace('<br>', '\n')
description = JSDOM.fragment(description).textContent.trim()

return {
return Event.parse({
dataSource: 'google',
startTime,
endTime,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
title: event.summary || '',
description: description,
location: event.location || '',
Expand All @@ -24,10 +25,8 @@ function convertGoogleEvents(data, now = moment()) {
endTime: true,
subtitle: 'location',
},
}
})
})

return events
}

export async function googleCalendar(calendarId, now = moment()) {
Expand Down
58 changes: 58 additions & 0 deletions source/calendar/ical.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {get} from '../ccc-lib/http.js'
import moment from 'moment'
import getUrls from 'get-urls'
import {JSDOM} from 'jsdom'
import InternetCalendar from 'ical.js'
import {Event} from './types.js'
import lodash from 'lodash'

const {sortBy} = lodash

/**
* @param {InternetCalendar.Event[]} data
* @param {typeof moment} now
* @returns {Event[]}
*/
function convertEvents(data, now = moment()) {
return data.map((event) => {
const startTime = moment(event.startDate.toString())
const endTime = moment(event.endDate.toString())
let description = JSDOM.fragment(event.description || '').textContent.trim()

return Event.parse({
dataSource: 'ical',
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
title: event.summary,
description: description,
location: event.location,
isOngoing: startTime.isBefore(now, 'day'),
links: [...getUrls(description)],
metadata: {
uid: event.uid,
},
config: {
startTime: true,
endTime: true,
subtitle: 'location',
},
})
})
}

export async function ical(url, {onlyFuture = true} = {}, now = moment()) {
let body = await get(url).text()

let comp = InternetCalendar.Component.fromString(body)
let events = comp
.getAllSubcomponents('vevent')
.map((vevent) => new InternetCalendar.Event(vevent))

if (onlyFuture) {
events = events.filter((event) =>
moment(event.endDate.toString()).isAfter(now, 'day'),
)
}

return sortBy(convertEvents(events, now), (event) => event.startTime)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import moment from 'moment-timezone'
import lodash from 'lodash'
import getUrls from 'get-urls'
import {JSDOM} from 'jsdom'
import {Event} from './types.js'

const {dropWhile, dropRightWhile, sortBy} = lodash

const TZ = 'US/Central'
Expand Down Expand Up @@ -116,7 +118,7 @@ function convertReasonEvent(event, now = moment()) {

let links = description ? [...getUrls(description)] : []

return {
return Event.parse({
dataSource: 'reason',
startTime: event.startTime,
endTime: event.endTime,
Expand All @@ -133,7 +135,7 @@ function convertReasonEvent(event, now = moment()) {
endTime: true,
subtitle: 'location',
},
}
})
}

export async function reasonCalendar(calendarUrl, now = moment()) {
Expand Down
19 changes: 19 additions & 0 deletions source/calendar/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {z} from 'zod'

const EventConfig = z.object({
startTime: z.boolean(),
endTime: z.boolean(),
subtitle: z.union([z.literal('location')]),
})

export const Event = z.object({
dataSource: z.string(),
startTime: z.string().datetime(),
endTime: z.string().datetime(),
title: z.string(),
description: z.string(),
isOngoing: z.boolean(),
links: z.array(z.unknown()),
config: EventConfig,
metadata: z.optional(z.unknown()),
})
36 changes: 15 additions & 21 deletions source/ccci-carleton-college/v1/calendar.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {googleCalendar} from '../../calendar-google/index.js'
import {reasonCalendar} from '../../calendar-reason/index.js'
import {googleCalendar} from '../../calendar/google.js'
import {ical} from '../../calendar/ical.js'
import {ONE_MINUTE} from '../../ccc-lib/constants.js'
import mem from 'memoize'

export const getGoogleCalendar = mem(googleCalendar, {maxAge: ONE_MINUTE})
export const getReasonCalendar = mem(reasonCalendar, {maxAge: ONE_MINUTE})
export const getInternetCalendar = mem(ical, {maxAge: ONE_MINUTE})

export async function google(ctx) {
ctx.cacheControl(ONE_MINUTE)
Expand All @@ -13,40 +13,34 @@ export async function google(ctx) {
ctx.body = await getGoogleCalendar(calendarId)
}

export async function reason(ctx) {
export async function ics(ctx) {
ctx.cacheControl(ONE_MINUTE)

let {url: calendarUrl} = ctx.query
ctx.body = await getReasonCalendar(calendarUrl)
}

export function ics(ctx) {
ctx.cacheControl(ONE_MINUTE)

ctx.throw(501, 'ICS support is not implemented yet.')
ctx.body = await getInternetCalendar(calendarUrl)
}

export async function carleton(ctx) {
ctx.cacheControl(ONE_MINUTE)

let url =
'webcal://www.carleton.edu/calendar/?loadFeed=calendar&stamp=1714843628'
ctx.body = await getGoogleCalendar(url)
'https://www.carleton.edu/calendar/?loadFeed=calendar&stamp=1714843628'
ctx.body = await getInternetCalendar(url)
}

export async function cave(ctx) {
ctx.cacheControl(ONE_MINUTE)

let url =
'webcal://www.carleton.edu/student/orgs/cave/calendar/?loadFeed=calendar&stamp=1714844429\n'
ctx.body = await getGoogleCalendar(url)
'https://www.carleton.edu/student/orgs/cave/calendar/?loadFeed=calendar&stamp=1714844429\n'
ctx.body = await getInternetCalendar(url)
}

export async function stolaf(ctx) {
ctx.cacheControl(ONE_MINUTE)

let id = '5g91il39n0sv4c2bjdv1jrvcpq4ulm4r@import.calendar.google.com'
ctx.body = await getGoogleCalendar(id)
let id = 'https://www.stolaf.edu/apps/calendar/ical.cfm'
ctx.body = await getInternetCalendar(id)
}

export async function northfield(ctx) {
Expand Down Expand Up @@ -74,14 +68,14 @@ export async function convos(ctx) {
ctx.cacheControl(ONE_MINUTE)

let url =
'webcal://www.carleton.edu/convocations/calendar/?loadFeed=calendar&stamp=1714843936'
ctx.body = await getGoogleCalendar(url)
'https://www.carleton.edu/convocations/calendar/?loadFeed=calendar&stamp=1714843936'
ctx.body = await getInternetCalendar(url)
}

export async function sumo(ctx) {
ctx.cacheControl(ONE_MINUTE)

let url =
'webcal://www.carleton.edu/student/orgs/sumo/schedule/?loadFeed=calendar&stamp=1714840383'
ctx.body = await getGoogleCalendar(url)
'https://www.carleton.edu/student/orgs/sumo/schedule/?loadFeed=calendar&stamp=1714840383'
ctx.body = await getInternetCalendar(url)
}
1 change: 0 additions & 1 deletion source/ccci-carleton-college/v1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ api.get('/food/named/menu/schulze', menus.schulzeMenu)

// calendar
api.get('/calendar/google', calendar.google)
api.get('/calendar/reason', calendar.reason)
api.get('/calendar/ics', calendar.ics)
api.get('/calendar/named/carleton', calendar.carleton)
api.get('/calendar/named/the-cave', calendar.cave)
Expand Down
46 changes: 27 additions & 19 deletions source/ccci-stolaf-college/v1/calendar.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {googleCalendar} from '../../calendar-google/index.js'
import {reasonCalendar} from '../../calendar-reason/index.js'
import {googleCalendar} from '../../calendar/google.js'
import {ical} from '../../calendar/ical.js'
import {ONE_MINUTE} from '../../ccc-lib/constants.js'
import mem from 'memoize'

export const getGoogleCalendar = mem(googleCalendar, {maxAge: ONE_MINUTE})
export const getReasonCalendar = mem(reasonCalendar, {maxAge: ONE_MINUTE})
export const getInternetCalendar = mem(ical, {maxAge: ONE_MINUTE})

export async function google(ctx) {
ctx.cacheControl(ONE_MINUTE)
Expand All @@ -13,48 +13,56 @@ export async function google(ctx) {
ctx.body = await getGoogleCalendar(calendarId)
}

export async function reason(ctx) {
export async function ics(ctx) {
ctx.cacheControl(ONE_MINUTE)

let {url: calendarUrl} = ctx.query
ctx.body = await getReasonCalendar(calendarUrl)
}

export function ics(ctx) {
ctx.throw(501, 'ICS support is not implemented yet.')
ctx.body = await getInternetCalendar(calendarUrl)
}

export async function stolaf(ctx) {
ctx.cacheControl(ONE_MINUTE)

let id = '5g91il39n0sv4c2bjdv1jrvcpq4ulm4r@import.calendar.google.com'
ctx.body = await getGoogleCalendar(id)
let id = 'https://www.stolaf.edu/apps/calendar/ical.cfm'
ctx.body = await getInternetCalendar(id)
}

export async function oleville(ctx) {
ctx.cacheControl(ONE_MINUTE)

let id = '[email protected]'
ctx.body = await getGoogleCalendar(id)
let id =
'https://calendar.google.com/calendar/ical/opha089fhthpchc0pkdqinca44nl7svk%40import.calendar.google.com/public/basic.ics'
ctx.body = await getInternetCalendar(id)
}

export async function thePause(ctx) {
ctx.cacheControl(ONE_MINUTE)

let id =
'https://calendar.google.com/calendar/ical/stolaf.edu_qkrej5rm8c8582dlnc28nreboc%40group.calendar.google.com/public/basic.ics'
ctx.body = await getInternetCalendar(id)
}

export async function northfield(ctx) {
ctx.cacheControl(ONE_MINUTE)

let id = '[email protected]'
ctx.body = await getGoogleCalendar(id)
let id =
'https://calendar.google.com/calendar/ical/thisisnorthfield%40gmail.com/public/basic.ics'
ctx.body = await getInternetCalendar(id)
}

export async function krlx(ctx) {
ctx.cacheControl(ONE_MINUTE)

let id = '[email protected]'
ctx.body = await getGoogleCalendar(id)
let id =
'https://calendar.google.com/calendar/ical/krlxradio88.1%40gmail.com/public/basic.ics'
ctx.body = await getInternetCalendar(id)
}

export async function ksto(ctx) {
ctx.cacheControl(ONE_MINUTE)

let id = '[email protected]'
ctx.body = await getGoogleCalendar(id)
let id =
'https://calendar.google.com/calendar/ical/stolaf.edu_7u3lgo4rr3o9dchr50q982ribk%40group.calendar.google.com/public/basic.ics'
ctx.body = await getInternetCalendar(id)
}
2 changes: 1 addition & 1 deletion source/ccci-stolaf-college/v1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ api.get('/food/named/menu/schulze', menus.schulzeMenu)

// calendar
api.get('/calendar/google', calendar.google)
api.get('/calendar/reason', calendar.reason)
api.get('/calendar/ics', calendar.ics)
api.get('/calendar/named/stolaf', calendar.stolaf)
api.get('/calendar/named/oleville', calendar.oleville)
api.get('/calendar/named/the-pause', calendar.thePause)
api.get('/calendar/named/northfield', calendar.northfield)
api.get('/calendar/named/krlx-schedule', calendar.krlx)
api.get('/calendar/named/ksto-schedule', calendar.ksto)
Expand Down
2 changes: 1 addition & 1 deletion source/ccci-stolaf-college/v1/orgs.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ONE_HOUR} from '../../ccc-lib/constants.js'
import mem from 'memoize'

import {presence as _presence} from '../../calendar-presence/index.js'
import {presence as _presence} from '../../student-orgs/presence.js'

const CACHE_DURATION = ONE_HOUR * 36

Expand Down
File renamed without changes.