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