Skip to content

Instantly share code, notes, and snippets.

@brianglass
Last active February 6, 2025 01:00
Show Gist options
  • Save brianglass/fcc53b5948e0dcbef2b4598964cfeb4e to your computer and use it in GitHub Desktop.
Save brianglass/fcc53b5948e0dcbef2b4598964cfeb4e to your computer and use it in GitHub Desktop.
Fetch Liturgical Information from Antiochian.org
import json
import sys
from datetime import date, datetime, timedelta
from urllib.parse import urljoin
import requests
HOST = 'www.antiochian.org'
CLIENT_ID = 'antiochian_api'
CLIENT_SECRET = 'TAxhx@9tH(l^MgQ9FWE8}T@NWUT9U)'
class Antiochian(requests.Session):
base_url = 'https://antiochian.org/'
token_endpoint = urljoin(base_url, '/connect/token')
liturgical_endpoint = urljoin(base_url, '/api/antiochian/LiturgicalDay/{}')
id_anchor = None
days = {}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.headers['Host'] = HOST
def authenticate(self):
response = session.post(self.token_endpoint, data={
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'grant_type': 'client_credentials',
})
response.raise_for_status()
token_data = response.json()
self.headers['Authorization'] = f'Bearer {token_data["access_token"]}'
# populate self.id_anchor
self.get_liturgical_day()
def get_id_from_date(self, dt):
anchor_dt, anchor_id = self.id_anchor
span = dt - anchor_dt
return span.days + anchor_id
def get_liturgical_day(self, dt=None):
if dt in self.days:
# Fetch from cache
return self.days[dt]
day_id = self.get_id_from_date(dt) if dt else 0
url = self.liturgical_endpoint.format(day_id)
response = self.get(url)
if not response.ok:
return None
response_data = response.json()
day = response_data['liturgicalDay']
if not self.id_anchor:
dt = datetime.strptime(day['originalCalendarDate'], '%Y-%m-%d')
self.id_anchor = dt.date(), day['itemId']
# cache
self.days[dt] = day
return day
if __name__ == '__main__':
session = Antiochian()
session.authenticate()
for i in range(10):
dt = date.today() + timedelta(days=i)
day = session.get_liturgical_day(dt)
print(
f'{dt}: {day["feastDayTitle"].title()}\n'
f'\t{day["fastDesignation"].title()}\n'
)
# The api always returns 3 readings, but not all are used
for num in range(1, 4):
reading_title = day[f'reading{num}Title']
if reading_title:
print(f'\t{reading_title.title()}')
print()
@Edesem
Copy link

Edesem commented Feb 6, 2025

import axios, { AxiosInstance } from 'axios';

const HOST = 'www.antiochian.org';
const CLIENT_ID = 'antiochian_api';
const CLIENT_SECRET = 'TAxhx@9tH(l^MgQ9FWE8}T@NWUT9U)';

interface LiturgicalDay {
    originalCalendarDate: string;
    itemId: number;
    feastDayTitle: string;
    fastDesignation: string;
    reading1Title?: string;
    reading2Title?: string;
    reading3Title?: string;
}

class Antiochian {
    private baseUrl = 'https://antiochian.org/';
    private tokenEndpoint = `${this.baseUrl}connect/token`;
    private liturgicalEndpoint = (id: number) => `${this.baseUrl}api/antiochian/LiturgicalDay/${id}`;
    private axiosInstance: AxiosInstance;
    private idAnchor?: [Date, number];
    private days: Record<string, LiturgicalDay> = {};

    constructor() {
        this.axiosInstance = axios.create({
            headers: { 'Host': HOST }
        });
    }

    async authenticate(): Promise<void> {
        const response = await this.axiosInstance.post(this.tokenEndpoint, new URLSearchParams({
            client_id: CLIENT_ID,
            client_secret: CLIENT_SECRET,
            grant_type: 'client_credentials',
        }).toString(), {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });

        const tokenData = response.data;
        this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${tokenData.access_token}`;
        await this.getLiturgicalDay(); // Populate idAnchor
    }

    private getIdFromDate(dt: Date): number {
        if (!this.idAnchor) throw new Error('ID Anchor not initialized');
        const [anchorDate, anchorId] = this.idAnchor;
        const span = Math.floor((dt.getTime() - anchorDate.getTime()) / (1000 * 60 * 60 * 24));
        return span + anchorId;
    }

    async getLiturgicalDay(dt?: Date): Promise<LiturgicalDay | null> {
        const key = dt ? dt.toISOString().split('T')[0] : '0';
        if (this.days[key]) return this.days[key];

        const dayId = dt ? this.getIdFromDate(dt) : 0;
        const response = await this.axiosInstance.get(this.liturgicalEndpoint(dayId));

        if (response.status !== 200) return null;

        const day: LiturgicalDay = response.data.liturgicalDay;

        if (!this.idAnchor) {
            const anchorDate = new Date(day.originalCalendarDate);
            this.idAnchor = [anchorDate, day.itemId];
        }

        this.days[key] = day; // Cache result
        return day;
    }
}

(async () => {
    const session = new Antiochian();
    await session.authenticate();

    for (let i = 0; i < 10; i++) {
        const dt = new Date();
        dt.setDate(dt.getDate() + i);
        const day = await session.getLiturgicalDay(dt);
        if (!day) continue;

        console.log(`${dt.toDateString()}: ${day.feastDayTitle}`);
        console.log(`\t${day.fastDesignation}`);
        
        [day.reading1Title, day.reading2Title, day.reading3Title].forEach((reading, index) => {
            if (reading) console.log(`\tReading ${index + 1}: ${reading}`);
        });
        console.log();
    }
})();

Converted to TypeScript

@Edesem
Copy link

Edesem commented Feb 6, 2025

Retrieve liturgical texts for the current date, in TypeScript

import axios, { AxiosInstance } from 'axios';

const HOST = 'www.antiochian.org';
const CLIENT_ID = 'antiochian_api';
const CLIENT_SECRET = 'TAxhx@9tH(l^MgQ9FWE8}T@NWUT9U)';

interface LiturgicalText {
    publicUrl: string;
    typeOfService: string;
}

class Antiochian {
    private baseUrl = 'https://antiochian.org/';
    private tokenEndpoint = `${this.baseUrl}connect/token`;
    private liturgicalTextEndpoint = (date: string) => `${this.baseUrl}api/antiochian/LiturgicalTexts/${date}`;
    private axiosInstance: AxiosInstance;
    private texts: Record<string, LiturgicalText[]> = {};

    constructor() {
        this.axiosInstance = axios.create({
            headers: { 'Host': HOST }
        });
    }

    async authenticate(): Promise<void> {
        const response = await this.axiosInstance.post(this.tokenEndpoint, new URLSearchParams({
            client_id: CLIENT_ID,
            client_secret: CLIENT_SECRET,
            grant_type: 'client_credentials',
        }).toString(), {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });

        const tokenData = response.data;
        this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${tokenData.access_token}`;
    }

    async getLiturgicalText(date: Date): Promise<LiturgicalText[] | null> {
        const key = date.toISOString().split('T')[0]; // Format the date as YYYY-MM-DD
        if (this.texts[key]) return this.texts[key];

        const response = await this.axiosInstance.get(this.liturgicalTextEndpoint(key));

        if (response.status !== 200) return null;

        const texts: LiturgicalText[] = response.data;

        this.texts[key] = texts; // Cache result
        return texts;
    }
}

(async () => {
    const session = new Antiochian();
    await session.authenticate();

    // Get today's date
    const dt = new Date();
    
    const texts = await session.getLiturgicalText(dt);
    if (!texts) {
        console.log('No liturgical texts found for today.');
        return;
    }

    console.log(`${dt.toDateString()}:`);

    texts.forEach((text) => {
        console.log(`\tType of Service: ${text.typeOfService}`);
        console.log(`\tPublic URL: ${text.publicUrl}`);
    });

    console.log();
})();

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