]> git.r.bdr.sh - rbdr/linkding-linkblog-updater/blob - index.js
279594aa6403152f991cff9cbf09404ab7d625f4
[rbdr/linkding-linkblog-updater] / index.js
1 const { exec } = require('child_process');
2 const { resolve, join} = require('path');
3 const { rm, writeFile } = require('fs/promises');
4 const { promisify } = require('util');
5 const Pinboard = require('node-pinboard').default;
6
7 const internals = {
8 exec: promisify(exec),
9
10 apiToken: process.env.PINBOARD_TOKEN,
11 blogUrl: process.env.BLOG_URL,
12 archiveUrl: process.env.ARCHIVE_URL,
13 blogPublicUrl: process.env.BLOG_PUBLIC_URL,
14 archivePublicUrl: process.env.ARCHIVE_PUBLIC_URL,
15 tootToken: process.env.TOOT_TOKEN,
16 mastodonDomain: process.env.MASTODON_DOMAIN,
17
18 date: (new Date()).toISOString().split('T')[0],
19
20 generateGemtext(title, text) {
21
22 return `# ${title}\n\n${text}`;
23 },
24
25 getText(posts) {
26
27 return posts.map((post) => {
28
29 return `=> ${post.href} ${post.description}\n${post.extended}`;
30 }).join('\n\n');
31 },
32
33 getTitle(posts) {
34
35 if (posts.length === 1) {
36 return `Link: ${posts[0].description}`;
37 }
38 return `${posts.length} links for ${internals.date}`;
39 },
40
41 slugify(text) {
42
43 return text.toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/ +/g, '-')
44 },
45
46 async toot(title) {
47
48 const body = new FormData();
49 body.set(
50 'status',
51 `New post: ${title}\n\nAvailable on:\n\n♊️ the gemini archive ${internals.archivePublicUrl}\n\n or, the ephemeral blog 🌐: ${internals.blogPublicUrl}`
52 );
53 return fetch(`https://${internals.mastodonDomain}/api/v1/statuses`, {
54 method: 'post',
55 headers: {
56 Authorization: `Bearer ${internals.tootToken}`,
57 },
58 body
59 });
60 }
61
62 };
63
64
65 async function run() {
66 const pinboard = new Pinboard(internals.apiToken);
67
68 const pins = await pinboard.all({ tag: 'linkblog' });
69
70 if (pins.length === 0) {
71 console.error('No links to post');
72 return;
73 }
74
75 const title = internals.getTitle(pins);
76 const text = internals.getText(pins);
77 const gemtext = internals.generateGemtext(title, text);
78 const filename = internals.slugify(title);
79
80 const gemfile = resolve(join(__dirname, `${filename}.gmi`));
81 await writeFile(gemfile, gemtext);
82 await internals.exec(`blog add ${gemfile}`);
83 await internals.exec(`blog publish ${internals.blogUrl}`);
84 await internals.exec(`blog publish-archive ${internals.archiveUrl}`);
85 await rm(gemfile);
86
87 for (const pin of pins) {
88 await pinboard.add({
89 url: pin.href,
90 description: pin.description,
91 extended: pin.extended,
92 dt: pin.time,
93 tags: pin.tags.replace('linkblog', 'linkblog-posted')
94 });
95 }
96
97 if (internals.tootToken) {
98 await internals.toot(title);
99 }
100 }
101
102 run()
103 .then(() => process.exit(0))
104 .catch((err) => {
105 console.error(err);
106 process.exit(1);
107 })