]> git.r.bdr.sh - rbdr/linkding-linkblog-updater/blame - index.js
Add GPL
[rbdr/linkding-linkblog-updater] / index.js
CommitLineData
89326417
RBR
1const { exec } = require('child_process');
2const { resolve, join} = require('path');
3const { rm, writeFile } = require('fs/promises');
2390d230 4const { promisify } = require('util');
89326417
RBR
5
6const internals = {
7 exec: promisify(exec),
2390d230 8
fdb5251c
RBR
9 apiUrl: process.env.API_URL,
10 apiToken: process.env.API_TOKEN,
2390d230
RBR
11 blogUrl: process.env.BLOG_URL,
12 archiveUrl: process.env.ARCHIVE_URL,
9d9552d1
RBR
13 blogPublicUrl: process.env.BLOG_PUBLIC_URL,
14 archivePublicUrl: process.env.ARCHIVE_PUBLIC_URL,
278296c2 15 tootToken: process.env.TOOT_TOKEN,
9d9552d1 16 mastodonDomain: process.env.MASTODON_DOMAIN,
2390d230 17
89326417
RBR
18 date: (new Date()).toISOString().split('T')[0],
19
f0b2faff 20 generateGemtext(title, text) {
89326417 21
f0b2faff
RBR
22 return `# ${title}\n\n${text}`;
23 },
89326417 24
f0b2faff
RBR
25 getText(posts) {
26
278296c2 27 return posts.map((post) => {
f0b2faff 28
fe344d4e
RBR
29 const title = post.title || post.website_title
30 return `=> ${post.url} ${title}\n${post.notes}`;
89326417 31 }).join('\n\n');
f0b2faff
RBR
32 },
33
34 getTitle(posts) {
89326417 35
fe344d4e
RBR
36 const title = posts[0].title || posts[0].website_title
37
f0b2faff 38 if (posts.length === 1) {
fe344d4e 39 return `Link: ${title}`;
f0b2faff
RBR
40 }
41 return `${posts.length} links for ${internals.date}`;
42 },
43
44 slugify(text) {
45
46 return text.toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/ +/g, '-')
278296c2
RBR
47 },
48
49 async toot(title) {
50
51 const body = new FormData();
52 body.set(
53 'status',
9d9552d1 54 `New post: ${title}\n\nAvailable on:\n\nā™Šļø the gemini archive ${internals.archivePublicUrl}\n\n or, the ephemeral blog šŸŒ: ${internals.blogPublicUrl}`
278296c2 55 );
9d9552d1 56 return fetch(`https://${internals.mastodonDomain}/api/v1/statuses`, {
fdb5251c 57 method: 'POST',
278296c2
RBR
58 headers: {
59 Authorization: `Bearer ${internals.tootToken}`,
60 },
61 body
62 });
fdb5251c
RBR
63 },
64
65 async getBookmarks() {
66
67 const url = join(internals.apiUrl, 'bookmarks') + '?q=%23linkblog';
68 const response = await fetch(url, {
69 headers: {
70 'Content-Type': 'application/json',
71 Authorization: `Token ${internals.apiToken}`
72 }
73 });
74 const data = await response.json();
75
76 return data.results;
77 },
278296c2 78
fdb5251c
RBR
79 async updateBookmark(bookmark) {
80
fdb5251c
RBR
81 const url = join(internals.apiUrl, 'bookmarks', `${bookmark.id}`)
82 const response = await fetch(url, {
83 method: 'PATCH',
84 body: JSON.stringify(
85 {
86 tag_names: bookmark.tag_names.map((tag) => tag === 'linkblog' ? 'linkblog-posted' : tag)
87 }
88 ),
89 headers: {
90 'Content-Type': 'application/json',
91 Authorization: `Token ${internals.apiToken}`
92 }
93 });
fdb5251c 94 }
89326417
RBR
95};
96
97
98async function run() {
fdb5251c 99 const bookmarks = await internals.getBookmarks();
89326417 100
fdb5251c 101 if (bookmarks.length === 0) {
89326417
RBR
102 console.error('No links to post');
103 return;
104 }
105
fdb5251c
RBR
106 const title = internals.getTitle(bookmarks);
107 const text = internals.getText(bookmarks);
f0b2faff
RBR
108 const gemtext = internals.generateGemtext(title, text);
109 const filename = internals.slugify(title);
110
111 const gemfile = resolve(join(__dirname, `${filename}.gmi`));
89326417 112 await writeFile(gemfile, gemtext);
70dc59db
RBR
113 await internals.exec(`blog add ${gemfile}`);
114 await internals.exec(`blog publish ${internals.blogUrl}`);
115 await internals.exec(`blog publish-archive ${internals.archiveUrl}`);
89326417
RBR
116 await rm(gemfile);
117
fdb5251c
RBR
118 for (const bookmark of bookmarks) {
119 await internals.updateBookmark(bookmark);
89326417 120 }
278296c2
RBR
121
122 if (internals.tootToken) {
123 await internals.toot(title);
124 }
89326417
RBR
125}
126
127run()
128 .then(() => process.exit(0))
129 .catch((err) => {
130 console.error(err);
131 process.exit(1);
132 })