Skip to content

Commit

Permalink
Add web interface for downloading
Browse files Browse the repository at this point in the history
A web interface allows this application to be useful to
a much larger userbase. This commit also dockerises the
codebase to try and replicate how it may be deployed on
a server.
  • Loading branch information
benkaiser committed Nov 22, 2016
1 parent d70a288 commit bf51877
Show file tree
Hide file tree
Showing 16 changed files with 2,266 additions and 102 deletions.
6 changes: 6 additions & 0 deletions Dockerfile.local
@@ -0,0 +1,6 @@
FROM node:6-slim
RUN npm install -g yarn
RUN mkdir /app
ADD package.json yarn.lock /app/
WORKDIR /app
RUN yarn --ignore-engines
3 changes: 3 additions & 0 deletions bin/build
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

docker-compose build
3 changes: 3 additions & 0 deletions bin/go
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

docker-compose up -d
3 changes: 3 additions & 0 deletions bin/restart
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

docker-compose down && bin/build && bin/go
3 changes: 3 additions & 0 deletions bin/run
@@ -0,0 +1,3 @@
#!/bin/sh

docker-compose run --rm web /bin/bash -c "$@"
3 changes: 3 additions & 0 deletions bin/test
@@ -0,0 +1,3 @@
#!/bin/sh

bin/run 'npm test'
23 changes: 23 additions & 0 deletions controllers/index.js
@@ -0,0 +1,23 @@
var express = require('express');
var router = express.Router();

var Facebook = require('../services/facebook');

router.get('/', function (req, res) {
res.render('index');
});

router.post('/', function(req, res) {
let email = req.body.email;
let password = req.body.password;
Facebook.login(email, password)
.then((fb) => {
fb.getConversations((conversations) => {
console.log(conversations);
res.render('conversations', { conversations: conversations });
});
})
.catch((err) => console.log('Failed to login:' + err));
});

module.exports = router;
12 changes: 12 additions & 0 deletions docker-compose.yml
@@ -0,0 +1,12 @@
web:
build: .
command: npm run nodemon
dockerfile: Dockerfile.local
environment:
PORT: 3000
APP_URL: 'http://localhost:3000'
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
62 changes: 12 additions & 50 deletions index.js
@@ -1,51 +1,13 @@
let login = require('facebook-chat-api');
let util = require('util');
let fs = require('fs');
let async = require('async');

let email = process.env.FB_EMAIL;
let password = process.env.FB_PASSWORD;
let threadId = process.env.THREAD_ID;

// Create simple echo bot
login({ email: email, password: password }, (err, api) => {
if (err) return console.error(err);

if (!threadId) {
api.getThreadList(0, 50, 'inbox', (err, arr) => {
arr.forEach((item) => {
api.getThreadInfo(item.threadID, (err, info) => {
console.log({
threadId: item.threadID,
name: info.name,
numberOfMessage: info.messageCount,
});
});
});
});
} else {
api.getThreadInfo(threadId, (err, info) => {
console.log(`Thread with: ${info.name}`);
console.log(`Starting download of ${info.messageCount} messages...`);

let iteration = 0;
let chunkSize = 9999;
let lastTimestamp = +Date.now();
let allMessages = [];
async.until(() => (iteration * chunkSize) > info.messageCount, (callback) => {
api.getThreadHistory(threadId, 0, chunkSize, lastTimestamp, (err, history) => {
if (err) { console.log(err); }

allMessages = history.concat(allMessages);
lastTimestamp = history[0].timestamp - 1;
iteration++;
console.log(`Downloaded ${allMessages.length} messages...`);
callback();
});
}, () => {
fs.writeFileSync('./data.json', JSON.stringify(allMessages, null, 2), 'utf-8');
console.log('Download completed. See `data.json` file.');
});
});
}
var bodyParser = require('body-parser');
var express = require('express');
var app = express();

app.set('view engine', 'pug');
app.set('views', __dirname + '/views');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(require('./controllers'));

app.listen(process.env.PORT || 3000, function() {
console.log('Ready to rock!');
});
32 changes: 25 additions & 7 deletions package.json
Expand Up @@ -3,20 +3,38 @@
"version": "0.1.0",
"description": "Utility to download facebook messages",
"main": "index.js",
"scripts": {
"start": "node --harmony index.js"
},
"repository": {
"url": "https://github.com/benkaiser/facebook-chat-downloader",
"type": "git"
},
"jshintConfig": {
"esversion": 6
},
"author": "Benjamin Kaiser",
"license": "MIT",
"dependencies": {
"async": "^2.1.2",
"facebook-chat-api": "^1.2.0"
"body-parser": "^1.15.2",
"express": "^4.14.0",
"facebook-chat-api": "^1.2.0",
"geohash-coordinates": "^1.1.6",
"geolib": "^2.0.21",
"moment": "^2.15.2",
"mongoose": "^4.6.5",
"nodemailer": "^2.6.4",
"pug": "^2.0.0-beta6"
},
"scripts": {
"nodemon": "./node_modules/nodemon/bin/nodemon.js -x 'node --harmony' index.js",
"start": "node --harmony index.js"
},
"devDependencies": {
"nodemon": "^1.11.0"
},
"jshintConfig": {
"esversion": 6
},
"engines": {
"node": "6"
},
"jscsConfig": {
"requireTrailingComma": false
}
}
36 changes: 36 additions & 0 deletions services/facebook.js
@@ -0,0 +1,36 @@
let async = require('async');
let facebookChatApi = require('facebook-chat-api');
let fs = require('fs');
let util = require('util');

let accounts = [];
class Facebook {
static login(email, password) {
return new Promise((resolve, reject) => {
facebookChatApi({email: email, password: password }, (err, api) => {
if (err) reject(err);
resolve(new Facebook(api));
});
});
}

constructor(api) {
this.api = api;
}

getConversations(callback) {
this.api.getThreadList(0, 10, 'inbox', (err, rawConversations) => {
async.map(rawConversations, (raw, done) => {
this.api.getThreadInfo(raw.threadID, (err, conversation) => {
raw.full = conversation;
done(null, raw);
});
}, (err, conversations) => {
if (err) console.log(err);
callback(conversations);
});
});
}
}

module.exports = Facebook;
11 changes: 11 additions & 0 deletions views/conversations.pug
@@ -0,0 +1,11 @@
extends layout.pug

block content
h1 Conversation List
p.lead
| Your conversations are displayed below
ul
each conversation in conversations
li
img(src='http://graph.facebook.com/' + conversation.participants[0] + '/picture?type=square')
<b>#{conversation.full.name}</b> #{conversation.snippet}
7 changes: 7 additions & 0 deletions views/github_banner.pug
@@ -0,0 +1,7 @@
a.github-corner(href='https://github.com/benkaiser/facebook-chat-downloader', aria-label='View source on Github')
svg(width='80', height='80', viewbox='0 0 250 250', style='fill:#70B7FD; color:#fff; position: absolute; top: 0; border: 0; right: 0;', aria-hidden='true')
path(d='M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z')
path.octo-arm(d='M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2', fill='currentColor', style='transform-origin: 130px 106px;')
path.octo-body(d='M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z', fill='currentColor')
style.
.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
22 changes: 22 additions & 0 deletions views/index.pug
@@ -0,0 +1,22 @@
extends layout.pug

block content
h1 Facebook Chat Downloader
p.lead
| This is a service to allow you to download your facebook chat history
| in various different formats.
br
| This application does not store your data or send it to third parties. You
| can view the source code
a(href='https://github.com/benkaiser/facebook-chat-downloader') on github
| .
if alert
.alert.alert-success(role="alert") #{alert}
form(action='/', method='POST')
.form-group
label(for='email') Facebook Email address
input#email.form-control(type='email', name='email', placeholder='Email', required)
.form-group
label(for='password') Facebook Password
input#password.form-control(type='password', name='password', required)
button.btn.btn-primary(type='submit') Submit
25 changes: 25 additions & 0 deletions views/layout.pug
@@ -0,0 +1,25 @@
doctype html
html(lang='en')
head
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(name='viewport', content='width=device-width, initial-scale=1')
meta(name='description', content='Simple service to download facebook conversations')
meta(name='author', content='Benjamin Kaiser')
title Goehash Notifier
block stylesheets
link(href='//cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/lumen/bootstrap.min.css', rel='stylesheet')
style.
body, html {
margin: 0;
padding: 0;
height: 100%;
}
.container {
margin-bottom: 20px;
}
body
.container
block content
include github_banner
block scripts

0 comments on commit bf51877

Please sign in to comment.