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'), };