diff --git a/bin/www.js b/bin/www.js index 031ef5de..ee6c4c43 100644 --- a/bin/www.js +++ b/bin/www.js @@ -5,8 +5,10 @@ var config = require("../config"); var db = require("../core/db"); var server = require("../server"); +var instagram = require("../instagram"); db.init(config.db.uri) + .then(instagram) .then(function () { server(config.port); }); \ No newline at end of file diff --git a/client/instagram.js b/client/instagram.js new file mode 100644 index 00000000..1c53256f --- /dev/null +++ b/client/instagram.js @@ -0,0 +1,28 @@ +/** + * Created by tmshv on 06/11/14. + */ + +angular.module("hudozhka.instagram", []) + .controller("InstagramController", function($scope, $http, $window){ + //$scope.auth = function() { + // $http.get("/instagram/auth/url") + // .success(function (data) { + // $window.location.href = data.url; + // }); + //}; + + $http.get("/instagram/user") + .error(function() { + + }) + .success(function (user) { + $scope.user = user; + }); + + $scope.invite = function() { + $http.get("/instagram/invite") + .success(function (data) { + $scope.inviteCode = data.code; + }); + }; + }); \ No newline at end of file diff --git a/config.js b/config.js index dfc282c8..23102de5 100644 --- a/config.js +++ b/config.js @@ -23,7 +23,7 @@ var config = { uri: "mongodb://localhost:27017/hudozka" }, - tags: ["shlb_hudozka"] + tag: "shlb_hudozka" }; module.exports = Object.keys(privateData) diff --git a/core/instagram.js b/core/instagram.js deleted file mode 100644 index 25b67d1c..00000000 --- a/core/instagram.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Created by tmshv on 22/11/14. - */ - -var config = require("../config"); -var instagram = require("instagram-node"); -var auth_clients = {}; - -module.exports = { - client: function(code){ - if(code && code in auth_clients) { - return auth_clients[code].client; - }else{ - return createClient(); - } - }, - - user: function(code){ - if(code in auth_clients) { - return auth_clients[code].user; - }else{ - return null; - } - }, - - authorize: function(code){ - return new Promise(function(resolve, reject){ - var api = createClient(); - api.authorize_user(code, config.instagram.redirect_uri, function (err, result) { - if (err) return reject(err); - else { - var user = result.user; - var token = result.access_token; - var client = createClient(token); - - auth_clients[code] = { - client: client, - user: user - }; - - resolve(client); - } - }); - }); - } -}; - -function createClient(token) { - var client = instagram.instagram({}, {}); - if (token) { - client.use({access_token: token}); - } else { - client.use({ - client_id: config.instagram.client_id, - client_secret: config.instagram.client_secret - }); - } - return client; -} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index cce1da31..88b5a916 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -50,6 +50,13 @@ gulp.task("compile", function(){ })) .pipe(uglify()) .pipe(gulp.dest("public")); + + gulp.src("client/instagram.js") + .pipe(browserify({ + transform: [annotate] + })) + .pipe(uglify()) + .pipe(gulp.dest("public")); }); gulp.task("imagemin", function(){ diff --git a/instagram/index.js b/instagram/index.js new file mode 100644 index 00000000..9f7a28e2 --- /dev/null +++ b/instagram/index.js @@ -0,0 +1,39 @@ +/** + * Created by tmshv on 12/03/15. + */ + +var db = require("../core/db"); +var co = require("co"); +var instagram = require("./instagram"); +var publish = require("./publish"); +var config = require("../config"); + +module.exports = function () { + return loadUsers() + .then(publish) + .then(updateSubscription) +}; + +function loadUsers() { + return new Promise(function (resolve, reject) { + db.c("users").find({provider: "instagram"}) + .toArray() + .then(function (users) { + users.forEach(function (u) { + return instagram.initUser(u); + }); + return users; + }) + .then(resolve); + }); +} + +function updateSubscription() { + var tags = [config["tag"]]; + return tags.map(function (tag) { + var cb = config.instagram["tag_callback"]; + return co(function *() { + return yield instagram.api.subscribe(tag, cb); + }) + }); +} \ No newline at end of file diff --git a/instagram/instagram.js b/instagram/instagram.js new file mode 100644 index 00000000..fc1fe36d --- /dev/null +++ b/instagram/instagram.js @@ -0,0 +1,172 @@ +/** + * Created by tmshv on 22/11/14. + */ + +var EventEmitter = require("events").EventEmitter; +var thunkify = require("thunkify"); +var config = require("../config"); +var db = require("../core/db"); +var instagram = require("instagram-node"); + +function Instagram(options) { + this.client_id = options.client_id; + this.client_secret = options.client_secret; + this.redirect_uri = options.redirect_uri; + this.default_user = options.default_user; + this.clients = {}; + this.users = {}; + + EventEmitter.call(this); +} + +Instagram.prototype = Object.create(EventEmitter.prototype); + +Instagram.prototype.authorizationURL = function (scope, state) { + var params = {}; + params.scope = scope || ["basic"]; + if (state) { + params.state = state; + } + + var client = createClient(); + return client["get_authorization_url"](this.redirect_uri, params); +}; + +Instagram.prototype.addClient = function *(code, params) { + var token = params.access_token; + var item = params.user; + item.code = code; + item.access_token = token; + item.provider = "instagram"; + yield db.c("users").update({provider: "instagram", id: params.user.id}, item, {upsert: true}); + + return this.initUser(item); +}; + +Instagram.prototype.initUser = function (user) { + var token = user.access_token; + this.clients[user.code] = createClient(token); + this.users[user.code] = user; + return this; +}; + +Instagram.prototype.getUsers = function () { + var users = this.users; + return Object.keys(users) + .map(function (code) { + return users[code]; + }); +}; + +Instagram.prototype.update = function (body) { + this.emit("update", body); +}; + +Instagram.prototype.client = function (code) { + if (code && code in this.clients) { + return this.clients[code]; + } else { + return createClient(); + } +}; + +Instagram.prototype.user = function (code) { + if (code && code in this.users) { + return this.users[code]; + } else { + return null; + } +}; + +Instagram.prototype.authorize = function *(code) { + var api = createClient(); + var authorize = thunkify(api["authorize_user"]); + var result = yield authorize(code, this.redirect_uri); + + if (result) { + yield this.addClient(code, result); + } + + return result; +}; + +Instagram.prototype.getUser = function *() { + if (this.default_user) { + var du = yield db.c("users").findOne({username: this.default_user}); + if (du) { + return du; + } + } + + return yield db.c("users").findOne({provider: "instagram"}); +}; + +Instagram.prototype.getUsers = function *() { + return yield db.c("users").find({provider: "instagram"}).toArray(); +}; + +Instagram.prototype.api = { + subscriptions: function *() { + var ig = createClient(); + var subs = thunkify(ig["subscriptions"]); + try { + var s = yield subs(); + return s[0]; + } catch (error) { + return null; + } + }, + + subscribe: function *(tag, callbackURL) { + var ig = createClient(); + var sub = thunkify(ig["add_tag_subscription"]); + try { + var s = yield sub(tag, callbackURL); + return s[0]; + } catch (error) { + return null; + } + }, + + unsubscribe: function *(id) { + var ig = createClient(); + var sub = thunkify(ig["del_subscription"]); + try { + var s = yield sub({id: id}); + return s[0]; + } catch (error) { + return null; + } + }, + + tagMediaRecent: function *(client, tag, count) { + var tag_media_recent = thunkify(client["tag_media_recent"]); + + try{ + var result = yield tag_media_recent(tag, {count: count}); + return { + medias: result[0], + pagination: result[1], + remaining: result[2], + limit: result[3] + } + }catch(error){ + return null; + } + } +}; + +module.exports = new Instagram(config.instagram); + +function createClient(token) { + var client = instagram.instagram({}, {}); + if (token) { + client.use({access_token: token}); + } else { + client.use({ + client_id: config.instagram.client_id, + client_secret: config.instagram.client_secret + }); + } + return client; +} \ No newline at end of file diff --git a/instagram/publish.js b/instagram/publish.js new file mode 100644 index 00000000..f51faed5 --- /dev/null +++ b/instagram/publish.js @@ -0,0 +1,70 @@ +/** + * Created by tmshv on 10/03/15. + */ + +var co = require("co"); +var instagram = require("./instagram"); +var config = require("../config"); +var db = require("../core/db"); + + +module.exports = function () { + return co(function *() { + instagram.on("update", function (data) { + var tag = config["tag"]; + + co(function *() { + var user = yield instagram.getUser(); + if (user) { + var client = instagram.client(user.code); + var permitted_users = yield instagram.getUsers(); + permitted_users = permitted_users.map(function (user) { + return user["username"]; + }); + + var medias = yield fbt(client, tag); + medias = medias.filter(function (gram) { + return permitted_users.indexOf(gram.data.author) > -1; + }); + + return medias; + } + }) + .then(function (posts) { + return posts.map(function (g) { + console.log(g); + return db.c("posts").update({"data.id": g.data.id}, g, {upsert: true}) + .catch(function (error) { + console.log(error); + }); + }); + }) + .catch(function (error) { + console.log(error); + }); + }); + }); +}; + +function *fbt(client, tag) { + var param = yield instagram.api.tagMediaRecent(client, tag, 10); + return param.medias.map(toInstagram); +} + +function toInstagram(gram) { + var post = {}; + + post.body = gram.caption.text; + post.type = "instagram"; + post.tags = gram.tags; + post.date = new Date(parseInt(gram["created_time"]) * 1000); + + post.data = { + id: gram.id, + author: gram.user["username"], + image: gram.images, + url: gram.link + }; + + return post; +} diff --git a/instagram/router.js b/instagram/router.js new file mode 100644 index 00000000..511040b4 --- /dev/null +++ b/instagram/router.js @@ -0,0 +1,162 @@ +/** + * Created by tmshv on 22/11/14. + */ + +var path = require("path"); +var route = require("koa-route"); +var router = require("../routes"); +var instagram = require("../instagram/instagram"); + +module.exports = function (app) { + app.use(function *(next) { + var code = this.cookies.get("instagram_code"); + if (code) { + var client = instagram.client(code); + var user = instagram.user(code); + if (client && user) { + this.instagram = { + client: client, + user: user + } + } + } + + yield* next; + }); + + app.use( + route.get("/instagram", function *() { + var invite = this.cookies.get("invite_code") || this.query.code; + + if(!this.instagram) { + if(invite == undefined){ + this.body = "invite required"; + this.status = 401; + }else if(invite == "LOL"){ + this.cookies.set("invite_code", invite, {expires: expire()}); + + //todo: invalidate invite + + yield router.index( + path.join(__dirname, "../templates/instagram.html") + ); + }else{ + this.body = "invite ist correct"; + this.status = 401; + } + }else{ + yield router.index( + path.join(__dirname, "../templates/instagram.html") + ); + } + }) + ); + + app.use( + route.get("/instagram/auth", function *() { + var invite = this.cookies.get("invite_code"); + if(invite) { + var url = instagram.authorizationURL(null, "/instagram"); + this.redirect(url); + }else{ + this.status = 401; + this.body = "invite required"; + } + }) + ); + + app.use( + route.get("/instagram/user", function *() { + var code = this.cookies.get("instagram_code"); + var user = instagram.user(code); + + if(user) { + this.body = user; + }else{ + this.status = 404; + } + }) + ); + + app.use( + route.get("/instagram/auth/callback", function *() { + var state = this.query.state || "/"; + var code = this.query.code; + var client = yield instagram.authorize(code); + + if (client) { + this.cookies.set("instagram_code", code, {expires: expire()}); + this.redirect(state); + } else { + this.status = 401; + this.redirect(state); + } + }) + ); + + app.use(route.get("/instagram/subscription/list", function *() { + var result = yield instagram.api.subscriptions(); + if(result) { + this.body = result; + }else{ + this.status = 500; + } + })); + + app.use(route.get("/instagram/subscription/add", function *() { + var tag = this.query.tag; + var cu = this.query.url; + + var result = yield instagram.api.subscribe(tag, cu); + if(result) { + this.body = result; + }else{ + this.status = 500; + } + })); + + app.use(route.get("/instagram/subscription/delete", function *() { + var id = this.query.id; + + var result = yield instagram.api.unsubscribe(id); + if(result) { + this.body = result; + }else{ + this.status = 500; + } + })); + + app.use(route.get("/instagram/callback/:rnd", function *() { + if(this.query["hub.mode"] == "subscribe"){ + this.body = this.query["hub.challenge"]; + }else{ + this.status = 400; + } + })); + + app.use(route.post("/instagram/callback/:rnd", function *(rnd) { + var sign = this.header["x-hub-signature"]; + //console.log(this.headers); + + var body = this.request.body; + + //var hmac = crypto.createHmac("sha1", instagram.client_secret); + //hmac.update(body); + //var shasum = hmac.digest("hex"); + + + //console.log("X-Hub-Signature", sign); + //console.log("Hmac", shasum); + //console.log(); + + //if(shasum.digest("hex") == sign) { + instagram.update(body); + //} + + this.body = ""; + })); +}; + +function expire(time){ + return time || new Date(Date.now() + 1000 * 60 * 60 * 24 * 7); +} \ No newline at end of file diff --git a/package.json b/package.json index 9920bb64..395924c6 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,10 @@ "author": "Roman Timashev", "license": "ISC", "dependencies": { + "co": "^4.4.0", "instagram-node": "^0.5.1", "koa": "^0.18.0", - "koa-bodyparser": "^1.4.0", + "koa-bodyparser": "^1.4.1", "koa-conditional-get": "^1.0.2", "koa-etag": "^2.0.0", "koa-helmet": "0.0.3", diff --git a/routes/instagram.js b/routes/instagram.js deleted file mode 100644 index 3914225f..00000000 --- a/routes/instagram.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Created by tmshv on 22/11/14. - */ - -var Router = require("express").Router; -var instagram = require("../core/instagram"); - -module.exports = function (app) { - var router; - router = new Router(); - router.route("/auth/instagram/callback") - .get(function (req, res) { - var code = req.query.code; - instagram.authorize(code) - .catch(function(error){ - console.log(error.body); - res.status(500).send(''); - }) - .then(function (client) { - res.cookie("instagram_code", code); - res.redirect("/"); - }); - }); - app.use(router); - - //http://your-callback.com/url/?hub.mode=subscribe&hub.challenge=15f7d1a91c1f40f8a748fd134752feb3&hub.verify_token=myVerifyToken - router = new Router(); - router.route("/instagram/tag/:tag") - .get(function (req, res) { - var hub_challenge = req.query["hub.challenge"]; - //var hub_verify_token = req.query["hub.verify_token"]; - - console.log("real time get", req.param("tag"), hub_challenge); - res.send(hub_challenge); - }) - .post(function (req, res) { - console.log("real time tag", req.param("tag")); - console.log(req.body); - res.send(""); - }); - app.use(router); -}; \ No newline at end of file diff --git a/server.js b/server.js index 25154cbe..e6ca5d1d 100644 --- a/server.js +++ b/server.js @@ -8,6 +8,7 @@ var conditional = require("koa-conditional-get"); var etag = require("koa-etag"); var prerender = require("koa-prerender"); var helmet = require('koa-helmet'); +var bodyParser = require("koa-bodyparser"); var config = require("./config"); var routes = require("./routes"); @@ -15,6 +16,7 @@ var routes = require("./routes"); var app = koa(); app.proxy = true; +app.use(bodyParser()); app.use(logger()); app.use(conditional()); app.use(etag()); @@ -55,6 +57,8 @@ require("./routes/news")(app); require("./routes/404")(app); +require("./instagram/router")(app); + if (process.env["NODE_ENV"] === "development") { app.use(route.get("/debug", function *() { var out = {}; diff --git a/templates/instagram.html b/templates/instagram.html new file mode 100644 index 00000000..dd978afa --- /dev/null +++ b/templates/instagram.html @@ -0,0 +1,24 @@ + + +
+