Skip to content
Snippets Groups Projects
Commit d90ee51d authored by Benedikt Hermann's avatar Benedikt Hermann
Browse files

added episode list

parent 7281070f
No related branches found
No related tags found
No related merge requests found
/node_modules /node_modules
package-lock.json
.env
import { ActivityPubNote, ActivityPubOutbox, EpisodeResponse } from "./types";
export function transformEpisode(response: EpisodeResponse) {
const item = response.item;
return {
"@context": "https://www.w3.org/ns/activitystreams",
"id": item.assetId,
"type": "PodcastEpisode",
"published": item.published,
"updated": item.updated,
"attributedTo": item.show.coreId,
"externalId": item.url,
"title": item.title,
"description": {
"type": "Note",
"mediaType": "text/markdown",
"content": item.description
},
"image": item.imagesList.length > 0 ? {
"type": "Image",
"url": item.imagesList[0].url
} : undefined,
"audio": item.audioList.map(audio => ({
"type": "Audio",
"name": audio.title,
"mediaType": "audio/mpeg",
"url": audio.href
})),
"partOf": {
"type": "PodcastSeries",
"id": item.show.coreId,
"externalId": item.show.externalId,
"name": item.show.title,
"attributedTo": item.show.publisher.coreId,
"publisher": {
"type": "BroadcastService",
"name": item.show.publisher.title,
"url": item.show.publisher.url,
"organization": {
"type": "Organization",
"name": item.show.publisher.organization.name,
"url": item.show.publisher.organization.url
}
}
}
};
}
export function transformEpisodeList(input: any): ActivityPubOutbox {
const baseUrl = "https://example.com/outbox";
const actorUrl = "https://example.com/actor";
const orderedItems: ActivityPubNote[] = input.show.items.nodes.map((item: any) => ({
id: item.assetId,
type: "Note",
actor: actorUrl,
published: new Date().toISOString(),
to: ["https://www.w3.org/ns/activitystreams#Public"],
content: item.title,
url: item.url,
}));
return {
"@context": "https://www.w3.org/ns/activitystreams",
id: baseUrl,
type: "OrderedCollection",
orderedItems,
};
}
\ No newline at end of file
import express from 'express'; import express from 'express';
import { Client, cacheExchange, fetchExchange, gql } from '@urql/core'; import { Client, cacheExchange, fetchExchange, gql } from '@urql/core';
import { Response } from './types'; import { EpisodeResponse } from './types';
import { transformEpisode, transformEpisodeList } from './mappings';
const app = express(); const app = express();
const PORT = 3000; const PORT = 3000;
const audiothekApi = new Client({ const audiothekApi = new Client({
url: 'https://api.ardaudiothek.de/graphql', url: 'https://api.ardaudiothek.de/graphql',
exchanges: [cacheExchange, fetchExchange], exchanges: [cacheExchange, fetchExchange],
}); });
const query = gql` const query = gql`
query Item { query Item {
item(id: "14278953") { item(id: "14278953") {
show { show {
coreId coreId
externalId externalId
title title
url url
publisher: publicationService { publisher: publicationService {
coreId coreId
dvbServiceId dvbServiceId
title title
url url
organizationName organizationName
organization { organization {
name name
url url
} }
} }
coreDocument coreDocument
} }
assetId assetId
url: sharingUrl url: sharingUrl
title title
description description
published: publishDate published: publishDate
updated: core(key: "modified") updated: core(key: "modified")
imagesList { imagesList {
url url
title title
} }
coreDocument coreDocument
audioList { audioList {
title title
audioCodec audioCodec
href href
} }
} }
} }
`; `;
async function fetchData(query: any, variables: any) { async function fetchData<R>(query: any, variables: any) {
const result = await audiothekApi.query<Response>(query, variables).toPromise(); const result = await audiothekApi.query<R>(query, variables).toPromise();
if (result.error) { if (result.error) {
throw result.error; throw result.error;
} }
// console.log(JSON.stringify(result.data, null, 2)); // console.log(JSON.stringify(result.data, null, 2));
return result.data; return result.data;
} }
function transformEpisodeToActivityPub(response: Response) {
const item = response.item;
return {
"@context": "https://www.w3.org/ns/activitystreams",
"id": item.assetId,
"type": "PodcastEpisode",
"published": item.published,
"updated": item.updated,
"attributedTo": item.show.coreId,
"externalId": item.url,
"title": item.title,
"description": {
"type": "Note",
"mediaType": "text/markdown",
"content": item.description
},
"image": item.imagesList.length > 0 ? {
"type": "Image",
"url": item.imagesList[0].url
} : undefined,
"audio": item.audioList.map(audio => ({
"type": "Audio",
"name": audio.title,
"mediaType": "audio/mpeg",
"url": audio.href
})),
"partOf": {
"type": "PodcastSeries",
"id": item.show.coreId,
"externalId": item.show.externalId,
"name": item.show.title,
"attributedTo": item.show.publisher.coreId,
"publisher": {
"type": "BroadcastService",
"name": item.show.publisher.title,
"url": item.show.publisher.url,
"organization": {
"type": "Organization",
"name": item.show.publisher.organization.name,
"url": item.show.publisher.organization.url
}
}
}
};
}
async function main() { async function main() {
const response = await fetchData(query, { id: '14278953' }); const response = await fetchData(query, { id: '14278953' });
if (response) { if (response) {
const activityPubEpisode = transformEpisodeToActivityPub(response); const activityPubEpisode = transformEpisode(response);
console.log(JSON.stringify(activityPubEpisode, null, 2)); console.log(JSON.stringify(activityPubEpisode, null, 2));
} }
} }
// main(); // main();
...@@ -122,43 +79,99 @@ async function main() { ...@@ -122,43 +79,99 @@ async function main() {
app.get('/:actor/episodes/:id', async (req, res) => { app.get('/:actor/episodes/:id', async (req, res) => {
try { try {
const data = await fetchData(query, { id: req.params.id }); const data = await fetchData<EpisodeResponse>(query, { id: req.params.id });
if (!data) { if (!data) {
res.status(404).json({ error: 'Episode not found' }); res.status(404).json({ error: 'Episode not found' });
return; return;
} }
// TODO: verifiy that the actor is the same as the attributedTo field
// TODO: verifiy that the actor is the same as the attributedTo field const activityPubDocument = transformEpisode(data);
res.json(activityPubDocument);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch episode data' });
}
});
const activityPubDocument = transformEpisodeToActivityPub(data); app.get('/:actor/episodes', async (req, res) => {
res.json(activityPubDocument); const query = gql`
} catch (error) { query ShowWithEpisodes($showId: ID!, $first: Int = 10, $offset: Int) {
res.status(500).json({ error: 'Failed to fetch episode data' }); show(id: $showId) {
} coreId
externalId
title
url
items(first: $first, orderBy: PUBLISH_DATE_DESC, offset: $offset, condition: {
isPublished: true
}) {
nodes {
assetId
title
url: sharingUrl
}
}
publisher: publicationService {
coreId
dvbServiceId
title
url
organizationName
organization {
name
url
}
}
}
}
`;
try {
// TODO: verifiy that the actor is the same as the attributedTo field
const data = await fetchData<any>(query, { showId: "urn:ard:show:a42d1ea0b4a07053" });
if (!data) {
res.status(404).json({ error: 'Episode not found' });
return;
}
const activityPubDocument = transformEpisodeList(data);
res.json(activityPubDocument);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch episode data' });
}
}); });
app.get('/@blaue-couch', async (req, res) => { app.get('/@blaue-couch', async (req, res) => {
res.json({ res.json({
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": "urn:ard:show:a42d1ea0b4a07053", "id": "urn:ard:show:a42d1ea0b4a07053",
"type": "Service", "type": "Service",
"name": "Blaue Couch", "name": "Blaue Couch",
"externalId": "https://feeds.br.de/blaue-couch/feed.xml", "externalId": "https://feeds.br.de/blaue-couch/feed.xml",
"attributedTo": "urn:ard:publisher:c4a9cee041835529", "attributedTo": "urn:ard:publisher:c4a9cee041835529",
"publisher": { "publisher": {
"type": "BroadcastService", "type": "BroadcastService",
"name": "BAYERN 1", "name": "BAYERN 1",
"url": "https://www.ardaudiothek.de/radio/br/bayern-1/", "url": "https://www.ardaudiothek.de/radio/br/bayern-1/",
"organization": { "organization": {
"type": "Organization", "type": "Organization",
"name": "BR", "name": "BR",
"url": "https://www.ardaudiothek.de/radio/br/" "url": "https://www.ardaudiothek.de/radio/br/"
} }
} }
}); });
}); });
app.get('/', (req, res) => res.redirect('/@blaue-couch'));
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`); console.log(`Server running at http://localhost:${PORT}`);
}); });
...@@ -30,6 +30,23 @@ export type Item = { ...@@ -30,6 +30,23 @@ export type Item = {
show: Show; show: Show;
}; };
export type Response = { export type EpisodeResponse = {
item: Item; item: Item;
};
export type ActivityPubOutbox = {
"@context": string;
id: string;
type: string;
orderedItems: ActivityPubNote[];
};
export type ActivityPubNote = {
id: string;
type: string;
actor: string;
published: string;
to: string[];
content: string;
url: string;
}; };
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment