From 55a279b71dbd7feaab7b16929bfd75bfea0c1950 Mon Sep 17 00:00:00 2001 From: Malte Heinzelmann Date: Mon, 27 Jun 2016 00:44:49 +0200 Subject: [PATCH] Implemented Bing Image Search API Bot is now able to serve u with fresh images from Bing Image search --- app.js | 65 +++++++++- bing.js | 292 +++++++++++++++++++++++++++++++++++++++++++++ config.sample.json | 9 +- package.json | 6 +- 4 files changed, 364 insertions(+), 8 deletions(-) create mode 100644 bing.js diff --git a/app.js b/app.js index 60785e2..487efc1 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,60 @@ -var config = new (require('./config.js'))("config.sample.json"); -console.log(config.exists("token")); -console.log(config.get("token")); - +"use strict"; var config = require('./config.js')(); -console.log(config.exists("token")); -console.log(config.get("token")); +if (!config.exists("telegram", "token")) { + console.error("You need to specify a token for the Telegram API"); + process.exit(1); +} + +var request = require('request'); + +var TelegramBot = require('node-telegram-bot-api'); + +// Setup polling way +var bot = new TelegramBot(config.get("telegram", "token"), { polling: true }); + +function sendImage(query, msg, nsfw) { + var msgId = msg.message_id; + var chatId = msg.chat.id; + bing.getImageData(query, nsfw, function(result) { + switch (result.status) { + case 0: + var image = result["image"]; + var path = image["contentUrl"]; + var caption = image["name"]; + bot.sendPhoto(chatId, request(path), { + reply_to_message_id: msgId, + caption: caption + }); + break; + default: + var message = result.message || "Unknown error!"; + bot.sendMessage(chatId, message, { + reply_to_message_id: msgId + }); + break; + } + }); +} + +function onCommand(command, query, msg) { + switch(command) { + case "image": + sendImage(query, msg); + break; + case "image": + sendImage(query, msg, true); + break; + } +} + +// Matches /image [whatever] +bot.onText(/\/image (.+)/, function (msg, match) { + onCommand("image", match[1], msg); +}); + +// Matches /nsfw [whatever] +bot.onText(/\/nsfw (.+)/, function (msg, match) { + onCommand("nsfw", match[1], msg); +}); + +var bing = require('./bing.js')(config.get("bing", "keys")); diff --git a/bing.js b/bing.js new file mode 100644 index 0000000..2d0186b --- /dev/null +++ b/bing.js @@ -0,0 +1,292 @@ +"use strict"; +// bing.js + +var request = require('request'); + +var Bing = function(keys, config) { + var _keys, _config; + + var _errorCodes = { + "no_error": 0, + "": 1, + "": 2, + "": 3, + "": 4, + "": 5, + "": 6 + }; + + var _errorMessages = {}; + var _errors = []; + + var addError = function(name, message) { + if (_errors.length == 42) { + _errors.push({ + "name": "answer_to_the_ultimate_question_of_life_the_universe_and_everything", + "message": "You just found the Answer to the Ultimate Question of Life, The Universe, and Everything!" + }); + } + _errors.push({ + "name": name, + "message": message + }); + } + + addError("no_error", "There were no errors."); + addError("unknown_error", "I don't know why it didn't work. :("); + addError("connection_error", "The bot couldn't connect to the API server"); + addError("no_keys_usable", "Apparently you have keys with quota left."); + addError("invalid_key", "The key is invalid."); + addError("infinite_loop_detected", "Loop-Loop-Loop"); + addError("malformed_response", "The bot couldn't parse the API servers response."); + + var errorCode = function(name) { + for(var i = 0; i < _errors.length; i++) { + if (_errors[i]["name"] === name) { + return i; + } + } + // Don't have 42 error messages + return 42; + } + + var errorMessage = function(code) { + return _errors[code]["message"]; + } + + var clone = function(o) { + if (undefined == o || typeof o !== "object") { + return o; + } + var copy = o.constructor(); + for (var k in o) { + if (o.hasOwnProperty(k)) { + copy[k] = o[k]; + } + } + return copy; + }; + var extend = function(o1, o2) { + // extend object1 with object2 + var e = clone(o1); + for (var k in o2) { + if (o2.hasOwnProperty(k)) { + e[k] = o2[k]; + } + } + return e; + }; + + // inclusive low and exclusive high + var randomInt = function(low, high) { + return Math.floor(Math.random() * (high - low) + low); + }; + + var getAvailableApiKey = function() { + var result = undefined; + for (var i = 0; i < _keys.length; i++) { + if (_keys[i]["usable"] === true) { + result = { + "i": i, + "key": _keys[i]["key"] + }; + break; + } + } + return result; + } + + var getLeastUsedApiKey = function() { + var result = { + "i": -1, + "date": new Date().getTime() + }; + for (var i = 0; i < _keys.length; i++) { + if (_keys[i]["usable"] === true) { + result = { + "i": i, + "key": _keys[i]["key"] + }; + break; + } + } + return result; + } + + /* + Use a key, if it falis try the next key + */ + var useKey = function(onUse, keysUsed) { + var keysUsed = (keysUsed === undefined) ? -1 : keysUsed; + var onSuccess = function(cb, key, data) { + _keys[key["i"]]["usable"] = true; + _keys[key["i"]]["usedon"] = new Date().getTime(); + cb({ + "status": errorCode("no_error"), + "data": data + }); + }; + var onError = function(cb, key) { + _keys[key["i"]]["usable"] = false; + _keys[key["i"]]["usedon"] = new Date().getTime(); + var result = useKey(onUse, ++keysUsed); + if (result === false) { + return cb({ + "status": errorCode("no_keys_usable") + }); + } + return result; + }; + var key; + if (keysUsed === -1) { + key = getAvailableApiKey(); + } + if (key === undefined && keysUsed < _keys.length) { + key = { + "i": keysUsed, + "key": _keys[keysUsed]["key"] + }; + } + // try with another key + if (keysUsed < _keys.length) { + onUse(key, keysUsed, onSuccess, onError); + return true; + } + // we tried all keys... we dont have any quota left + return false; + + } + + var generateUrl = function(query, nsfw) { + nsfw = nsfw || false; + return 'https://api.cognitive.microsoft.com/bing/v5.0/images/search' + + '?q=' + encodeURIComponent(query) + + '&count=' + _config["count"] + + '&offset=0' + + '&mkt=' + _config["market"] + + '&safeSearch=' + (nsfw ? 'Off' : 'Moderate'); + } + + /* + status: + 0 - everything ok + 1 - no connection + 1 - no connection + 2 - no available keys + 3 - used key was invalid + */ + var queryApi = function(query, nsfw, cb) { + if (typeof nsfw === "function") { + cb = nsfw; + nsfw = undefined; + } + useKey(function(key, keysUsed, success, error) { + var options = { + url: generateUrl(query, nsfw), + headers: { + 'Ocp-Apim-Subscription-Key': key["key"] + } + }; + request.get(options, function (err, res, body) { + if (err != null) { + console.error(err); + cb({ + "status": errorCode("connection_error") + }); + } else { + switch (res.statusCode) { + case 200: + try { + var json = JSON.parse(body); + success(cb, key, json); + } catch(e) { + //Received data was malformed json! Unable to parse. + cb({ + "status": errorCode("malformed_response") + }); + } + break; + case 401: + // This is an invalid key! You should remove it. But we will try a different key + console.error("The key\n", key, "\n is invalid! You should remove it from the configuration file."); + error(cb, key); + break; + case 403: + // This key doesn't work. Try another one + error(cb, key); + break; + default: + // What happened? + cb({ + "status": errorCode("unknown_error"), + "data": body + }); + break; + } + } + }); + }); + } + + // Constructor + function Bing(keys, config) { + if (!(this instanceof Bing)) { + return new Bing(keys); + } + keys = keys || []; + _keys = []; + for (var i = 0; i < keys.length; i++) { + _keys.push({ + "key": keys[i], + "usable": true + }); + } + var defaults = { + "count": 1000, + "market": 'de-de' + } + _config = extend(defaults, config || {}); + + } + + Bing.prototype.test = function (cb) { + queryApi("test", function(result) { + if (!result) { + result = { + "status": errorCode("unknown_error") + }; + } + cb(result); + }); + }; + + Bing.prototype.getImageData = function(query, nsfw, cb) { + if (typeof nsfw === "function") { + cb = nsfw; + nsfw = undefined; + } + queryApi(query, nsfw, function(result) { + if (!result) { + result = { + "status": errorCode("unknown_error") + }; + } else if (result["status"] === errorCode("no_error")) { + var data = result["data"] + if (data && data["value"]) { + var value = data["value"]; + if (typeof value === "object" && Array.isArray(value)) { + var max = Math.min(_config["count"], value.length); + result["image"] = value[randomInt(0, max)]; + } + } + delete result["data"]; + } + result["message"] = errorMessage(result["status"]); + cb(result); + }); + }; + + return new Bing(keys, config); +} + +var exports = module.exports = Bing diff --git a/config.sample.json b/config.sample.json index 1482295..e752962 100644 --- a/config.sample.json +++ b/config.sample.json @@ -1,3 +1,10 @@ { - "token": "YOUR BOT TOKEN" + "telegram": { + "token": "YOUR BOT TOKEN" + }, + "bing": { + "keys": [ + "ONE OR MORE BING SEARCH API KEY(S)" + ] + } } diff --git a/package.json b/package.json index f233468..94f1674 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,9 @@ "bugs": { "url": "https://github.com/CaffeineProcessorsUnited/telegram-image-bot/issues" }, - "homepage": "https://github.com/CaffeineProcessorsUnited/telegram-image-bot#readme" + "homepage": "https://github.com/CaffeineProcessorsUnited/telegram-image-bot#readme", + "dependencies": { + "node-telegram-bot-api": "^0.23.3", + "request": "^2.72.0" + } }