feat(matrix/fediverse): allow users to directly reply on Fediverse posts from Matrix, using <mx-reply> on our new meta field.

feat(matrix/fediverse): allow users to favorite, reblog, and redact Fediverse posts from Matrix, using m.reaction on our new meta field.
feat(fediverse): add suggestions for follow/unfollow commands.
refactor(matrix/fediverse): add meta field for commands by reaction, and commands by reply.
refactor(fediverse): relax polling on timeline/notifs thru new handling.
This commit is contained in:
vulet 2021-02-14 15:57:35 +08:00
parent 430fb350c1
commit 42563ebc35
26 changed files with 619 additions and 375 deletions

View file

@ -1,15 +1,15 @@
const { JSDOM } = require("jsdom");
const { JSDOM } = require('jsdom');
const headers = ({ domain, userAgent }) => ({
'Host': `${domain}`,
'User-Agent': `${userAgent}`
Host: `${domain}`,
'User-Agent': `${userAgent}`,
});
const nitter = async (instance, url) => {
const req = await instance({ method: 'GET', url });
if (req.statusText !== 'OK') throw req;
const dom = new JSDOM(req.data);
const document = dom.window.document;
const { document } = dom.window;
const tweet = document.querySelector('#m');
const stats = tweet.querySelectorAll('.tweet-body > .tweet-stats .icon-container');
const quote = tweet.querySelector('.tweet-body > .quote');
@ -33,8 +33,8 @@ const nitter = async (instance, url) => {
stats: {
replies: stats[0].textContent.trim(),
retweets: stats[1].textContent.trim(),
favorites: stats[2].textContent.trim()
}
favorites: stats[2].textContent.trim(),
},
};
};
@ -49,24 +49,25 @@ const card = (tweet, base, check, path) =>
(tweet.hasAttachments ? '<blockquote><b>This tweet has attached media.</b></blockquote>' : '') +
(tweet.isReply ? tweet.isReply === 'unavailable' ? '<blockquote>Replied Tweet is unavailable</blockquote>' : `<blockquote><b><a href="${base}${tweet.isReply.path}">Replied Tweet</a></b><br /><b><i>${tweet.isReply.text.replace('\n', '<br />')}</i></b></blockquote>` : '') +
(tweet.quote ? `<blockquote><b><a href="${base}${tweet.quote.path}">Quoted Tweet</a></b><br /><b><i>${tweet.quote.text.replace('\n', '<br />')}</i></b></blockquote>` : '');
const run = async (matrixClient, { roomId }, userInput) => {
const run = async (roomId, userInput) => {
const instance = axios.create({
baseURL: `https://${config.nitter.domain}`,
headers: headers(config.nitter),
transformResponse: [],
timeout: 10 * 1000
timeout: 10 * 1000,
});
const tweet = await nitter(instance, userInput);
return await matrixClient.sendHtmlNotice(roomId, '', card(tweet, `https://${config.nitter.domain}`, config.nitter.check, userInput));
}
};
exports.runQuery = async (client, room, userInput) => {
exports.runQuery = async (roomId, event, userInput) => {
try {
const url = new URL(userInput);
if(!config.nitter.domains.includes(url.hostname)) throw '';
if(!/^\/[^/]+\/status\/\d+\/?$/.test(url.pathname)) throw '';
return await run(client, room, url.pathname);
} catch(e) {
return client.sendHtmlNotice(room.roomId, 'Sad!', `<strong>Sad!</strong>`).catch(()=>{});
if (!config.nitter.domains.includes(url.hostname)) throw '';
if (!/^\/[^/]+\/status\/\d+\/?$/.test(url.pathname)) throw '';
return await run(roomId, url.pathname);
} catch (e) {
return matrixClient.sendHtmlNotice(roomId, 'Sad!', '<strong>Sad!</strong>').catch(() => {});
}
};