Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
H
HackITM ActivityPub Fediverse Experimente
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Releases
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Andreas Hubel
HackITM ActivityPub Fediverse Experimente
Commits
d90ee51d
Commit
d90ee51d
authored
2 months ago
by
Benedikt Hermann
Browse files
Options
Downloads
Patches
Plain Diff
added episode list
parent
7281070f
No related branches found
No related tags found
No related merge requests found
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
.gitignore
+2
-0
2 additions, 0 deletions
.gitignore
src/mappings.ts
+69
-0
69 additions, 0 deletions
src/mappings.ts
src/server.ts
+142
-129
142 additions, 129 deletions
src/server.ts
src/types.ts
+18
-1
18 additions, 1 deletion
src/types.ts
with
231 additions
and
130 deletions
.gitignore
+
2
−
0
View file @
d90ee51d
/node_modules
/node_modules
package-lock.json
.env
This diff is collapsed.
Click to expand it.
src/mappings.ts
0 → 100644
+
69
−
0
View file @
d90ee51d
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
This diff is collapsed.
Click to expand it.
src/server.ts
+
142
−
129
View file @
d90ee51d
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
<
R
esponse
>
(
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
=
transformEpisode
ToActivityPub
(
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
}
`
);
});
});
This diff is collapsed.
Click to expand it.
src/types.ts
+
18
−
1
View file @
d90ee51d
...
@@ -30,6 +30,23 @@ export type Item = {
...
@@ -30,6 +30,23 @@ export type Item = {
show
:
Show
;
show
:
Show
;
};
};
export
type
Response
=
{
export
type
Episode
Response
=
{
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
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment