ekardnam 4 years ago
parent
commit
2eeadd9f2a
10 changed files with 406 additions and 568 deletions
  1. 1 0
      .gitignore
  2. 26 0
      app.js
  3. 197 0
      bot.js
  4. 34 0
      cert.pem
  5. 54 0
      key.pem
  6. 0 189
      main.js
  7. 58 0
      oauth.js
  8. 0 376
      package-lock.json
  9. 16 3
      package.json
  10. 20 0
      storage.js

+ 1 - 0
.gitignore

@@ -1,2 +1,3 @@
 node_modules
 config.js
+storage.json

+ 26 - 0
app.js

@@ -0,0 +1,26 @@
+
+let oauth = require('./oauth.js')
+let storage = require('./storage.js')
+let bot = require('./bot.js')
+
+const crypto = require('crypto')
+
+
+// main
+let main = () => {
+  storage.init()
+
+  if (!storage.data.sess_secret) {
+    storage.data.sess_secret = crypto.randomBytes(128).toString('base64')
+    storage.save()
+  }
+
+  if (storage.data.token && storage.data.token_secret) {
+    console.log("[INFO] Starting bot")
+    bot.start()
+  } else {
+    oauth.start()
+  }
+}
+
+main()

+ 197 - 0
bot.js

@@ -0,0 +1,197 @@
+
+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 pieces = this.splitForImage(post)
+
+    let uploads = []
+
+    for (let media of medias) {
+      let b64content = readBase64(media)
+      uploads.push(
+        new Promise((resolve, reject) => {
+          this.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)
+      let altText =  'test'
+      mediaIds.forEach(mediaId => {
+        this.twitter.post('media/metadata/create', { media_id: mediaId, alt_text: { text: altText } }, (err, data, response) => {
+          if (!err) {
+            this.twitter.post('statuses/update', { status: pieces[0], media_ids: mediaIds }, (err, data, response) => {
+              if (!err) {
+                console.log(`[INFO] Tweeted ${post} with ${medias}`)
+                if (pieces.length > 1) this.twitterPost(pieces[1], data.id_str)
+              } else throw err
+            })
+          } else throw err
+        })
+      })
+    })
+  },
+
+  // post to twitter
+  // if post is longer than TWITTER_MAX_CHARS divides in multiple tweets
+  twitterPost: function (post, reply = null) {
+    let replyTo = reply
+    let success = true
+    this.splitTweet(post).forEach((piece) => {
+      this.twitter.post('statuses/update', { status: piece, in_reply_to_status_id: replyTo }, (err, data, response) => {
+        if (err) {
+          success = false
+          console.error(err)
+        } else {
+          replyTo = data.id_str
+        }
+      })
+    })
+
+    if (success) console.log(`[INFO] Tweeted ${post}`)
+  },
+
+  // 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) {
+    post = post.replace('<br/>', '\n') // new lines
+    post = post.replace('<p>', '')
+    post = post.replace('</p>', '\n')
+    return post
+  },
+
+  // 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), 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))
+            }
+          })
+
+          this.lastPostDate = lastPostDateRecord
+
+        })
+
+    }, 10000)
+  }
+}
+
+module.exports = bot

+ 34 - 0
cert.pem

@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+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
+Pvn5BPPFdg==
+-----END CERTIFICATE-----

+ 54 - 0
key.pem

@@ -0,0 +1,54 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+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
+eMuOya/vdpCeoLRwdxXAPg==
+-----END ENCRYPTED PRIVATE KEY-----

+ 0 - 189
main.js

@@ -1,189 +0,0 @@
-
-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')
-
-const TWITTER_MAX_CHARS = 280
-const TWITTER_IMAGE_MAX_CHARS = 140 //?
-
-let twitter = new Twit({
-  consumer_key:         config.twitter_consumer_key,
-  consumer_secret:      config.twitter_consumer_secret,
-  access_token:         config.twitter_access_token,
-  access_token_secret:  config.twitter_access_token_secret,
-  timeout_ms:           60*1000,  // optional HTTP request timeout to apply to all requests
-  strictSSL:            true,     // optional - requires SSL certificates to be valid
-})
-
-let 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/
-})
-
-let lastPostDate = new Date().getTime()
-
-// splits a tweet into pieces that are less than TWITTER_MAX_CHARS long
-function splitTweet(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 splitTweet(post.substring(index+1), pieces.concat([post.substring(0, index) + '...']))
-  }
-  return pieces.concat([post])
-}
-
-function splitForImage(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]
-}
-
-function readBase64(filename) {
-  let buf = fs.readFileSync(filename)
-  let b64 = buf.toString('base64')
-  return b64
-}
-
-// post to twitter with media attachments
-// Size restrictions for uploading via API
-//    Image 5MB
-//    GIF 15MB
-//    Video 15MB
-// medias is an array of filenames
-function twitterPostWithMedias(post, medias) {
-  let pieces = splitForImage(twitterFormat(post))
-
-  let uploads = []
-
-  for (let media of medias) {
-    let b64content = readBase64(media)
-    uploads.push(
-      new Promise((resolve, reject) => {
-        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)
-    let altText =  'test'
-    mediaIds.forEach(mediaId => {
-      twitter.post('media/metadata/create', { media_id: mediaId, alt_text: { text: altText } }, (err, data, response) => {
-        if (!err) {
-          twitter.post('statuses/update', { status: pieces[0], media_ids: mediaIds }, (err, data, response) => {
-            if (!err) {
-              console.log(`[INFO] Tweeted ${post} with ${medias}`)
-              if (pieces.length > 1) twitterPost(pieces[1], data.id_str)
-            } else throw err
-          })
-        } else throw err
-      })
-    })
-  })
-}
-
-// post to twitter
-// if post is longer than TWITTER_MAX_CHARS divides in multiple tweets
-function twitterPost(post, reply = null) {
-  let replyTo = reply
-  let success = true
-  splitTweet(post).forEach((piece) => {
-    twitter.post('statuses/update', { status: piece, in_reply_to_status_id: replyTo }, (err, data, response) => {
-      if (err) {
-        success = false
-        console.error(err)
-      } else {
-        replyTo = data.id_str
-      }
-    })
-  })
-
-  if (success) console.log(`[INFO] Tweeted ${post}`)
-}
-
-// downloads a file from the given url and saves it to temp dir
-function downloadFile(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
-function twitterFormat(post) {
-  post = post.replace('<br/>', '\n') // new lines
-  post = post.replace('<p>', '')
-  post = post.replace('</p>', '\n')
-  return post
-}
-
-// poll on mastodon status updates
-setInterval(() => {
-
-  mastodon.get('timelines/home', {}).then(resp => {
-    let lastPostDateRecord = lastPostDate
-
-    resp.data
-      .filter(it => it.account.username === config.mastodon_user)
-      .filter(it => Date.parse(it.created_at) > 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 =>
-              downloadFile(media.url, media.id)
-            )
-            Promise.all(downloads)
-            .then(files => {
-              twitterPostWithMedias(twitterFormat(status.content), 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 {
-          twitterPost(twitterFormat(status.content))
-        }
-      })
-
-      lastPostDate = lastPostDateRecord
-
-    })
-
-}, 10000)

+ 58 - 0
oauth.js

@@ -0,0 +1,58 @@
+
+const express = require('express')
+const fs = require('fs')
+const https = require('https')
+const passport = require('passport')
+const TwitterStrategy = require('passport-twitter').Strategy
+let config = require('./config.js')
+let storage = require('./storage.js')
+
+
+let oauth = {
+  start: function() {
+    let app = express()
+
+    app.use(require('express-session')({
+      secret: storage.data.sess_secret,
+      resave: true,
+      saveUninitialized: true
+    }));
+
+    app.use(passport.initialize())
+    app.use(passport.session({ secret: storage.data.sess_secret }))
+
+    passport.use(new TwitterStrategy({
+        consumerKey: config.twitter_consumer_key,
+        consumerSecret: config.twitter_consumer_secret,
+        callbackURL: `${config.callback_url}${config.callback_path}`
+      },
+      (token, tokenSecret, profile, cb) => {
+        storage.data.token = token
+        storage.data.token_secret = tokenSecret
+        storage.save()
+        console.log("[INFO] Got tokens, restart!")
+      }
+    ))
+
+    console.log('[INFO] Starting HTTPS webserver for OAuth')
+
+    app.get('/', passport.authenticate('twitter'))
+
+    app.get(config.callback_path, passport.authenticate('twitter', {  failureRedirect: '/failed' }), (req, res) => {
+      res.redirect("/success")
+    })
+
+    app.get('/failed', (req, res) => res.send('Authentication failed'))
+    app.get('/success', (req, res) => res.send('Authentication success. Restart XTweet when it stops'))
+
+    https.createServer({
+      key: fs.readFileSync(config.key),
+      cert: fs.readFileSync(config.cert),
+      passphrase: config.passphrase
+    }, app).listen(config.port, config.bind_ip)
+
+    console.log(`[INFO] Server listening on ${config.bind_ip}:${config.port}`)
+  }
+}
+
+module.exports = oauth;

+ 0 - 376
package-lock.json

@@ -1,376 +0,0 @@
-{
-  "name": "xtweet",
-  "version": "1.0.0",
-  "lockfileVersion": 1,
-  "requires": true,
-  "dependencies": {
-    "ajv": {
-      "version": "6.10.2",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
-      "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
-      "requires": {
-        "fast-deep-equal": "^2.0.1",
-        "fast-json-stable-stringify": "^2.0.0",
-        "json-schema-traverse": "^0.4.1",
-        "uri-js": "^4.2.2"
-      }
-    },
-    "asn1": {
-      "version": "0.2.4",
-      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
-      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
-      "requires": {
-        "safer-buffer": "~2.1.0"
-      }
-    },
-    "assert-plus": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
-    },
-    "asynckit": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
-    },
-    "aws-sign2": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
-      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
-    },
-    "aws4": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
-      "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
-    },
-    "bcrypt-pbkdf": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
-      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
-      "requires": {
-        "tweetnacl": "^0.14.3"
-      }
-    },
-    "bluebird": {
-      "version": "3.5.5",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
-      "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w=="
-    },
-    "caseless": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
-      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
-    },
-    "combined-stream": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
-      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "requires": {
-        "delayed-stream": "~1.0.0"
-      }
-    },
-    "core-util-is": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
-    },
-    "dashdash": {
-      "version": "1.14.1",
-      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
-      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
-      "requires": {
-        "assert-plus": "^1.0.0"
-      }
-    },
-    "delayed-stream": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
-    },
-    "ecc-jsbn": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
-      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
-      "requires": {
-        "jsbn": "~0.1.0",
-        "safer-buffer": "^2.1.0"
-      }
-    },
-    "extend": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
-      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
-    },
-    "extsprintf": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
-      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
-    },
-    "fast-deep-equal": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
-      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
-    },
-    "fast-json-stable-stringify": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
-      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
-    },
-    "forever-agent": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
-    },
-    "form-data": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
-      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
-      "requires": {
-        "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.6",
-        "mime-types": "^2.1.12"
-      }
-    },
-    "getpass": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
-      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
-      "requires": {
-        "assert-plus": "^1.0.0"
-      }
-    },
-    "har-schema": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
-      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
-    },
-    "har-validator": {
-      "version": "5.1.3",
-      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
-      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
-      "requires": {
-        "ajv": "^6.5.5",
-        "har-schema": "^2.0.0"
-      }
-    },
-    "http-signature": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
-      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
-      "requires": {
-        "assert-plus": "^1.0.0",
-        "jsprim": "^1.2.2",
-        "sshpk": "^1.7.0"
-      }
-    },
-    "is-typedarray": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
-    },
-    "isstream": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
-    },
-    "jsbn": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
-      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
-    },
-    "json-schema": {
-      "version": "0.2.3",
-      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
-      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
-    },
-    "json-schema-traverse": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
-    },
-    "json-stringify-safe": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
-    },
-    "jsprim": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
-      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
-      "requires": {
-        "assert-plus": "1.0.0",
-        "extsprintf": "1.3.0",
-        "json-schema": "0.2.3",
-        "verror": "1.10.0"
-      }
-    },
-    "mastodon": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/mastodon/-/mastodon-1.2.2.tgz",
-      "integrity": "sha512-ixcYkzn6SorH8U2jNc1vwiX89EiVMjzd2aDYFtr191YY9rdoVo+owI6cQo2EjUnzg2RN9WxyBJ9KDuw+R4lt+w==",
-      "requires": {
-        "bluebird": "^3.1.5",
-        "mime": "^1.3.4",
-        "request": "^2.68.0"
-      }
-    },
-    "mime": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
-      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
-    },
-    "mime-db": {
-      "version": "1.40.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
-      "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
-    },
-    "mime-types": {
-      "version": "2.1.24",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
-      "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
-      "requires": {
-        "mime-db": "1.40.0"
-      }
-    },
-    "oauth-sign": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
-      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
-    },
-    "performance-now": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
-      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
-    },
-    "psl": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz",
-      "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA=="
-    },
-    "punycode": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
-      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
-    },
-    "qs": {
-      "version": "6.5.2",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
-      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
-    },
-    "request": {
-      "version": "2.88.0",
-      "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
-      "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
-      "requires": {
-        "aws-sign2": "~0.7.0",
-        "aws4": "^1.8.0",
-        "caseless": "~0.12.0",
-        "combined-stream": "~1.0.6",
-        "extend": "~3.0.2",
-        "forever-agent": "~0.6.1",
-        "form-data": "~2.3.2",
-        "har-validator": "~5.1.0",
-        "http-signature": "~1.2.0",
-        "is-typedarray": "~1.0.0",
-        "isstream": "~0.1.2",
-        "json-stringify-safe": "~5.0.1",
-        "mime-types": "~2.1.19",
-        "oauth-sign": "~0.9.0",
-        "performance-now": "^2.1.0",
-        "qs": "~6.5.2",
-        "safe-buffer": "^5.1.2",
-        "tough-cookie": "~2.4.3",
-        "tunnel-agent": "^0.6.0",
-        "uuid": "^3.3.2"
-      }
-    },
-    "safe-buffer": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
-      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
-    },
-    "safer-buffer": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
-    },
-    "sshpk": {
-      "version": "1.16.1",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
-      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
-      "requires": {
-        "asn1": "~0.2.3",
-        "assert-plus": "^1.0.0",
-        "bcrypt-pbkdf": "^1.0.0",
-        "dashdash": "^1.12.0",
-        "ecc-jsbn": "~0.1.1",
-        "getpass": "^0.1.1",
-        "jsbn": "~0.1.0",
-        "safer-buffer": "^2.0.2",
-        "tweetnacl": "~0.14.0"
-      }
-    },
-    "tough-cookie": {
-      "version": "2.4.3",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
-      "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
-      "requires": {
-        "psl": "^1.1.24",
-        "punycode": "^1.4.1"
-      },
-      "dependencies": {
-        "punycode": {
-          "version": "1.4.1",
-          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
-          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
-        }
-      }
-    },
-    "tunnel-agent": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
-      "requires": {
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "tweetnacl": {
-      "version": "0.14.5",
-      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
-      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
-    },
-    "twit": {
-      "version": "2.2.11",
-      "resolved": "https://registry.npmjs.org/twit/-/twit-2.2.11.tgz",
-      "integrity": "sha512-BkdwvZGRVoUTcEBp0zuocuqfih4LB+kEFUWkWJOVBg6pAE9Ebv9vmsYTTrfXleZGf45Bj5H3A1/O9YhF2uSYNg==",
-      "requires": {
-        "bluebird": "^3.1.5",
-        "mime": "^1.3.4",
-        "request": "^2.68.0"
-      }
-    },
-    "uri-js": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
-      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
-      "requires": {
-        "punycode": "^2.1.0"
-      }
-    },
-    "uuid": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
-      "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
-    },
-    "verror": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
-      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
-      "requires": {
-        "assert-plus": "^1.0.0",
-        "core-util-is": "1.0.2",
-        "extsprintf": "^1.2.0"
-      }
-    }
-  }
-}

+ 16 - 3
package.json

@@ -3,14 +3,27 @@
   "version": "1.0.0",
   "description": "Cross tweet from Mastodon",
   "main": "main.js",
-  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+  "scripts": {},
+  "bin": {
+    "xtweet": "app.js"
   },
-  "author": "",
+  "repository": {
+    "type": "git",
+    "url": "https://git.lattuga.net/ekardnam/XTweet.git"
+  },
+  "bugs": {
+    "email": "ekardnam@autistici.org",
+    "url": "https://git.lattuga.net/ekardnam/XTweet/issues"
+  },
+  "author": "ekardnam <ekardnam@autistici.org>",
   "license": "AGPL-3.0-or-later",
   "dependencies": {
+    "express": "^4.17.1",
+    "express-session": "^1.16.2",
     "mastodon": "^1.2.2",
     "mime-types": "^2.1.24",
+    "passport": "^0.4.0",
+    "passport-twitter": "^1.0.4",
     "twit": "^2.2.11"
   }
 }

+ 20 - 0
storage.js

@@ -0,0 +1,20 @@
+
+const fs = require('fs')
+
+const STORAGE_FILE = 'storage.json'
+
+let storage = {
+  init: function() {
+    if (!fs.existsSync(STORAGE_FILE)) {
+      fs.writeFileSync(STORAGE_FILE, '{}')
+    }
+    let raw = fs.readFileSync(STORAGE_FILE)
+    this.data = JSON.parse(raw)
+  },
+
+  save: function() {
+    fs.writeFileSync(STORAGE_FILE, JSON.stringify(this.data))
+  }
+}
+
+module.exports = storage;