diff --git a/commands/nitter.js b/commands/nitter.js
new file mode 100644
index 0000000..9770f08
--- /dev/null
+++ b/commands/nitter.js
@@ -0,0 +1,63 @@
+const axios = require('axios');
+const { JSDOM } = require("jsdom");
+
+const headers = ({ domain, 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 tweet = dom.window.document.querySelector('#m');
+ const stats = tweet.querySelectorAll('.tweet-body > .tweet-stats .icon-container');
+ const quote = tweet.querySelector('.tweet-body > .quote a.quote-link');
+ return {
+ text: tweet.querySelector('.tweet-body > .tweet-content').innerHTML,
+ date: tweet.querySelector('.tweet-body > .tweet-published').textContent,
+ name: tweet.querySelector('.tweet-body > div .fullname').textContent,
+ handle: tweet.querySelector('.tweet-body > div .username').textContent,
+ hasAttachments: !!tweet.querySelector('.tweet-body > .attachments'),
+ quote: quote ? quote.href : null,
+ stats: {
+ replies: stats[0].textContent.trim(),
+ retweets: stats[1].textContent.trim(),
+ favorites: stats[2].textContent.trim()
+ }
+ };
+};
+
+const card = (tweet, base, path) =>
+`${tweet.name} ` +
+`${tweet.date} ` +
+`🗨️ ${tweet.stats.replies} ` +
+`🔁 ${tweet.stats.retweets} ` +
+`❤️ ${tweet.stats.favorites} ` +
+`
${tweet.text.replace('\n', '` + +(tweet.hasAttachments ? '
')}
This tweet has attached media.' : '') + +(tweet.quote ? `
Quoted Tweet` : ''); + +const run = async (matrixClient, { roomId }, userInput, registrar) => { + const config = registrar.config.nitter; + const instance = axios.create({ + baseURL: `https://${config.domain}`, + headers: headers(config), + transformResponse: [], + timeout: 10 * 1000 + }); + const tweet = await nitter(instance, userInput); + return await matrixClient.sendHtmlNotice(roomId, '', card(tweet, `https://${config.domain}`, userInput)); +} + +exports.runQuery = (client, room, userInput, registrar) => { + let url = null; + try { + url = new URL(userInput); + if(!registrar.config.nitter.domains.includes(url.hostname)) throw ''; + if(!/^\/[^/]+\/status\/\d+\/?$/.test(url.pathname)) throw ''; + } catch(e) { + return client.sendHtmlNotice(roomId, 'Sad!', `Sad!`).catch(()=>{}); + } + return run(client, room, url.pathname, registrar); +}; diff --git a/config.js b/config.js index eca558b..e3ec895 100644 --- a/config.js +++ b/config.js @@ -8,5 +8,10 @@ module.exports = { archive: { domain: 'archive.is', userAgent: 'Mozilla/4.0 (compatible; Beep Boop)' + }, + nitter: { + domain: 'nitter.net', + userAgent: 'Mozilla/4.0 (compatible; Beep Boop)', + domains: [ 'nitter.net', 'twitter.com' ] } }; diff --git a/main.js b/main.js index e25a996..a402f73 100644 --- a/main.js +++ b/main.js @@ -114,6 +114,10 @@ let CreateClient = (token) => { if (command === 'rearchive') { registrar.archive.runQuery(matrixClient, room, userInput, true, registrar); } + + if (command === 'nitter') { + registrar.nitter.runQuery(matrixClient, room, userInput, registrar); + } } }); diff --git a/package.json b/package.json index 8d3656a..fa6e754 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "dependencies": { "axios": "^0.19.2", "file-system": "^2.2.2", - "matrix-js-sdk": "^2.4.6" + "matrix-js-sdk": "^2.4.6", + "jsdom": "^16.2.2" }, "devDependencies": { "eslint": "^5.16.0", diff --git a/registrar.js b/registrar.js index 593b5d7..ac9fc6e 100644 --- a/registrar.js +++ b/registrar.js @@ -17,4 +17,5 @@ module.exports = { unpin: require('./commands/unpin.js'), mordy: require('./commands/mordy.js'), archive: require('./commands/archive.js'), + nitter: require('./commands/nitter.js'), };