206 lines
6.7 KiB
JavaScript
206 lines
6.7 KiB
JavaScript
|
|
let Mastodon = require('mastodon')
|
|
let Twit = require('twit')
|
|
|
|
// for downloading and saving media
|
|
const https = require('https')
|
|
const fs = require('fs')
|
|
const mime = require('mime-types')
|
|
|
|
// load the configuration
|
|
const config = require('./config.js')
|
|
let storage = require('./storage.js')
|
|
|
|
const TWITTER_MAX_CHARS = 280
|
|
const TWITTER_IMAGE_MAX_CHARS = 140 //?
|
|
|
|
function readBase64(filename) {
|
|
let buf = fs.readFileSync(filename)
|
|
let b64 = buf.toString('base64')
|
|
return b64
|
|
}
|
|
|
|
let bot = {
|
|
lastPostDate: new Date().getTime(),
|
|
|
|
// splits a tweet into pieces that are less than TWITTER_MAX_CHARS long
|
|
splitTweet: function (post, pieces = []) {
|
|
if (post.length > TWITTER_MAX_CHARS) {
|
|
let index = TWITTER_MAX_CHARS - 3 // 3 for appending ...
|
|
while (post.charAt(index) !== ' ' && index > 0) index--
|
|
if (index === 0) {
|
|
/*if a word if longer than TWITTER_MAX_CHARS-3 split the word*/
|
|
index = TWITTER_MAX_CHARS - 3
|
|
}
|
|
return this.splitTweet(post.substring(index+1), pieces.concat([post.substring(0, index) + '...']))
|
|
}
|
|
return pieces.concat([post])
|
|
},
|
|
|
|
splitForImage: function (post) {
|
|
if (post.length > TWITTER_IMAGE_MAX_CHARS) {
|
|
let index = TWITTER_IMAGE_MAX_CHARS - 3 // 3 for appending ...
|
|
while (post.charAt(index) !== ' ' && index > 0) index--
|
|
if (index === 0) {
|
|
/*if a word if longer than TWITTER_MAX_CHARS-3 split the word*/
|
|
index = TWITTER_IMAGE_MAX_CHARS - 3
|
|
}
|
|
return [post.substring(0, index) + '...', post.substring(index+1)]
|
|
}
|
|
return [post]
|
|
},
|
|
|
|
// post to twitter with media attachments
|
|
// Size restrictions for uploading via API
|
|
// Image 5MB
|
|
// GIF 15MB
|
|
// Video 15MB
|
|
// medias is an array of filenames
|
|
twitterPostWithMedias: function (post, medias) {
|
|
let that = this
|
|
let pieces = this.splitForImage(post)
|
|
|
|
let uploads = []
|
|
|
|
for (let media of medias) {
|
|
let b64content = readBase64(media)
|
|
uploads.push(
|
|
new Promise((resolve, reject) => {
|
|
that.twitter.post('media/upload', { media_data: b64content }, (err, data, response) => {
|
|
if (err) reject(err)
|
|
else resolve(data)
|
|
})
|
|
})
|
|
)
|
|
}
|
|
|
|
return Promise.all(uploads).then(datas => {
|
|
let mediaIds = datas.map(data => data.media_id_string)
|
|
return new Promise((resolve, reject) => {
|
|
that.twitter.post('statuses/update', { status: pieces[0], media_ids: mediaIds }, (err, data, response) => {
|
|
if (!err) {
|
|
resolve(data)
|
|
} else reject(err)
|
|
})
|
|
})
|
|
}).then((data) => {
|
|
if (pieces.length > 1) return that.twitterPost(pieces[1], data.id_str)
|
|
else return Promise.resolve()
|
|
}).then(() => {
|
|
console.log(`[INFO] Tweeted ${post} with ${medias}`)
|
|
})
|
|
},
|
|
|
|
promisePost: function(post, reply = null) {
|
|
let that = this
|
|
return new Promise((resolve, reject) => {
|
|
that.twitter.post('statuses/update', { status: post, in_reply_to_status_id: reply }, (err, data, response) => {
|
|
if (err) reject(err)
|
|
else resolve(data)
|
|
})
|
|
})
|
|
},
|
|
|
|
// post to twitter
|
|
// if post is longer than TWITTER_MAX_CHARS divides in multiple tweets
|
|
twitterPost: function (post, reply = null) {
|
|
let pieces = this.splitTweet(post)
|
|
|
|
let promise = this.promisePost(pieces[0], reply)
|
|
for (let i = 1; i < pieces.length; i++) {
|
|
promise = promise.then(data => this.promisePost(pieces[i], data.id_str))
|
|
}
|
|
|
|
return promise
|
|
.catch(err => console.error(err))
|
|
},
|
|
|
|
// downloads a file from the given url and saves it to temp dir
|
|
downloadFile: function(url, name, temp = '/tmp') {
|
|
return new Promise(resolve => {
|
|
https.get(url, response => {
|
|
let ext = mime.extension(response.headers['content-type'])
|
|
let filename = `${temp}/${name}.${ext}`
|
|
const file = fs.createWriteStream(filename)
|
|
const stream = response.pipe(file)
|
|
stream.on('finish', () => {
|
|
console.log(`[INFO] Downloaded ${url} to ${filename}`)
|
|
resolve(filename)
|
|
})
|
|
})
|
|
})
|
|
},
|
|
|
|
// makes a post formatted for twitter
|
|
// mastodon returns posts in HTML
|
|
twitterFormat: function(post) {
|
|
return post
|
|
.replace(/<br\/>/g, '\n')
|
|
.replace(/<br \/>/g, '\n')
|
|
.replace(/<\/p>/g, '\n')
|
|
.replace(/'/g, '\'')
|
|
.replace(/"/g, '"')
|
|
.replace(/<[^>]*>?/gm, '')
|
|
},
|
|
|
|
// poll on mastodon status updates
|
|
start: function() {
|
|
|
|
this.twitter = new Twit({
|
|
consumer_key: config.twitter_consumer_key,
|
|
consumer_secret: config.twitter_consumer_secret,
|
|
access_token: storage.data.token,
|
|
access_token_secret: storage.data.token_secret,
|
|
timeout_ms: 60*1000, // optional HTTP request timeout to apply to all requests
|
|
strictSSL: true, // optional - requires SSL certificates to be valid
|
|
})
|
|
|
|
this.mastodon = new Mastodon({
|
|
access_token: config.mastodon_token,
|
|
timeout_ms: 60*1000, // optional HTTP request timeout to apply to all requests.
|
|
api_url: `https://${config.instance}/api/v1/`, // optional, defaults to https://mastodon.social/api/v1/
|
|
})
|
|
|
|
setInterval(() => {
|
|
|
|
this.mastodon.get('timelines/home', {}).then(resp => {
|
|
let lastPostDateRecord = this.lastPostDate
|
|
|
|
resp.data
|
|
.filter(it => it.account.username === config.mastodon_user)
|
|
.filter(it => Date.parse(it.created_at) > this.lastPostDate)
|
|
.forEach((status) => {
|
|
let date = Date.parse(status.created_at)
|
|
if (date > lastPostDateRecord) lastPostDateRecord = date
|
|
|
|
if (status.media_attachments.length > 0) {
|
|
let downloads = status.media_attachments.map(media =>
|
|
this.downloadFile(media.url, media.id)
|
|
)
|
|
Promise.all(downloads)
|
|
.then(files => {
|
|
this.twitterPostWithMedias(`${this.twitterFormat(status.content)} \n\n ${status.uri}`, files)
|
|
.then(() => {
|
|
files.forEach(file => {
|
|
fs.unlink(file, (err) => {
|
|
if (err) throw err
|
|
console.log(`[INFO] Deleted ${file}`)
|
|
})
|
|
})
|
|
}).catch(e => { console.error(e) })
|
|
}).catch(err => { console.error(err) })
|
|
} else {
|
|
this.twitterPost(`${this.twitterFormat(status.content)} \n\n ${status.uri}`)
|
|
.then(() => console.log(`[INFO] Tweeted ${this.twitterFormat(status.content)} \n\n ${status.uri}`))
|
|
}
|
|
})
|
|
|
|
this.lastPostDate = lastPostDateRecord
|
|
|
|
})
|
|
|
|
}, 10000)
|
|
}
|
|
}
|
|
|
|
module.exports = bot
|