-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
59b41a8
commit cd48039
Showing
25 changed files
with
6,742 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
indent_size = 2 | ||
indent_style = tab | ||
insert_final_newline = true | ||
tab_width = 2 | ||
trim_trailing_whitespace = true | ||
|
||
[*.{json,yaml,yml}] | ||
indent_style = space |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,7 +89,6 @@ out | |
|
||
# Nuxt.js build / generate output | ||
.nuxt | ||
dist | ||
|
||
# Gatsby files | ||
.cache/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
npx --no -- commitlint --edit $1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
npm run-script lint | ||
npm test | ||
npm run build | ||
git add dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"editorconfig": true, | ||
"semi": true, | ||
"singleQuote": true, | ||
"trailingComma": "all", | ||
"importOrderSeparation": false, | ||
"importOrderSortSpecifiers": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Syndicate notes | ||
|
||
This GitHub action lets you publish notes to various services. | ||
|
||
## Usage | ||
|
||
```yaml | ||
on: push | ||
name: 🚀 Deploy website on push | ||
jobs: | ||
web-deploy: | ||
name: 🎉 Deploy | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: 🚚 Get latest code | ||
uses: actions/checkout@v4 | ||
|
||
- name: 📂 Sync files | ||
uses: timseverien/syndicate-notes | ||
with: | ||
feedType: jsonfeed | ||
feedUrl: https://tsev.dev/notes/feed.json | ||
|
||
# Optional: format the message - allows you to add prefixes and suffixes | ||
# contentFormat: '{{content}} {{url}}' | ||
|
||
# Optional: change the cache directory used to track what’s published | ||
# cacheDirectory: .cache/syndicate-notes | ||
|
||
# Integration details | ||
# These are all optional — omit the integrations you don’t want to publish to | ||
discordWebhookId: '...' | ||
discordWebhookToken: '...' | ||
mastodonInstance: 'https://mastodon.social' | ||
mastodonAccessToken: '...' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
name: 'Syndicate notes' | ||
description: 'Publish content elsewhere' | ||
author: 'Tim Severien' | ||
|
||
branding: | ||
color: purple | ||
icon: upload-cloud | ||
|
||
inputs: | ||
cacheDirectory: | ||
description: 'Path to the directory where cache files are stored' | ||
default: .cache/syndicate-notes | ||
|
||
contentFormat: | ||
description: '' | ||
default: '{{content}} {{url}}' | ||
|
||
feedType: | ||
description: 'The feed type of feedUrl' | ||
default: jsonfeed | ||
feedUrl: | ||
description: 'Feed URL where the notes are in' | ||
required: true | ||
|
||
# Discord | ||
discordWebhookId: | ||
description: 'The ID of the Discord Webhook' | ||
discordWebhookToken: | ||
description: 'The token of the Discord Webhook' | ||
|
||
# Mastodon | ||
mastodonInstance: | ||
description: 'The root URL of the Mastodon instance where the toot should be created' | ||
mastodonAccessToken: | ||
description: 'Your access token for the Mastodon API, get it from /settings/applications/new' | ||
|
||
runs: | ||
using: 'node20' | ||
main: 'dist/index.js' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default { | ||
extends: ['@commitlint/config-conventional'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { addMessageToCache, createMessageFilter, getCache, persistCache, } from '@/lib/cache'; | ||
import { getMessages as getMessagesFromJsonFeed } from '@/lib/sources/jsonfeed'; | ||
import * as core from '@actions/core'; | ||
import { publish as syndicate, } from '@tsev/social-gateway'; | ||
import { createDiscordIntegration } from '@tsev/social-gateway/integrations/discord/index'; | ||
import { createMastodonIntegration } from '@tsev/social-gateway/integrations/mastodon/index'; | ||
import { z } from 'zod'; | ||
export function createIntegrationOrNull(inputKeys, factory) { | ||
const inputs = Object.fromEntries(inputKeys.map((input) => [input, core.getInput(input)])); | ||
if (!Object.values(inputs).every((a) => Boolean(a))) { | ||
return null; | ||
} | ||
return factory(inputs); | ||
} | ||
const FEED_KEYS = ['jsonfeed']; | ||
const FEED_PARSE_MAP = { | ||
jsonfeed: getMessagesFromJsonFeed, | ||
}; | ||
const integrations = [ | ||
createIntegrationOrNull(['discordWebhookId', 'discordWebhookToken'], (inputs) => createDiscordIntegration({ | ||
webhookId: inputs.discordWebhookId, | ||
webhookToken: inputs.discordWebhookToken, | ||
})), | ||
createIntegrationOrNull(['mastodonInstance', 'mastodonAccessToken'], (inputs) => createMastodonIntegration({ | ||
accessToken: inputs.mastodonAccessToken, | ||
instanceUrl: inputs.mastodonInstance, | ||
})), | ||
]; | ||
const cacheDirectorySchema = z.string().min(1); | ||
const contentFormatSchema = z.string().min(1); | ||
const feedTypeSchema = z.enum(FEED_KEYS); | ||
const feedUrlSchema = z.string().url(); | ||
try { | ||
const cacheDirectory = cacheDirectorySchema.parse(core.getInput('cacheDirectory')); | ||
const contentFormat = contentFormatSchema.parse(core.getInput('contentFormat')); | ||
const feedType = feedTypeSchema.parse(core.getInput('feedType')); | ||
const feedUrl = new URL(feedUrlSchema.parse(core.getInput('feedUrl'))); | ||
const formatMessage = (content, url) => contentFormat | ||
.replace(/{{content}}/g, content) | ||
.replace(/{{url}}/g, url) | ||
.trim(); | ||
const cache = await getCache(cacheDirectory); | ||
const filterMessage = createMessageFilter(cache); | ||
const messages = await FEED_PARSE_MAP[feedType](feedUrl, { | ||
filter: filterMessage, | ||
format: formatMessage, | ||
}); | ||
const options = { | ||
integrations: integrations.filter((i) => i !== null), | ||
}; | ||
let isMessageFailed = false; | ||
for (const message of messages) { | ||
try { | ||
await syndicate(message, options); | ||
} | ||
catch (error) { | ||
core.error(`Unable to syndicate message ${message.id}`); | ||
isMessageFailed = true; | ||
} | ||
try { | ||
addMessageToCache(cache, message); | ||
await persistCache(cacheDirectory, cache); | ||
} | ||
catch (error) { | ||
core.error('Unable to save to cache'); | ||
} | ||
} | ||
if (isMessageFailed) { | ||
throw new Error('Not all messages were syndicated'); | ||
} | ||
} | ||
catch (error) { | ||
core.setFailed(error.message); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { isBefore } from 'date-fns'; | ||
import { parse, stringify } from 'devalue'; | ||
import * as fs from 'fs-extra'; | ||
import * as path from 'node:path'; | ||
import { z } from 'zod'; | ||
const CACHE_FILE_NAME = 'syndicate-notes.json'; | ||
const cacheDataSchema = z.object({ | ||
lastSyndicated: z.date(), | ||
syndicatedItems: z.set(z.string().min(1)), | ||
}); | ||
function getCacheFilePath(directory) { | ||
return path.relative(directory, CACHE_FILE_NAME); | ||
} | ||
export function addMessageToCache(cache, message) { | ||
const items = new Set(cache.syndicatedItems); | ||
items.add(message.id); | ||
return { | ||
lastSyndicated: new Date(), | ||
syndicatedItems: items, | ||
}; | ||
} | ||
export function createMessageFilter(cache) { | ||
return (message) => { | ||
// Message is explicitly included in syndicated items | ||
if (!cache.syndicatedItems.has(message.id)) | ||
return false; | ||
// Message was published before last syndication | ||
if (message.publishDate && | ||
isBefore(message.publishDate, cache.lastSyndicated)) | ||
return false; | ||
return true; | ||
}; | ||
} | ||
export async function getCache(directory) { | ||
const cacheFilePath = getCacheFilePath(directory); | ||
try { | ||
const buffer = await fs.readFile(cacheFilePath); | ||
const data = parse(buffer.toString()); | ||
return cacheDataSchema.parse(data); | ||
} | ||
catch { | ||
return { | ||
lastSyndicated: new Date(), | ||
syndicatedItems: new Set(), | ||
}; | ||
} | ||
} | ||
export async function persistCache(directory, cache) { | ||
const cacheFilePath = getCacheFilePath(directory); | ||
try { | ||
const data = await fs.writeFile(cacheFilePath, stringify(cache)); | ||
return cacheDataSchema.parse(data); | ||
} | ||
catch { | ||
throw new Error('Unable to write cache'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { parseISO } from 'date-fns'; | ||
import { describe, expect, test } from 'vitest'; | ||
import { addMessageToCache } from './cache'; | ||
describe(addMessageToCache.name, () => { | ||
test('given message, returns cache with message added to syndicatedItems list', () => { | ||
const cache = { | ||
lastSyndicated: parseISO('2024-01-01'), | ||
syndicatedItems: new Set(), | ||
}; | ||
const message = { | ||
id: 'foobar', | ||
content: 'Foobar!', | ||
}; | ||
const result = addMessageToCache(cache, message); | ||
expect(result).toStrictEqual(expect.objectContaining({ | ||
syndicatedItems: new Set(['foobar']), | ||
})); | ||
}); | ||
test('given message, returns cache with updated lastSyndicated ', () => { | ||
const cache = { | ||
lastSyndicated: parseISO('2024-01-01'), | ||
syndicatedItems: new Set(), | ||
}; | ||
const message = { | ||
id: 'foobar', | ||
content: 'Foobar!', | ||
}; | ||
const result = addMessageToCache(cache, message); | ||
expect(result).not.toStrictEqual(expect.objectContaining({ | ||
lastSyndicated: parseISO('2024-01-01'), | ||
})); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { z } from 'zod'; | ||
const jsonFeedAttachmentSchema = z.object({ | ||
mime_type: z.string().min(3).includes('/'), | ||
url: z.string().url(), | ||
title: z.string().optional(), | ||
size_in_bytes: z.number().optional(), | ||
duration_in_seconds: z.number().optional(), | ||
}); | ||
const jsonFeedAuthorSchema = z.object({ | ||
avatar: z.string().url().optional(), | ||
name: z.string().optional(), | ||
url: z.string().url().optional(), | ||
}); | ||
const jsonFeedItemSchema = z.object({ | ||
id: z.string().min(1), | ||
// One of these should be present | ||
content_html: z.string().optional(), | ||
content_text: z.string().optional(), | ||
authors: jsonFeedAuthorSchema.array().optional(), | ||
banner_image: z.string().url().optional(), | ||
date_modified: z.string().datetime().optional(), | ||
date_published: z.string().datetime().optional(), | ||
external_url: z.string().url().optional(), | ||
image: z.string().url().optional(), | ||
language: z.string().optional(), | ||
summary: z.string().optional(), | ||
tags: z.string().array().optional(), | ||
title: z.string().optional(), | ||
url: z.string().url().optional(), | ||
attachments: jsonFeedAttachmentSchema.array().optional(), | ||
}); | ||
const jsonFeedSchema = z.object({ | ||
version: z.string().startsWith('https://jsonfeed.org/version/').url(), | ||
title: z.string(), | ||
items: jsonFeedItemSchema.array(), | ||
authors: jsonFeedAuthorSchema.array().optional(), | ||
description: z.string().optional(), | ||
expired: z.boolean().optional(), | ||
favicon: z.string().url().optional(), | ||
feed_url: z.string().url().optional(), | ||
home_page_url: z.string().url().optional(), | ||
icon: z.string().url().optional(), | ||
language: z.string().optional(), | ||
next_url: z.string().optional(), | ||
user_comment: z.string().optional(), | ||
// Deprecated: JSON feed 1.0 | ||
author: jsonFeedAuthorSchema.optional(), | ||
}); | ||
export const getMessages = async (url, { format, filter }) => { | ||
try { | ||
const response = await fetch(url); | ||
const json = await response.json(); | ||
const feed = jsonFeedSchema.parse(json); | ||
return feed.items | ||
.map((item) => { | ||
const contentMessage = item.content_text ?? item.content_html ?? ''; | ||
const contentUrl = item.url ?? ''; | ||
return { | ||
id: item.id, | ||
language: item.language ?? feed.language ?? undefined, | ||
content: format(contentMessage, contentUrl ?? ''), | ||
}; | ||
}) | ||
.filter(filter); | ||
} | ||
catch (error) { | ||
throw new Error(`Unable to read feed ${url}`); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import pluginJs from '@eslint/js'; | ||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; | ||
import globals from 'globals'; | ||
import tseslint from 'typescript-eslint'; | ||
|
||
export default [ | ||
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } }, | ||
pluginJs.configs.recommended, | ||
...tseslint.configs.recommended, | ||
eslintPluginPrettierRecommended, | ||
]; |
Oops, something went wrong.