From ea622733f6b748560f99219b11b305ae129e389b Mon Sep 17 00:00:00 2001 From: Akamaru Date: Thu, 12 Nov 2015 17:42:03 +0100 Subject: [PATCH] Erster privater commit --- .gitignore | 47 +---- bot/bot.lua | 188 ++++++++--------- bot/utils.lua | 267 +++++++++++++++--------- pictures/inlove.gif | Bin 0 -> 62047 bytes plugins/9gag.lua | 24 +-- plugins/afk.lua | 113 ++++++++-- plugins/aktien.lua | 54 +++++ plugins/alive.lua | 4 +- plugins/app_store.lua | 122 +++++++++++ plugins/birthday_get.lua | 51 +++++ plugins/birthday_set.lua | 47 +++++ plugins/bitly.lua | 34 +++ plugins/bitly_create.lua | 43 ++++ plugins/{pluginsold => }/btc.lua | 23 +- plugins/change_pic.lua | 3 +- plugins/channels.lua | 87 ++++---- plugins/cleverbot.lua | 27 +++ plugins/create_sticker.lua | 57 +++++ plugins/credentials_manager.lua | 118 +++++++++++ plugins/curl_head.lua | 10 +- plugins/currency.lua | 64 ++++++ plugins/dailymotion.lua | 37 ++++ plugins/danbooru2.lua | 41 ++++ plugins/derpibooru.lua | 45 ++++ plugins/derpibooru_nsfw.lua | 45 ++++ plugins/deviantart.lua | 48 +++++ plugins/dns.lua | 40 ++++ plugins/dogify.lua | 2 +- plugins/dropbox.lua | 41 ++++ plugins/e621.lua | 36 ++++ plugins/facebook.lua | 40 ++++ plugins/facebook_photo.lua | 46 ++++ plugins/facebook_video.lua | 39 ++++ plugins/flickr.lua | 50 +++++ plugins/flickr_search.lua | 38 ++++ plugins/forecast.lua | 117 +++++++++++ plugins/ftp.lua | 6 +- plugins/gamesdb.lua | 139 ++++++++++++ plugins/gay.lua | 41 ++-- plugins/gdrive.lua | 82 ++++++++ plugins/gender.lua | 46 ++++ plugins/get.lua | 64 +++--- plugins/get_data.lua | 21 ++ plugins/get_txt.lua | 13 ++ plugins/gfycat.lua | 46 ++++ plugins/github.lua | 76 +++++++ plugins/googl.lua | 38 ++++ plugins/google.lua | 49 +++-- plugins/google_books.lua | 70 +++++++ plugins/gronkh_soundboard.lua | 33 +++ plugins/help.lua | 2 +- plugins/images.lua | 2 +- plugins/imdb.lua | 30 ++- plugins/img_google.lua | 152 ++++++++++++-- plugins/img_google_nsfw.lua | 139 ++++++++++-- plugins/instagram.lua | 72 +++++++ plugins/ip_info.lua | 63 ++++++ plugins/isup.lua | 184 ++++++++++++---- plugins/konachan.lua | 32 +++ plugins/konachan_nsfw.lua | 32 +++ plugins/location_manager.lua | 63 ++++++ plugins/love_calculator.lua | 37 ++++ plugins/manager.lua | 64 +++++- plugins/media_download.lua | 3 +- plugins/miiverse.lua | 89 ++++++++ plugins/minecraft.lua | 32 +-- plugins/music163.lua | 53 +++++ plugins/myanimelist.lua | 139 +++++++++--- plugins/page2image.lua | 61 ++++++ plugins/pagespeed_insights.lua | 29 +++ plugins/play_store.lua | 55 +++++ plugins/plugins.lua | 133 +++++++----- plugins/{ => pluginsold}/boersedown.lua | 0 plugins/pluginsold/get.lua | 49 +++++ plugins/pluginsold/myanimelist.lua | 40 ++++ plugins/pluginsold/quotes.lua | 65 ++++++ plugins/pluginsold/set.lua | 30 +++ plugins/ponyfaces.lua | 52 +++++ plugins/ponywave_img.lua | 31 +++ plugins/porndoge.lua | 15 ++ plugins/pornhub_gif.lua | 19 ++ plugins/pr0gramm.lua | 43 ++++ plugins/quotes.lua | 128 ++++++------ plugins/rechner.lua | 18 +- plugins/reddit.lua | 42 ++++ plugins/reload.lua | 3 +- plugins/rss.lua | 39 ++-- plugins/rss_old.lua | 84 ++++++++ plugins/safebrowsing.lua | 36 ++++ plugins/set.lua | 64 ++++-- plugins/soundboard.lua | 31 +++ plugins/soundcloud.lua | 53 +++++ plugins/special.lua | 81 ++++--- plugins/speedtest.lua | 20 ++ plugins/speedtest_net.lua | 13 ++ plugins/spotify.lua | 58 +++++ plugins/spotify_search.lua | 70 +++++++ plugins/stickerpacks.lua | 19 +- plugins/text_get.lua | 46 ++++ plugins/text_set.lua | 45 ++++ plugins/tts.lua | 4 +- plugins/tweet.lua | 10 +- plugins/twitch.lua | 45 ++++ plugins/twitter.lua | 85 ++++++-- plugins/url_title.lua | 29 ++- plugins/version.lua | 7 +- plugins/videotogif.lua | 43 ++++ plugins/vimeo.lua | 46 ++++ plugins/vine.lua | 43 ++++ plugins/weather.lua | 66 +++--- plugins/wiki.lua | 2 +- plugins/yandere.lua | 36 ++++ plugins/yourls.lua | 49 +++++ plugins/youtube.lua | 70 ++++++- plugins/youtube_channel.lua | 51 +++++ plugins/youtube_dl.lua | 27 ++- plugins/youtube_playlist.lua | 48 +++++ plugins/youtube_search.lua | 16 +- 118 files changed, 5289 insertions(+), 820 deletions(-) create mode 100644 pictures/inlove.gif create mode 100644 plugins/aktien.lua create mode 100644 plugins/app_store.lua create mode 100644 plugins/birthday_get.lua create mode 100644 plugins/birthday_set.lua create mode 100644 plugins/bitly.lua create mode 100644 plugins/bitly_create.lua rename plugins/{pluginsold => }/btc.lua (68%) create mode 100644 plugins/cleverbot.lua create mode 100644 plugins/create_sticker.lua create mode 100644 plugins/credentials_manager.lua create mode 100644 plugins/currency.lua create mode 100644 plugins/dailymotion.lua create mode 100644 plugins/danbooru2.lua create mode 100644 plugins/derpibooru.lua create mode 100644 plugins/derpibooru_nsfw.lua create mode 100644 plugins/deviantart.lua create mode 100644 plugins/dns.lua create mode 100644 plugins/dropbox.lua create mode 100644 plugins/e621.lua create mode 100644 plugins/facebook.lua create mode 100644 plugins/facebook_photo.lua create mode 100644 plugins/facebook_video.lua create mode 100644 plugins/flickr.lua create mode 100644 plugins/flickr_search.lua create mode 100644 plugins/forecast.lua create mode 100644 plugins/gamesdb.lua create mode 100644 plugins/gdrive.lua create mode 100644 plugins/gender.lua create mode 100644 plugins/get_data.lua create mode 100644 plugins/get_txt.lua create mode 100644 plugins/gfycat.lua create mode 100644 plugins/github.lua create mode 100644 plugins/googl.lua create mode 100644 plugins/google_books.lua create mode 100644 plugins/gronkh_soundboard.lua create mode 100644 plugins/instagram.lua create mode 100644 plugins/ip_info.lua create mode 100644 plugins/konachan.lua create mode 100644 plugins/konachan_nsfw.lua create mode 100644 plugins/location_manager.lua create mode 100644 plugins/love_calculator.lua create mode 100644 plugins/miiverse.lua create mode 100644 plugins/music163.lua create mode 100644 plugins/page2image.lua create mode 100644 plugins/pagespeed_insights.lua create mode 100644 plugins/play_store.lua rename plugins/{ => pluginsold}/boersedown.lua (100%) create mode 100644 plugins/pluginsold/get.lua create mode 100644 plugins/pluginsold/myanimelist.lua create mode 100644 plugins/pluginsold/quotes.lua create mode 100644 plugins/pluginsold/set.lua create mode 100644 plugins/ponyfaces.lua create mode 100644 plugins/ponywave_img.lua create mode 100644 plugins/porndoge.lua create mode 100644 plugins/pornhub_gif.lua create mode 100644 plugins/pr0gramm.lua create mode 100644 plugins/reddit.lua create mode 100644 plugins/rss_old.lua create mode 100644 plugins/safebrowsing.lua create mode 100644 plugins/soundboard.lua create mode 100644 plugins/soundcloud.lua create mode 100644 plugins/speedtest.lua create mode 100644 plugins/speedtest_net.lua create mode 100644 plugins/spotify.lua create mode 100644 plugins/spotify_search.lua create mode 100644 plugins/text_get.lua create mode 100644 plugins/text_set.lua create mode 100644 plugins/twitch.lua create mode 100644 plugins/videotogif.lua create mode 100644 plugins/vimeo.lua create mode 100644 plugins/vine.lua create mode 100644 plugins/yandere.lua create mode 100644 plugins/yourls.lua create mode 100644 plugins/youtube_channel.lua create mode 100644 plugins/youtube_playlist.lua diff --git a/.gitignore b/.gitignore index 646705e..f1be7ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,53 +1,8 @@ res/ .luarocks *.db -plugins/bitly.lua -plugins/danbooru2.lua -plugins/deviantart.lua -plugins/facebook.lua -plugins/facebook_photo.lua -plugins/facebook_video.lua -plugins/gender.lua -plugins/github.lua -plugins/googl.lua -plugins/ip_info.lua -plugins/konachan.lua -plugins/konachan_nsfw.lua -plugins/page2image.lua -plugins/pagespeed_insights.lua -plugins/ping_adress.lua plugins/test.lua -plugins/twitch.lua -plugins/derpibooru_nsfw.lua -plugins/derpibooru.lua -plugins/app_store.lua -plugins/dailymotion.lua -plugins/dns.lua -plugins/gdrive.lua -plugins/instagram.lua -plugins/play_store.lua -plugins/reddit.lua -plugins/soundcloud.lua -plugins/spotify.lua -plugins/vimeo.lua -plugins/vine.lua -plugins/youtube_playlist.lua -plugins/yandere.lua -plugins/e621.lua -plugins/ponyfaces.lua -plugins/rss_old.lua -plugins/gfycat.lua -plugins/yourls.lua -plugins/youtube_channel.lua -plugins/pr0gramm.lua plugins/licht.lua plugins/dantest.lua -plugins/flickr.lua -plugins/flickr_search.lua -plugins/google_books.lua -plugins/safebrowsing.lua plugins/stats2.lua -plugins/help2.lua -plugins/miiverse.lua -plugins/videotogif.lua -plugins/music163.lua \ No newline at end of file +plugins/help2.lua \ No newline at end of file diff --git a/bot/bot.lua b/bot/bot.lua index b374c90..7a3730c 100644 --- a/bot/bot.lua +++ b/bot/bot.lua @@ -1,26 +1,25 @@ -package.path = package.path .. ';.luarocks/share/lua/5.2/?.lua' - ..';.luarocks/share/lua/5.2/?/init.lua' -package.cpath = package.cpath .. ';.luarocks/lib/lua/5.2/?.so' - +package.path = './.luarocks/share/lua/5.2/?.lua;./.luarocks/share/lua/5.2/?/init.lua;./.luarocks/lib/lua/5.2/?.lua;./.luarocks/lib/lua/5.2/?/init.lua;' .. package.path +require("luarocks.loader") require("./bot/utils") -VERSION = '2.5-reloaded' +VERSION = '20151003' -- This function is called when tg receive a msg function on_msg_receive (msg) + if not started then return end local receiver = get_receiver(msg) - + -- vardump(msg) msg = pre_process_service_msg(msg) if msg_valid(msg) then msg = pre_process_msg(msg) - if msg then - match_plugins(msg) - --mark_read(receiver, ok_cb, false) + if msg then + match_plugins(msg) + -- mark_read(receiver, ok_cb, false) end end end @@ -33,9 +32,10 @@ function on_binlog_replay_end() postpone (cron_plugins, false, 60*5.0) -- See plugins/isup.lua as an example for cron - _config = load_config() + -- load sudo_users and credentials + sudo_users = load_sudo_users() cred_data = load_cred() - + -- load plugins plugins = {} load_plugins() @@ -53,11 +53,6 @@ function msg_valid(msg) print('\27[36mNicht gültig: alte Nachricht\27[39m') return false end - - if msg.unread == 0 then - print('\27[36mNicht gültig: gelesen\27[39m') - return false - end if not msg.to.id then print('\27[36mNicht gültig: To id not provided\27[39m') @@ -68,6 +63,11 @@ function msg_valid(msg) print('\27[36mNicht gültig: From id not provided\27[39m') return false end + + if msg.unread == 0 then + print('\27[36mNicht gültig: gelesen\27[39m') + return false + end if msg.from.id == our_id then print('\27[36mNicht gültig: Nachricht von unserer ID\27[39m') @@ -83,11 +83,10 @@ function msg_valid(msg) print('\27[36mNicht gültig: Telegram Nachricht\27[39m') return false end - + return true end --- function pre_process_service_msg(msg) if msg.service then local action = msg.action or {type=""} @@ -111,7 +110,7 @@ function pre_process_msg(msg) for name,plugin in pairs(plugins) do if plugin.pre_process and msg then -- print('Preprocess', name) - msg = plugin.pre_process(msg) + msg = plugin.pre_process(msg) end end @@ -125,22 +124,18 @@ function match_plugins(msg) end end --- Check if plugin is on _config.disabled_plugin_on_chat table -local function is_plugin_disabled_on_chat(plugin_name, receiver) - local disabled_chats = _config.disabled_plugin_on_chat - -- Table exists and chat has disabled plugins - if disabled_chats and disabled_chats[receiver] then - -- Checks if plugin is disabled on this chat - for disabled_plugin,disabled in pairs(disabled_chats[receiver]) do - if disabled_plugin == plugin_name and disabled then - local warning = 'Plugin '..disabled_plugin..' ist in diesem Chat deaktiviert' - print(warning) - -- send_msg(receiver, warning, ok_cb, false) - return true - end - end +-- Check if plugin is deactivated in this chat +local function is_plugin_disabled_on_chat(plugin_name, msg) + local hash = get_redis_hash(msg, 'disabled_plugins') + local disabled = redis:hget(hash, plugin_name) + + -- Plugin is disabled + if disabled == 'true' then + print('Plugin '..plugin_name..' ist in diesem Chat deaktiviert') + return true + else + return false end - return false end function match_plugin(plugin, plugin_name, msg) @@ -151,20 +146,13 @@ function match_plugin(plugin, plugin_name, msg) local matches = match_pattern(pattern, msg.text) if matches then print("Nachricht stimmt überein mit ", pattern) - -- Send typing - --if pattern ~= ".*" then send_typing(receiver, ok_cb, true) end - if is_plugin_disabled_on_chat(plugin_name, receiver) then + if is_plugin_disabled_on_chat(plugin_name, msg) then return nil end -- Function exists if plugin.run then - -- check if user has privileges - if can_use_bot(msg) then - print("Gesperrt") - -- local text = 'Du darfst den Bot nicht nutzen!' - -- send_msg(receiver, text, ok_cb, false) - else + if not plugin.notyping then send_typing(receiver, ok_cb, true) end -- If plugin is for privileged users only if not warns_user_not_allowed(plugin, msg) then local result = plugin.run(msg, matches) @@ -173,7 +161,6 @@ function match_plugin(plugin, plugin_name, msg) end end end - end -- One patterns matches return end @@ -185,61 +172,37 @@ function _send_msg(destination, text) send_large_msg(destination, text) end --- Save the content of _config to config.lua -function save_config( ) - serialize_to_file(_config, './data/config.lua') - print ('Configuration in ./data/config.lua gespeichert') +-- Load superusers from redis +function load_sudo_users() + if redis:exists("telegram:sudo_users") == false then + -- If sudo_users set doesnt exists + print ("Created new sudo_users set: telegram:sudo_users") + create_sudo_users() + end + local sudo_users = redis:smembers("telegram:sudo_users") + for v,user in pairs(sudo_users) do + print("Superuser: " .. user) + end + return sudo_users end --- Returns the config from config.lua file. --- If file doesn't exist, create it. -function load_config( ) - local f = io.open('./data/config.lua', "r") - -- If config.lua doesn't exist - if not f then - print ("Neue Config-Datei erstellt: data/config.lua") - create_config() - else - f:close() - end - local config = loadfile ("./data/config.lua")() - for v,user in pairs(config.sudo_users) do - print("Erlaubter Benutzer: " .. user) - end - return config -end - -function load_cred( ) - local cf = io.open('./data/credentials.lua', "r") - -- If credentials.lua doesnt exists - if not cf then - print ("Neue Credentials-Datei erstellt: data/credentials.lua") +-- Load credentials from redis +function load_cred() + if redis:exists("telegram:credentials") == false then + -- If credentials hash doesnt exists + print ("Neuen Credentials-Hash erstellt: telegram:credentials") create_cred() - else - cf:close() end - local _file_cred = loadfile ("./data/credentials.lua")() - return _file_cred + return redis:hgetall("telegram:credentials") end --- Create a basic config.json file and saves it. -function create_config( ) - -- A simple config with basic plugins and ourselves as privileged user - config = { - enabled_plugins = { - "help", - "plugins" }, - sudo_users = {our_id}, - disabled_channels = {} - } - serialize_to_file(config, './data/config.lua') - print ('Configuration in ./data/config.lua gespeichert') -end - -function create_cred( ) +-- create credentials hash with redis +function create_cred() cred = { bitly_access_token = "", - cloudconvert_apikey = "", + cloudinary_apikey = "", + cloudinary_api_secret = "", + cloudinary_public_id = "", derpibooru_apikey = "", fb_access_token = "", flickr_apikey = "", @@ -247,15 +210,20 @@ function create_cred( ) ftp_username = "", ftp_password = "", gender_apikey = "", + golem_apikey = "", google_apikey = "", google_cse_id = "", + gitlab_private_token = "", + gitlab_project_id = "", instagram_access_token = "", lyricsnmusic_apikey = "", + mal_username = "", + mal_pw = "", neutrino_userid = "", neutrino_apikey = "", + owm_apikey = "", page2images_restkey = "", soundcloud_client_id = "", - superfeedr_authorization = "", tw_consumer_key = "", tw_consumer_secret = "", tw_access_token = "", @@ -266,8 +234,28 @@ function create_cred( ) yourls_site_url = "", yourls_signature_token = "" } - serialize_to_file(cred, './data/credentials.lua') - print ('Credentials gespeichert in ./data/credentials.lua') + redis:hmset("telegram:credentials", cred) + print ('Credentials gespeichert in telegram:credentials') +end + +function create_sudo_users() + redis:sadd("telegram:sudo_users", '0') + redis:sadd("telegram:sudo_users", '1') + redis:sadd("telegram:sudo_users", our_id) + print('Speichere Superuser in telegram:sudo_users') + print('Adde deine ID mit Redis: SADD telegram:sudo_users YOURID') +end + +-- create plugin set if it doesn't exist +function create_plugin_set() + enabled_plugins = { + "plugins", + "manager" + } + print ('enabling a few plugins - saving to redis set telegram:enabled_plugins') + for _,plugin in pairs(enabled_plugins) do + redis:sadd("telegram:enabled_plugins", plugin) + end end function on_our_id (id) @@ -291,7 +279,11 @@ end -- Enable plugins in config.json function load_plugins() - for k, v in pairs(_config.enabled_plugins) do + enabled_plugins = redis:smembers('telegram:enabled_plugins') + if not enabled_plugins[1] then + create_plugin_set() + end + for k, v in pairs(enabled_plugins) do print("Lade Plugin", v) local ok, err = pcall(function() @@ -300,9 +292,10 @@ function load_plugins() end) if not ok then - print('\27[31mFehler beim laden des Plugins '..v..'\27[39m') + print('\27[31mFehler beim Laden vom Plugin '..v..'\27[39m') print('\27[31m'..err..'\27[39m') end + end end @@ -324,4 +317,5 @@ end our_id = 0 now = os.time() math.randomseed(now) -started = false + +started = false \ No newline at end of file diff --git a/bot/utils.lua b/bot/utils.lua index 2e032a7..51c6198 100644 --- a/bot/utils.lua +++ b/bot/utils.lua @@ -1,14 +1,14 @@ -URL = require "socket.url" -http = require "socket.http" -https = require "ssl.https" +http = require("socket.http") +https = require("ssl.https") ltn12 = require "ltn12" -serpent = require "serpent" -feedparser = require "feedparser" +URL = require("socket.url") +feedparser = require ("feedparser") json = (loadfile "./libs/JSON.lua")() +serpent = (loadfile "./libs/serpent.lua")() mimetype = (loadfile "./libs/mimetype.lua")() redis = (loadfile "./libs/redis.lua")() -http.TIMEOUT = 10 +http.TIMEOUT = 5 function get_receiver(msg) if msg.to.type == 'user' then @@ -19,7 +19,7 @@ function get_receiver(msg) end if msg.to.type == 'encr_chat' then return msg.to.print_name - end + end end function is_chat_msg( msg ) @@ -45,15 +45,9 @@ function string:split(sep) return fields end --- DEPRECATED -function string.trim(s) - print("string.trim(s) is DEPRECATED use string:trim() instead") - return s:gsub("^%s*(.-)%s*$", "%1") -end - -- Removes spaces -function string:trim() - return self:gsub("^%s*(.-)%s*$", "%1") +function string.trim(s) + return s:gsub("^%s*(.-)%s*$", "%1") end function get_http_file_name(url, headers) @@ -63,17 +57,18 @@ function get_http_file_name(url, headers) file_name = file_name or url:match("[^%w]+(%w+)[^%w]+$") -- Random name, hope content-type works file_name = file_name or str:random(5) - - local content_type = headers["content-type"] + + local content_type = headers["content-type"] local extension = nil if content_type then - extension = mimetype.get_mime_extension(content_type) + extension = mimetype.get_mime_extension(content_type) end + if extension then file_name = file_name.."."..extension end - + local disposition = headers["content-disposition"] if disposition then -- attachment; filename=CodeCogsEqn.png @@ -95,7 +90,7 @@ function download_to_file(url, file_name) url = url, sink = ltn12.sink.table(respbody), redirect = true - } + } -- nil, code, headers, status local response = nil @@ -147,16 +142,15 @@ function run_command(str) return result end --- User has privileges function is_sudo(msg) - local var = false - -- Check users id in config - for v,user in pairs(_config.sudo_users) do - if user == msg.from.id then - var = true - end - end - return var + local var = false + -- Check if user id is in sudoers table + for v,user in pairs(sudo_users) do + if string.match(user, msg.from.id) then + var = true + end + end + return var end function can_use_bot(msg) @@ -172,11 +166,11 @@ end -- Returns the name of the sender function get_name(msg) - local name = msg.from.first_name - if name == nil then - name = msg.from.id - end - return name + local name = msg.from.first_name + if name == nil then + name = msg.from.id + end + return name end -- Returns at table of lua files inside plugins @@ -255,27 +249,33 @@ end -- Download the image and send to receiver, it will be deleted. -- cb_function and cb_extra are optionals callback -function send_photo_from_url(receiver, url, cb_function, cb_extra) +function send_photo_from_url(receiver, url, cb_function, cb_extra, sendNotErrMsg) -- If callback not provided cb_function = cb_function or ok_cb cb_extra = cb_extra or false local file_path = download_to_file(url, false) if not file_path then -- Error - local text = 'Fehler beim laden des Bildes' - send_msg(receiver, text, cb_function, cb_extra) + if sendNotErrMsg then + return false + else + local text = 'Fehler beim Laden des Bildes' + send_msg(receiver, text, cb_function, cb_extra) + end else print("Datei Pfad: "..file_path) _send_photo(receiver, file_path, cb_function, cb_extra) + return true end end -- Same as send_photo_from_url but as callback function -function send_photo_from_url_callback(cb_extra, success, result) +function send_photo_from_url_callback(cb_extra, success, result, sendErrMsg) local receiver = cb_extra.receiver local url = cb_extra.url + local file_path = download_to_file(url, false) if not file_path then -- Error - local text = 'Fehler beim laden des Bildes' + local text = 'Fehler beim Herunterladen des Bildes' send_msg(receiver, text, ok_cb, false) else print("Datei Pfad: "..file_path) @@ -283,6 +283,21 @@ function send_photo_from_url_callback(cb_extra, success, result) end end +-- Same as above, but with send_as_document +function send_document_from_url_callback(cb_extra, success, result) + local receiver = cb_extra.receiver + local url = cb_extra.url + + local file_path = download_to_file(url, false) + if not file_path then -- Error + local text = 'Fehler beim Herunterladen des Dokumentes' + send_msg(receiver, text, ok_cb, false) + else + print("File path: "..file_path) + _send_document(receiver, file_path, ok_cb, false) + end +end + -- Send multiple images asynchronous. -- param urls must be a table. function send_photos_from_url(receiver, urls) @@ -355,10 +370,24 @@ end -- Download the image and send to receiver, it will be deleted. -- cb_function and cb_extra are optionals callback -function send_document_from_url(receiver, url, cb_function, cb_extra) +function send_document_from_url(receiver, url, cb_function, cb_extra, sendNotErrMsg) + -- If callback not provided + cb_function = cb_function or ok_cb + cb_extra = cb_extra or false + local file_path = download_to_file(url, false) - print("Datei Pfad: "..file_path) - _send_document(receiver, file_path, cb_function, cb_extra) + if not file_path then -- Error + if sendNotErrMsg then + return false + else + local text = 'Fehler beim Herunterladen des Dokumentes' + send_msg(receiver, text, cb_function, cb_extra) + end + else + print("Datei Pfad: "..file_path) + _send_document(receiver, file_path, cb_function, cb_extra) + return true + end end -- Parameters in ?a=1&b=2 style @@ -409,48 +438,6 @@ function send_order_msg(destination, msgs) send_order_msg_callback(cb_extra, true) end -function send_order_msg_callback(cb_extra, success, result) - local destination = cb_extra.destination - local msgs = cb_extra.msgs - local file_path = cb_extra.file_path - if file_path ~= nil then - os.remove(file_path) - print("Deleted: " .. file_path) - end - if type(msgs) == 'string' then - send_large_msg(destination, msgs) - elseif type(msgs) ~= 'table' then - return - end - if #msgs < 1 then - return - end - local msg = table.remove(msgs, 1) - local new_cb_extra = { - destination = destination, - msgs = msgs - } - if type(msg) == 'string' then - send_msg(destination, msg, send_order_msg_callback, new_cb_extra) - elseif type(msg) == 'table' then - local typ = msg[1] - local nmsg = msg[2] - new_cb_extra.file_path = nmsg - if typ == 'document' then - send_document(destination, nmsg, send_order_msg_callback, new_cb_extra) - elseif typ == 'image' or typ == 'photo' then - send_photo(destination, nmsg, send_order_msg_callback, new_cb_extra) - elseif typ == 'audio' then - send_audio(destination, nmsg, send_order_msg_callback, new_cb_extra) - elseif typ == 'video' then - send_video(destination, nmsg, send_order_msg_callback, new_cb_extra) - else - send_file(destination, nmsg, send_order_msg_callback, new_cb_extra) - end - end -end - --- Same as send_large_msg_callback but friendly params function send_large_msg(destination, text) local cb_extra = { destination = destination, @@ -486,17 +473,13 @@ function send_large_msg_callback(cb_extra, success, result) end -- Returns a table with matches or nil -function match_pattern(pattern, text, lower_case) +--function match_pattern(pattern, text, lower_case) +function match_pattern(pattern, text) if text then - local matches = {} - if lower_case then - matches = { string.match(text:lower(), pattern) } - else - matches = { string.match(text, pattern) } - end - if next(matches) then - return matches - end + local matches = { string.match(text, pattern) } + if next(matches) then + return matches + end end -- nil end @@ -521,7 +504,7 @@ function load_from_file(file, default_data) return loadfile (file)() end - function run_bash(str) +function run_bash(str) local cmd = io.popen(str) local result = cmd:read('*all') cmd:close() @@ -536,11 +519,26 @@ function run_sh(msg) return text end +function round(num, idp) + if idp and idp>0 then + local mult = 10^idp + return math.floor(num * mult + 0.5) / mult + end + return math.floor(num + 0.5) +end + function unescape(str) str = string.gsub( str, '<', '<' ) str = string.gsub( str, '>', '>' ) str = string.gsub( str, '"', '"' ) str = string.gsub( str, ''', "'" ) + str = string.gsub( str, "Ä", "Ä") + str = string.gsub( str, "ä", "ä") + str = string.gsub( str, "Ö", "Ö") + str = string.gsub( str, "ö", "ö") + str = string.gsub( str, "Uuml;", "Ü") + str = string.gsub( str, "ü", "ü") + str = string.gsub( str, "ß", "ß") str = string.gsub( str, '&#(%d+);', function(n) return string.char(n) end ) str = string.gsub( str, '&#x(%d+);', function(n) return string.char(tonumber(n,16)) end ) str = string.gsub( str, '&', '&' ) -- Be sure to do this after all others @@ -563,4 +561,83 @@ function unescape_html(str) return var end) return new +end + +function post_petition(url, arguments) + local url, h = string.gsub(url, "http://", "") + local url, hs = string.gsub(url, "https://", "") + local post_prot = "http" + if hs == 1 then + post_prot = "https" + end + local response_body = {} + local request_constructor = { + url = post_prot..'://'..url, + method = "POST", + sink = ltn12.sink.table(response_body), + headers = {}, + redirect = false + } + + local source = arguments + if type(arguments) == "table" then + local source = helpers.url_encode_arguments(arguments) + end + request_constructor.headers["Content-Type"] = "application/x-www-form-urlencoded" + request_constructor.headers["Content-Length"] = tostring(#source) + request_constructor.source = ltn12.source.string(source) + if post_prot == "http" then + ok, response_code, response_headers, response_status_line = http.request(request_constructor) + else + ok, response_code, response_headers, response_status_line = https.request(request_constructor) + end + + if not ok then + return nil + end + + response_body = json:decode(table.concat(response_body)) + + return response_body +end + +function get_redis_hash(msg, var) + if msg.to.type == 'chat' then + return 'chat:'..msg.to.id..':'..var + end + if msg.to.type == 'user' then + return 'user:'..msg.from.id..':'..var + end +end + +function tablelength(T) + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count +end + +function comma_value(amount) + local formatted = amount + while true do + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1.%2') + if (k==0) then + break + end + end + return formatted +end + + +function string.ends(str, fin) + return fin=='' or string.sub(str,-string.len(fin)) == fin +end + +function get_location(user_id) + local hash = 'user:'..user_id + local set_location = redis:hget(hash, 'location') + if set_location == 'false' or set_location == nil then + return false + else + return set_location + end end \ No newline at end of file diff --git a/pictures/inlove.gif b/pictures/inlove.gif new file mode 100644 index 0000000000000000000000000000000000000000..08e5d5a9928dcb098b81add84c28c30368803eb7 GIT binary patch literal 62047 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

gnvbVQk8(5}sbkDITLsas#1? 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 t +-- DO NOT USE WITHOUT PERMISSION + +do + +local function get_stock (symbol) + local BASE_URL = 'https://query.yahooapis.com/v1/public/yql' + local url = BASE_URL..'?q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol="'..symbol..'"&env=http%3A%2F%2Fdatatables.org%2Falltables.env&format=json' + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res).query.results.quote + return data +end + +function send_stock_data(data, receiver) + -- local ask = data.Ask + local bid = data.Bid + local change = data.Change + local percent = data.ChangeinPercent + local currency = data.Currency + local range = data.DaysRange + local name = data.Name + local yesterday = data.PreviousClose + local year_range = data.YearRange + local symbol = data.symbol + + local text = name..' ('..symbol..') Aktien in '..currency..':\nAktueller Preis: '..bid..' ('..change..' / '..percent..')\nGestriger Preis: '..yesterday..'\nHeute: '..range..'\nDieses Jahr: '..year_range + send_msg(receiver, text, ok_cb, false) +end + +local function run(msg, matches) + local symbol = string.upper(matches[1]) + local data = get_stock(symbol) + local receiver = get_receiver(msg) + if data.Bid ~= nil then + send_stock_data(data, receiver) + else + return "Dieses Symbol existiert nicht." + end +end + +return { + description = "Sendet Aktieninfos", + usage = "/aktien [Symbol]: Sendet Aktieninfos", + patterns = { + "^/aktien ([A-Za-z0-9]+)$", + "^/stocks ([A-Za-z0-9]+)$", + "^/aktie ([A-Za-z0-9]+)$", + "^/stock ([A-Za-z0-9]+)$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/alive.lua b/plugins/alive.lua index 43dd334..6d4d600 100644 --- a/plugins/alive.lua +++ b/plugins/alive.lua @@ -8,7 +8,9 @@ end return { description = "Ist der Bot noch da?", usage = {"Miku"}, - patterns = {"^[M|m]iku(?)$","^[M|m]iku$"}, + patterns = {"^[Mm][Ii][Kk][Uu](?)$", + "^[Mm][Ii][Kk][Uu]$" + }, run = run } --by Akamaru [https://ponywave.de] \ No newline at end of file diff --git a/plugins/app_store.lua b/plugins/app_store.lua new file mode 100644 index 0000000..e3e4185 --- /dev/null +++ b/plugins/app_store.lua @@ -0,0 +1,122 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://itunes.apple.com/lookup' + +local makeOurDate = function(dateString) + local pattern = "(%d+)%-(%d+)%-(%d+)T" + local year, month, day = dateString:match(pattern) + return day..'.'..month..'.'..year +end + +local function get_appstore_data() + local url = BASE_URL..'/?id='..appid..'&country=de' + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res).results[1] + + if data == nil then return 'NOTFOUND' end + if data.wrapperType ~= 'software' then return nil end + + return data +end + +local function send_appstore_data(data, receiver) + -- Header + local name = data.trackName + local author = data.sellerName + local price = data.formattedPrice + local version = data.version + + -- Body + local description = string.sub(unescape(data.description), 1, 150) .. '...' + local min_ios_ver = data.minimumOsVersion + local size = string.gsub(round(data.fileSizeBytes / 1000000, 2), "%.", "%,") -- wtf Apple, it's 1024, not 1000! + local release = makeOurDate(data.releaseDate) + if data.isGameCenterEnabled then + game_center = '\nUnterstützt Game Center' + else + game_center = '' + end + local category_count = tablelength(data.genres) + if category_count == 1 then + category = '\nKategorie: '..data.genres[1] + else + local category_loop = '\nKategorien: ' + for v in pairs(data.genres) do + if v < category_count then + category_loop = category_loop..data.genres[v]..', ' + else + category_loop = category_loop..data.genres[v] + end + end + category = category_loop + end + + -- Footer + if data.averageUserRating and data.userRatingCount then + avg_rating = 'Bewertung: '..string.gsub(data.averageUserRating, "%.", "%,")..' Sterne ' + ratings = 'von '..comma_value(data.userRatingCount)..' Bewertungen' + else + avg_rating = "" + ratings = "" + end + + + local header = name..' v'..version..' von '..author..' ('..price..'):' + local body = '\n'..description..'\nBenötigt mind. iOS '..min_ios_ver..'\nGröße: '..size..' MB\nErstveröffentlicht am '..release..game_center..category + local footer = '\n'..avg_rating..ratings + local text = header..body..footer + + -- Picture + if data.screenshotUrls[1] and data.ipadScreenshotUrls[1] then + image_url = data.screenshotUrls[1] + elseif data.screenshotUrls[1] and not data.ipadScreenshotUrls[1] then + image_url = data.screenshotUrls[1] + elseif not data.screenshotUrls[1] and data.ipadScreenshotUrls[1] then + image_url = data.ipadScreenshotUrls[1] + else + image_url = nil + end + + if image_url then + local cb_extra = { + receiver=receiver, + url=image_url + } + send_msg(receiver, text, send_photo_from_url_callback, cb_extra) + else + send_msg(receiver, text, ok_cb, false) + end +end + +local function run(msg, matches) + local receiver = get_receiver(msg) + if not matches[3] then + appid = matches[1] + else + appid = matches[3] + end + local data = get_appstore_data() + if data == nil then print('Das Appstore-Plugin unterstützt zurzeit nur Apps!') end + if data == 'HTTP-FEHLER' or data == 'NOTFOUND' then + return 'App nicht gefunden!' + else + send_appstore_data(data, receiver) + end +end + +return { + description = "Sendet iPhone App-Store Info.", + usage = {"Link zu App auf iTunes", + "/itunes (ID)" + }, + patterns = {"itunes.apple.com/(.*)/app/(.*)/id(%d+)", + "^/itunes (%d+)$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/birthday_get.lua b/plugins/birthday_get.lua new file mode 100644 index 0000000..92406a5 --- /dev/null +++ b/plugins/birthday_get.lua @@ -0,0 +1,51 @@ +local function get_value(msg, var_name) + local hash = 'telegram:birthdays' + if hash then + local value = redis:hget(hash, var_name) + if not value then + return'Geburtstag nicht gefunden, benutze "/getbd", um alle Geburtstage aufzulisten.' + else + return var_name..' hat am '..value..' Geburtstag' + end + end +end + +local function list_variables(msg) + local hash = 'telegram:birthdays' + + if hash then + print('Getting variable from redis hash '..hash) + local names = redis:hkeys(hash) + local text = '' + for i=1, #names do + variables = get_value(msg, names[i]) + text = text..variables.."\n" + end + if text == '' or text == nil then + return 'Keine Geburtstage vorhanden!' + else + return text + end + end +end + +local function run(msg, matches) + if matches[2] then + return get_value(msg, matches[2]) + else + return 'Geburtstagsliste:\n\n'..list_variables(msg) + end +end + +return { + description = "Zeigt Geburtstage, die mit /setbd gesetzt wurden", + usage = { + "/getbd: Gibt alle Geburtstage aus", + "/getbd (Name): Gibt den Geburtstag aus." + }, + patterns = { + "^(/getbd) (.+)$", + "^/getbd$" + }, + run = run +} \ No newline at end of file diff --git a/plugins/birthday_set.lua b/plugins/birthday_set.lua new file mode 100644 index 0000000..c7d4a6b --- /dev/null +++ b/plugins/birthday_set.lua @@ -0,0 +1,47 @@ +local function save_value(msg, name, value) + if (not name or not value) then + return "Benutzung: /setbd [Name] [Tag. Monat]" + end + + local hash = 'telegram:birthdays' + if hash then + print('Saving variable to redis hash '..hash) + redis:hset(hash, name, value) + return "Geburtstag von "..name.." am "..value.." gespeichert!" + end +end + +local function delete_value(msg, name) + local hash = 'telegram:birthdays' + if redis:hexists(hash, name) == true then + print('Deleting birthday from redis hash '..hash) + redis:hdel(hash, name) + return 'Geburtstag von "'..name..'" erfolgreich gelöscht!' + else + return 'Du kannst keinen Geburtstag löschen, der nicht existiert ._.' + end +end + +local function run(msg, matches) + local name = string.sub(matches[1], 1, 50) + local value = string.sub(matches[2], 1, 1000) + + if value == "nil" then + text = delete_value(msg, name, value) + else + text = save_value(msg, name, value) + end + return text +end + +return { + description = "Speichert Geburtstage.", + usage = { + "/setbd [Name] [Tag. Monat]: Speichert ein Geburtstag.", + "/setbd (Name) nil: Löscht Geburtstag" + }, + patterns = { + "^/setbd ([^%s]+) (.+)$" + }, + run = run +} \ No newline at end of file diff --git a/plugins/bitly.lua b/plugins/bitly.lua new file mode 100644 index 0000000..404701e --- /dev/null +++ b/plugins/bitly.lua @@ -0,0 +1,34 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://api-ssl.bitly.com/v3/expand' + +local function expand_bitly_link (shorturl) + local access_token = cred_data.bitly_access_token + local url = BASE_URL..'?access_token='..access_token..'&shortUrl=https://bit.ly/'..shorturl + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res).data + return data.expand[1].long_url +end + +local function run(msg, matches) + local shorturl = matches[1] + return expand_bitly_link(shorturl) +end + +return { + description = "Verlängert Bitly-Links", + usage = "Verlängert bit.ly, bitly.com, j.mp und andib.tk Links", + patterns = { + "bit.ly/([A-Za-z0-9-_-]+)", + "bitly.com/([A-Za-z0-9-_-]+)", + "j.mp/([A-Za-z0-9-_-]+)", + "andib.tk/([A-Za-z0-9-_-]+)" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/bitly_create.lua b/plugins/bitly_create.lua new file mode 100644 index 0000000..ee28a87 --- /dev/null +++ b/plugins/bitly_create.lua @@ -0,0 +1,43 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://api-ssl.bitly.com/v3/shorten' +local access_token = cred_data.bitly_access_token + +function create_bitlink (long_url, domain) + local url = BASE_URL..'?access_token='..access_token..'&domain='..domain..'&longUrl='..long_url..'&format=txt' + local text,code = https.request(url) + if code ~= 200 then return 'FEHLER: '..text end + return text +end + +function run(msg, matches) + if matches[2] == nil then + long_url = url_encode(matches[1]) + domain = 'bit.ly' + else + long_url = url_encode(matches[2]) + domain = matches[1] + end + return create_bitlink(long_url, domain) +end + +return { + description = "Krzt einen Link", + usage = { + "/short [Link]: Krzt einen Link mit Bitly", + "/short [j.mp|bit.ly|bitly.com|andib.tk] [Link]: Krzt einen Link mit der ausgewhlten Kurz-URL" + }, + patterns = { + "^/short (j.mp) (https?://[%w-_%.%?%.:/%+=&]+)$", + "^/short (bit.ly) (https?://[%w-_%.%?%.:/%+=&]+)$", + "^/short (bitly.com) (https?://[%w-_%.%?%.:/%+=&]+)$", + "^/short (andib.tk) (https?://[%w-_%.%?%.:/%+=&]+)$", + "^/short (https?://[%w-_%.%?%.:/%+=&]+)$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/pluginsold/btc.lua b/plugins/btc.lua similarity index 68% rename from plugins/pluginsold/btc.lua rename to plugins/btc.lua index 8183070..b8428e9 100644 --- a/plugins/pluginsold/btc.lua +++ b/plugins/btc.lua @@ -6,9 +6,11 @@ local function getBTCX(amount,currency) if code ~= 200 then return nil end local data = json:decode(res) + local ask = string.gsub(data.ask, "%.", "%,") + local bid = string.gsub(data.bid, "%.", "%,") -- Easy, it's right there - text = "BTC/"..currency..'\n'..'Buy: '..data.ask..'\n'..'Sell: '..data.bid + text = "BTC/"..currency..'\n'..'Kaufen: '..ask..'\n'..'Verkaufen: '..bid -- If we have a number as second parameter, calculate the bitcoin amount if amount~=nil then @@ -39,14 +41,17 @@ local function run(msg, matches) end return { - description = "Bitcoin global average market value (in EUR or USD)", - usage = "!btc [EUR|USD] [amount]", + description = "Globaler Bitcoin-Wert (in EUR oder USD)", + usage = { + "/btc: Zeigt aktuellen Bitcoin-Kurs", + "/btc [EUR|USD] [Menge]: Rechnet Bitcoin in Euro/USD um" + }, patterns = { - "^!btc$", - "^!btc ([Ee][Uu][Rr])$", - "^!btc ([Uu][Ss][Dd])$", - "^!btc (EUR) (%d+[%d%.]*)$", - "^!btc (USD) (%d+[%d%.]*)$" + "^/btc$", + "^/btc ([Ee][Uu][Rr])$", + "^/btc ([Uu][Ss][Dd])$", + "^/btc (EUR) (%d+[%d%.]*)$", + "^/btc (USD) (%d+[%d%.]*)$" }, run = run -} +} \ No newline at end of file diff --git a/plugins/change_pic.lua b/plugins/change_pic.lua index 88f9db7..4cbc88f 100644 --- a/plugins/change_pic.lua +++ b/plugins/change_pic.lua @@ -10,6 +10,7 @@ return { description = "", usage = {""}, patterns = {"^/pic (.*)$"}, - run = run + run = run, + notyping = true } --by Akamaru [https://ponywave.de] \ No newline at end of file diff --git a/plugins/channels.lua b/plugins/channels.lua index a4ce5fd..8c378bb 100644 --- a/plugins/channels.lua +++ b/plugins/channels.lua @@ -1,53 +1,50 @@ -- Checks if bot was disabled on specific chat -local function is_channel_disabled( receiver ) - if not _config.disabled_channels then +local function is_channel_disabled(msg) + local hash = 'chat:'..msg.to.id..':disabled' + local disabled = redis:get(hash) + + if not disabled or disabled == "false" then return false end - if _config.disabled_channels[receiver] == nil then - return false - end - - return _config.disabled_channels[receiver] + return disabled end -local function enable_channel(receiver) - if not _config.disabled_channels then - _config.disabled_channels = {} - end - - if _config.disabled_channels[receiver] == nil then - return 'Channel ist nicht deaktiviert!' - end - - _config.disabled_channels[receiver] = false - - save_config() - return "Channel wieder aktiviert!" +local function enable_channel(msg) + local hash = 'chat:'..msg.to.id..':disabled' + local disabled = redis:get(hash) + if disabled then + print('Setze Redis Variable "'..hash..'" auf false') + redis:set(hash, false) + return 'Channel aktiviert' + else + return 'Channel ist nicht deaktiviert!' + end end -local function disable_channel( receiver ) - if not _config.disabled_channels then - _config.disabled_channels = {} - end - - _config.disabled_channels[receiver] = true - - save_config() - return "Channel deaktiviert!" +local function disable_channel(msg) + local hash = 'chat:'..msg.to.id..':disabled' + local disabled = redis:get(hash) + if disabled ~= "true" then + print('Setze Redis Variable "'..hash..'" auf true') + redis:set(hash, true) + return 'Channel deaktiviert' + else + return 'Channel ist bereits deaktiviert!' + end end local function pre_process(msg) - local receiver = get_receiver(msg) - -- If sender is sudo then re-enable the channel + -- If is sudo can reeanble the channel if is_sudo(msg) then if msg.text == "/channel enable" then - enable_channel(receiver) + enable_channel(msg) end end - if is_channel_disabled(receiver) then + if is_channel_disabled(msg) then + print('Channel wurde deaktiviert') msg.text = "" end @@ -55,22 +52,28 @@ local function pre_process(msg) end local function run(msg, matches) - local receiver = get_receiver(msg) -- Enable a channel if matches[1] == 'enable' then - return enable_channel(receiver) + return enable_channel(msg) end -- Disable a channel if matches[1] == 'disable' then - return disable_channel(receiver) + return disable_channel(msg) end end return { - description = "", - usage = {"/channel kann nur Akamaru"}, - patterns = {"^/channel? (enable)","^/channel? (disable)" }, - run = run, - privileged = true, - pre_process = pre_process + description = "(De)aktiviert den Bot im Chat (nur Superuser).", + usage = { + "/channel enable: Aktiviert den Bot im Chat", + "/channel disable: Deaktiviert den Bot im Chat" + }, + patterns = { + "^/channel? (enable)", + "^/channel? (disable)" + }, + run = run, + privileged = true, + pre_process = pre_process, + notyping = true } \ No newline at end of file diff --git a/plugins/cleverbot.lua b/plugins/cleverbot.lua new file mode 100644 index 0000000..be6daad --- /dev/null +++ b/plugins/cleverbot.lua @@ -0,0 +1,27 @@ +do + +function run(msg, matches) + local text = msg.text + local url = "http://nocf.andibi.tk/dl/chatter-bot-api/cleverbot.php?text="..URL.escape(text) + local query = http.request(url) + if query == nil then return 'Ein Fehler ist aufgetreten :(' end + local decode = json:decode(query) + local answer = string.gsub(decode.clever, "Ä", "") + local answer = string.gsub(answer, "ä", "") + local answer = string.gsub(answer, "Ö", "") + local answer = string.gsub(answer, "ö", "") + local answer = string.gsub(answer, "Ü", "") + local answer = string.gsub(answer, "ü", "") + local answer = string.gsub(answer, "ß", "") + return answer +end + + +return { + description = "Chat mit CleverBot", + usage = "/cbot [Text]: Befragt den Cleverbot", + patterns = {"^/cbot (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/create_sticker.lua b/plugins/create_sticker.lua new file mode 100644 index 0000000..4deb9ec --- /dev/null +++ b/plugins/create_sticker.lua @@ -0,0 +1,57 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local apikey = cred_data.cloudinary_apikey +local api_secret = cred_data.cloudinary_api_secret +local public_id = cred_data.cloudinary_public_id +local BASE_URL = 'https://api.cloudinary.com/v1_1/'..public_id..'/image' + +local function upload_image(file_url) + local timestamp = os.time() + local signature = sha1('timestamp='..timestamp..api_secret) + local upload_url = BASE_URL..'/upload?api_key='..apikey..'&file='..file_url..'×tamp='..timestamp..'&signature='..signature + local res,code = https.request(upload_url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data.public_id +end + +local function destroy_image(image_id) + local timestamp = os.time() + local signature = sha1('public_id='..image_id..'×tamp='..timestamp..api_secret) + local destroy_url = BASE_URL..'/destroy?api_key='..apikey..'&public_id='..image_id..'×tamp='..timestamp..'&signature='..signature + local res,code = https.request(destroy_url) + if code ~= 200 then print("Lschen fehlgeschlagen") end + local data = json:decode(res) + if data.result == "ok" then + print("Datei von Cloudinary-Server gelscht") + else + print("Lschen fehlgeschlagen") + end +end + +local function run(msg, matches) + if not sha1 then + print('sha1 Library wird zum ersten Mal geladen...') + sha1 = (loadfile "./libs/sha1.lua")() + end + local file_url = matches[1] + local image_id = upload_image(file_url) + local file_url = 'https://res.cloudinary.com/'..public_id..'/image/upload/w_512/'..image_id..'.webp' + local receiver = get_receiver(msg) + send_document_from_url(receiver, file_url, cb_extra) + destroy_image(image_id) +end + +return { + description = "Erstellt einen Sticker on-the-fly.", + usage = "/sticker [Bilder-URL]: Erstelt einen Sticker aus einem Bild", + patterns = { + "^/sticker (https?://[%w-_%.%?%.:/%+=&]+)" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/credentials_manager.lua b/plugins/credentials_manager.lua new file mode 100644 index 0000000..e499134 --- /dev/null +++ b/plugins/credentials_manager.lua @@ -0,0 +1,118 @@ +-- This file is part of the Telegram Bot "Brawlbot" (telegram.me/Brawlbot) by Andreas Bielawski (telegram.me/Brawl) +-- Released unter the MPLv2 + +local hash = "telegram:credentials" + +-- See: http://www.lua.org/pil/19.3.html +function pairsByKeys (t, f) + local a = {} + for n in pairs(t) do table.insert(a, n) end + table.sort(a, f) + local i = 0 -- iterator variable + local iter = function () -- iterator function + i = i + 1 + if a[i] == nil then + return nil + else + return a[i], t[a[i]] + end + end + return iter +end + +function reload_creds() + cred_data = redis:hgetall(hash) +end + +function list_creds() + reload_creds() + if redis:exists("telegram:credentials") == true then + local text = "" + for var, key in pairsByKeys(cred_data) do + text = text..var..' = '..key..'\n' + end + return text + else + create_cred() + return "Es wurden noch keine Logininformationen gespeichert, lege Tabelle an...\nSpeichere Keys mit !creds add [Variable] [Key] ein!" + end +end + +function add_creds(var, key) + print('Saving credential for '..var..' to redis hash '..hash) + redis:hset(hash, var, key) + reload_creds() + return var..' = '..key..'\neingespeichert!' +end + +function del_creds(var) + if redis:hexists(hash, var) == true then + print('Deleting credential for '..var..' from redis hash '..hash) + redis:hdel(hash, var) + reload_creds() + return 'Key von "'..var..'" erfolgreich gelscht!' + else + return 'Du hast keine Logininformationen fr diese Variable eingespeichert.' + end +end + +function rename_creds(var, newvar) + if redis:hexists(hash, var) == true then + local key = redis:hget(hash, var) + if redis:hsetnx(hash, newvar, key) == true then + redis:hdel(hash, var) + reload_creds() + return '"'..var..'" erfolgreich zu "'..newvar..'" umbenannt.' + else + return "Variable konnte nicht umbenannt werden: Zielvariable existiert bereits." + end + else + return 'Die zu umbennende Variable existiert nicht.' + end +end + +function run(msg, matches) + local receiver = get_receiver(msg) + + if not is_sudo(msg) then + return 'Du bist kein Superuser. Dieser Vorfall wird gemeldet!' + end + + if msg.to.type == 'chat' then + return 'Das Plugin solltest du nur per PN nutzen!' + end + + if matches[1] == "!creds" then + return list_creds() + elseif matches[1] == "!creds add" then + local var = string.lower(string.sub(matches[2], 1, 50)) + local key = string.sub(matches[3], 1, 1000) + return add_creds(var, key) + elseif matches[1] == "!creds del" then + local var = string.lower(matches[2]) + return del_creds(var) + elseif matches[1] == "!creds rename" then + local var = string.lower(string.sub(matches[2], 1, 50)) + local newvar = string.lower(string.sub(matches[3], 1, 1000)) + return rename_creds(var, newvar) + end +end + +return { + description = "Loginmanager fr Telegram (nur Superuser)", + usage = { + "/creds: Zeigt alle Logindaten und API-Keys", + "/creds add [Variable] [Key]: Speichert Key mit der Variable ein", + "/creds del [Variable]: Lscht Key mit der Variable", + "/creds rename [Variable] [Neuer Name]: Benennt Variable um, behlt Key bei" + }, + patterns = { + "^(/creds)$", + "^(/creds add) ([^%s]+) (.+)$", + "^(/creds del) (.+)$", + "^(/creds rename) ([^%s]+) (.+)$" + }, + run = run, + privileged = true, + notyping = true +} \ No newline at end of file diff --git a/plugins/curl_head.lua b/plugins/curl_head.lua index ac7b467..5ffe390 100644 --- a/plugins/curl_head.lua +++ b/plugins/curl_head.lua @@ -5,9 +5,12 @@ function run(msg, matches) return 'Vergiss es' end - if string.match(msg.text, '/[Hh][Ee][Aa][Dd]') then + if string.match(msg.text, '[Hh][Ee][Aa][Dd]') then text = run_bash('curl --head --insecure ' .. URL) send_msg(receiver, text, ok_cb, false) + elseif string.match(msg.text, '[Dd][Ii][Gg]') then + text = run_bash('dig ' .. URL .. ' ANY') + send_msg(receiver, text, ok_cb, false) end end @@ -15,6 +18,7 @@ end return { description = "Fhrt Befehle in der Konsole aus", usage = {""}, - patterns = {"^/[Hh][Ee][Aa][Dd] (https?://[%w-_%.%?%.:/%+=&]+)$"}, + patterns = {"^/[Hh][Ee][Aa][Dd] (.*)$","^/[Dd][Ii][Gg] (.*)"}, run = run -} \ No newline at end of file +} +--by Akamaru [https://ponywave.de] \ No newline at end of file diff --git a/plugins/currency.lua b/plugins/currency.lua new file mode 100644 index 0000000..1c4d83a --- /dev/null +++ b/plugins/currency.lua @@ -0,0 +1,64 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://currencyconverter.p.mashape.com' + +local function get_currency_data (from, to, amount) + local apikey = cred_data.x_mashape_key + local url = BASE_URL..'/?from='..from..'&from_amount='..amount..'&to='..to..'&mashape-key='..apikey + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +local function send_currency_data(data, receiver) + if data.error ~= nil then + return + else + local from = data.from + local to = data.to + local from_amount = string.gsub(data.from_amount, "%.", "%,") + local dot_to_amount = round(data.to_amount, 2) + local to_amount = string.gsub(dot_to_amount, "%.", "%,") + local text = from_amount..' '..from..' = '..to_amount..' '..to + send_msg(receiver, text, ok_cb, false) + end +end + +local function run(msg, matches) + local from = string.upper(matches[1]) + if matches[2] == nil then + to = "EUR" + else + to = string.upper(matches[2]) + end + if matches[3] == nil then + amount = 1 + else + amount = string.gsub(matches[3],"%,","%.") + end + if matches[1] == "/eur" then + to = "USD" + from = "EUR" + end + local data = get_currency_data(from, to, amount) + local receiver = get_receiver(msg) + send_currency_data(data, receiver) +end + +return { + description = "Wandelt Geldeinheiten um. ?? ?? ??", + usage = "/money [von] [zu] [Menge]: Wandelt Geldeinheiten um (Symbole: ponywave.de/a/botmoney)", + patterns = { + "^/money ([A-Za-z]+)$", + "^/money ([A-Za-z]+) ([A-Za-z]+)$", + "^(money ([A-Za-z]+) ([A-Za-z]+) (%d+[%d%.,]*)$", + "^(/eur)$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/dailymotion.lua b/plugins/dailymotion.lua new file mode 100644 index 0000000..c8a87d4 --- /dev/null +++ b/plugins/dailymotion.lua @@ -0,0 +1,37 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://api.dailymotion.com' + +function get_dm_data (dm_code) + local url = BASE_URL..'/video/'..dm_code + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +function send_dailymotion_data(data, receiver) + local title = data.title + local channel = data.channel + local text = title..'\nHochgeladen in die Kategorie "'..channel..'"' + send_msg(receiver, text, ok_cb, false) +end + +function run(msg, matches) + local dm_code = matches[1] + local data = get_dm_data(dm_code) + local receiver = get_receiver(msg) + send_dailymotion_data(data, receiver) +end + +return { + description = "Sendet Dailymotion-Info.", + usage = "URL zu Dailymotion-Video", + patterns = {"dailymotion.com/video/([A-Za-z0-9-_-]+)"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/danbooru2.lua b/plugins/danbooru2.lua new file mode 100644 index 0000000..9315483 --- /dev/null +++ b/plugins/danbooru2.lua @@ -0,0 +1,41 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local function get_db(tag) + local BASE_URL = 'http://danbooru.donmai.us' + local url = BASE_URL..'/posts.json?tags='..tag + local b,c = http.request(url) + if c ~= 200 then return nil end + local db = json:decode(b) + -- truly randomize + math.randomseed(os.time()) + -- random max json table size + local i = math.random(#db) + local link_image = BASE_URL..db[i].file_url + return link_image +end + +local function run(msg, matches) + local tag = matches[1] + local tag = string.gsub(tag, " ", '_' ) + local tag = string.gsub(tag, ":", '%%3A' ) + local receiver = get_receiver(msg) + local url = get_db(tag) + if string.ends(url, ".gif") or string.ends(url, ".zip") or string.ends(url, ".webm") then + send_document_from_url(receiver, url) + else + send_photo_from_url(receiver, url, send_title, {receiver, title}) + end + return "Source: "..url +end + +return { + description = "Sendet ein zufälliges Bild von Danbooru.", + usage = {"/danbooru [Tags]","/db [Tags]"}, + patterns = {"^/danbooru (.*)$","^/db (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/derpibooru.lua b/plugins/derpibooru.lua new file mode 100644 index 0000000..f9cbf80 --- /dev/null +++ b/plugins/derpibooru.lua @@ -0,0 +1,45 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local function get_dp(tag) + local BASE_URL = 'https://derpiboo.ru/' + local apikey = "" + + -- Ohne API-Key: Safe for work - mit API-Key: Not safe for work + if apikey == nil then + url = BASE_URL..'/search.json?q='..tag + else + url = BASE_URL..'/search.json?key='..apikey..'&q='..tag + end + + local b,c = https.request(url) + if c ~= 200 then return nil end + local dp = json:decode(b).search + -- truly randomize + math.randomseed(os.time()) + -- random max json table size + local i = math.random(#dp) + local link_image = 'https:'..dp[i].image + source = 'https://derpiboo.ru/'..dp[i].id_number + return link_image +end + +local function run(msg, matches) + local tag = matches[1] + local tag = string.gsub(tag, " ", '+' ) + local receiver = get_receiver(msg) + local url = get_dp(tag) + send_photo_from_url(receiver, url, send_title, {receiver, title}) + return "Source: "..source +end + +return { + description = "Sendet zuflliges Bild von Derpibooru.", + usage = {"/derpibooru [Tags]","/dp [Tags]"}, + patterns = {"^/derpibooru (.*)$","^/dp (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/derpibooru_nsfw.lua b/plugins/derpibooru_nsfw.lua new file mode 100644 index 0000000..9b35c1d --- /dev/null +++ b/plugins/derpibooru_nsfw.lua @@ -0,0 +1,45 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local function get_dp2(tag) + local BASE_URL = 'https://derpiboo.ru/' + local apikey = cred_data.derpibooru_apikey + + -- Ohne API-Key: Safe for work - mit API-Key: Not safe for work + if apikey == nil then + url = BASE_URL..'/search.json?q='..tag + else + url = BASE_URL..'/search.json?key='..apikey..'&q='..tag + end + + local b,c = https.request(url) + if c ~= 200 then return nil end + local dp = json:decode(b).search + -- truly randomize + math.randomseed(os.time()) + -- random max json table size + local i = math.random(#dp) + local link_image = 'https:'..dp[i].image + source = 'https://derpiboo.ru/'..dp[i].id_number + return link_image +end + +local function run(msg, matches) + local tag = matches[1] + local tag = string.gsub(tag, " ", '+' ) + local receiver = get_receiver(msg) + local url = get_dp2(tag) + send_photo_from_url(receiver, url, send_title, {receiver, title}) + return "Source: "..source +end + +return { + description = "Sendet zuflliges Bild von Derpibooru.", + usage = {"/derpibooru2 [Tags]","/dp2 [Tags]"}, + patterns = {"^/derpibooru2 (.*)$","^/dp2 (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/deviantart.lua b/plugins/deviantart.lua new file mode 100644 index 0000000..9463490 --- /dev/null +++ b/plugins/deviantart.lua @@ -0,0 +1,48 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://backend.deviantart.com' + +function get_da_data (da_code) + local url = BASE_URL..'/oembed?url='..da_code + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +function send_da_data(data, receiver) + local title = data.title + local category = data.category + local author_name = data.author_name + local text = title..' von '..author_name..'\n'..category + local image_url = data.fullsize_url + if image_url == nil then + image_url = data.url + end + local cb_extra = { + receiver=receiver, + url=image_url + } + send_msg(receiver, text, send_photo_from_url_callback, cb_extra) +end + +function run(msg, matches) + local da_code = 'http://'..matches[1]..'.deviantart.com/art/'..matches[2] + local data = get_da_data(da_code) + local receiver = get_receiver(msg) + send_da_data(data, receiver) +end + + + +return { + description = "Sendet deviantArt-Info.", + usage = {"deviantArt Link"}, + patterns = {"http://(.*).deviantart.com/art/(.*)"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/dns.lua b/plugins/dns.lua new file mode 100644 index 0000000..3e25f55 --- /dev/null +++ b/plugins/dns.lua @@ -0,0 +1,40 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://jack-dns-tools.p.mashape.com/dnstools.php' + +local function get_domain_data (domain) + local apikey = cred_data.x_mashape_key + local url = BASE_URL..'?_method=DNS2IP&dns='..domain..'&mashape-key='..apikey + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +local function send_domain_data(data, receiver) + if domain == data.ip then + return + else + local text = data.ip + send_msg(receiver, text, ok_cb, false) + end +end + +local function run(msg, matches) + domain = matches[1] + local data = get_domain_data(domain) + local receiver = get_receiver(msg) + send_domain_data(data, receiver) +end + +return { + description = "Löst Domain nach IP auf.", + usage = "/dns [Domain]", + patterns = {"^/dns (.*)$"}, + run = run +} + +end diff --git a/plugins/dogify.lua b/plugins/dogify.lua index 7f84225..71e47f4 100644 --- a/plugins/dogify.lua +++ b/plugins/dogify.lua @@ -15,6 +15,6 @@ end return { description = "Erstelle ein Doge Bild mit Wörtern", usage = {"/dogify das/was/du/willst"}, - patterns = {"^/dogify (.+)$"}, + patterns = {"^/dogify (.+)$","^/doge (.+)$"}, run = run } \ No newline at end of file diff --git a/plugins/dropbox.lua b/plugins/dropbox.lua new file mode 100644 index 0000000..9fdb162 --- /dev/null +++ b/plugins/dropbox.lua @@ -0,0 +1,41 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local function send_dropbox_data(link, receiver) + if string.ends(link, ".png") or string.ends(link, ".jpeg") or string.ends(link, ".jpg") then + send_photo_from_url(receiver, link) + elseif string.ends(link, ".webp") or string.ends(link, ".gif") then + send_document_from_url(receiver, link) + else + send_msg(receiver, link, ok_cb, false) + end +end + +local function run(msg, matches) + local folder = matches[1] + local file = matches[2] + local receiver = get_receiver(msg) + local link = 'https://dl.dropboxusercontent.com/s/'..folder..'/'..file + + local v,code = https.request(link) + if code == 200 then + send_dropbox_data(link, receiver) + else + return nil + end +end + + +return { + description = "Dropbox-Plugin", + usage = { + "Dropbox-URL: Postet Bild oder Direktlink" + }, + patterns = { + "dropbox.com/s/([a-z0-9]+)/([A-Za-z0-9-_-.-.-]+)" + }, + run = run +} +end \ No newline at end of file diff --git a/plugins/e621.lua b/plugins/e621.lua new file mode 100644 index 0000000..41c2005 --- /dev/null +++ b/plugins/e621.lua @@ -0,0 +1,36 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION +-- Modified by Akamaru [https://ponywave.de] + +do + +local function get_e621(tag) + local BASE_URL = 'https://e621.net' + local url = BASE_URL..'/post/index.json?tags='..tag + local b,c = https.request(url) + if c ~= 200 then return nil end + local e621 = json:decode(b) + -- truly randomize + math.randomseed(os.time()) + -- random max json table size + local i = math.random(#e621) + local link_image = e621[i].file_url + return link_image +end + +local function run(msg, matches) + local tag = matches[1] + local receiver = get_receiver(msg) + local url = get_e621(tag) + send_photo_from_url(receiver, url, send_title, {receiver, title}) + return "Source: "..url +end + +return { + description = "Sendet zufälliges Bild von e621.", + usage = {"/e621 [Tags]"}, + patterns = {"^/e621 (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/facebook.lua b/plugins/facebook.lua new file mode 100644 index 0000000..5f4e08a --- /dev/null +++ b/plugins/facebook.lua @@ -0,0 +1,40 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://graph.facebook.com' + +function get_fb_data (fb_code) + local access_token = cred_data.fb_access_token + local url = BASE_URL..'/'..fb_code..'?access_token='..access_token..'&locale=de_DE' + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +function send_fb_data(data, receiver) + local from = data.from.name + local message = data.message + local name = data.name + local link = data.link + local text = from..' hat gepostet:\n'..message..'\n'..name..' '..link + send_msg(receiver, text, ok_cb, false) +end + +function run(msg, matches) + local fb_code = matches[2] + local data = get_fb_data(fb_code) + local receiver = get_receiver(msg) + send_fb_data(data, receiver) +end + +return { + description = "Sendet Facebook-Post.", + usage = {"Link zu einem Facebook-Post"}, + patterns = {"facebook.com/([A-Za-z0-9-_-]+)/posts/([0-9-]+)","facebook.com/permalink.([a-z-]+)%?story_fbid=([0-9-]+)"}, + run = run +} + +end diff --git a/plugins/facebook_photo.lua b/plugins/facebook_photo.lua new file mode 100644 index 0000000..a007728 --- /dev/null +++ b/plugins/facebook_photo.lua @@ -0,0 +1,46 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://graph.facebook.com' + +function get_fbphoto_data (fbphoto_code) + local access_token = cred_data.fb_access_token + local url = BASE_URL..'/'..fbphoto_code..'?access_token='..access_token..'&locale=de_DE' + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +function send_fbphoto_data(data, receiver) + local from = data.from.name + local name = data.name + if name then + text = from..' hat ein Bild gepostet:\n'..name + else + text = from..' hat ein Bild gepostet' + end + local image_url = data.source + local cb_extra = { + receiver=receiver, + url=image_url + } + send_msg(receiver, text, send_photo_from_url_callback, cb_extra) +end + +function run(msg, matches) + local fbphoto_code = matches[3] + local data = get_fbphoto_data(fbphoto_code) + local receiver = get_receiver(msg) + send_fbphoto_data(data, receiver) +end + +return { + description = "Sendet Facebook-Bilder-Post", + usage = "URL zu öffentlichem Facebook-Bild", + patterns = {"facebook.com/([A-Za-z0-9-._-]+)/photos/([A-Za-z0-9-._-]+)/([A-Za-z0-9-._-]+)"}, + run = run +} +end \ No newline at end of file diff --git a/plugins/facebook_video.lua b/plugins/facebook_video.lua new file mode 100644 index 0000000..166d6e1 --- /dev/null +++ b/plugins/facebook_video.lua @@ -0,0 +1,39 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://graph.facebook.com' + +function get_fbvid_data (fbvid_code) + local access_token = cred_data.fb_access_token + local url = BASE_URL..'/'..fbvid_code..'?access_token='..access_token..'&locale=de_DE' + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +function send_fbvid_data(data, receiver) + local from = data.from.name + local description = data.description + local source = data.source + local text = from..' hat ein Video gepostet:\n'..description..'\n'..source + send_msg(receiver, text, ok_cb, false) +end + +function run(msg, matches) + local fbvid_code = matches[1] + local data = get_fbvid_data(fbvid_code) + local receiver = get_receiver(msg) + send_fbvid_data(data, receiver) +end + +return { + description = "Sendet Facebook-Video.", + usage = {"Link zu einem Video auf Facebook"}, + patterns = {"facebook.com/video.php%?v=([0-9-]+)"}, + run = run +} + +end diff --git a/plugins/flickr.lua b/plugins/flickr.lua new file mode 100644 index 0000000..5d485de --- /dev/null +++ b/plugins/flickr.lua @@ -0,0 +1,50 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://api.flickr.com/services/rest' + +function get_flickr_photo_data (photo_id) + local apikey = cred_data.flickr_apikey + local url = BASE_URL..'/?method=flickr.photos.getInfo&api_key='..apikey..'&photo_id='..photo_id..'&format=json&nojsoncallback=1' + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res).photo + return data +end + +function send_flickr_photo_data(data, receiver) + local title = data.title._content + local username = data.owner.username + local taken = data.dates.taken + local views = data.views + if data.usage.candownload == 1 then + local image_url = 'https://farm'..data.farm..'.staticflickr.com/'..data.server..'/'..data.id..'_'..data.originalsecret..'_o_d.'..data.originalformat + local cb_extra = { + receiver=receiver, + url=image_url + } + local text = '"'..title..'", aufgenommen '..taken..' von '..username..' ('..data.views..' Aufrufe)' + send_msg(receiver, text, send_photo_from_url_callback, cb_extra) + else + local text = '"'..title..'", aufgenommen '..taken..' von '..username..' ('..data.views..' Aufrufe)\nBild kann nicht gepostet werden (Keine Berechtigung)' + send_msg(receiver, text, ok_cb, false) + end +end + +function run(msg, matches) + local photo_id = matches[2] + local data = get_flickr_photo_data(photo_id) + local receiver = get_receiver(msg) + send_flickr_photo_data(data, receiver) +end + +return { + description = "Sendet Flickr-Info.", + usage = "Flickr-URL: Sendet Bild von Flickr", + patterns = {"flickr.com/photos/([A-Za-z0-9-_-]+)/([0-9]+)"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/flickr_search.lua b/plugins/flickr_search.lua new file mode 100644 index 0000000..6202d11 --- /dev/null +++ b/plugins/flickr_search.lua @@ -0,0 +1,38 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local function get_flickr(term) + local BASE_URL = 'https://api.flickr.com/services/rest' + local url = BASE_URL..'/?method=flickr.photos.search&api_key=c34eff1b204eaecae0de7141bd60d578&format=json&nojsoncallback=1&privacy_filter=1&extras=url_o&text='..term + local b,c = https.request(url) + if c ~= 200 then return nil end + local photo = json:decode(b).photos.photo + -- truly randomize + math.randomseed(os.time()) + -- random max json table size + local i = math.random(#photo) + local link_image = photo[i].url_o + return link_image +end + +local function run(msg, matches) + local term = matches[1] + local receiver = get_receiver(msg) + local url = get_flickr(term) + if string.ends(url, ".gif") then + send_document_from_url(receiver, url) + else + send_photo_from_url(receiver, url) + end +end + +return { + description = "Sendet zuflliges Bild von Flickr.", + usage = "/flickr [Suchbegriff]: Postet Bild von Flickr", + patterns = {"^/flickr (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/forecast.lua b/plugins/forecast.lua new file mode 100644 index 0000000..8e9ee23 --- /dev/null +++ b/plugins/forecast.lua @@ -0,0 +1,117 @@ +do + +local BASE_URL = "http://api.openweathermap.org/data/2.5/forecast/daily" + +local function get_condition_symbol(weather, n) + if weather.list[n].weather[1].main == 'Clear' then + return ' ☀' + elseif weather.list[n].weather[1].main == 'Clouds' then + return ' ☁☁' + elseif weather.list[n].weather[1].main == 'Rain' then + return ' ☔' + elseif weather.list[n].weather[1].main == 'Thunderstorm' then + return ' ☔☔☔☔' + elseif weather.list[n].weather[1].main == 'Snow' then + return ' ❄️' + else + return '' + end +end + +local function get_temp(weather, n) + local day = string.gsub(round(weather.list[n].temp.day, 1), "%.", "%,") + local night = string.gsub(round(weather.list[n].temp.night, 1), "%.", "%,") + local condition = weather.list[n].weather[1].description + return '☀️ '..day..'°C | 🌙 '..night..'°C | '..condition +end + +local function get_forecast(location, days) + print("Bekomme Wettervorhersage für ", location) + local location = string.gsub(location," ","+") + local url = BASE_URL + local apikey = cred_data.owm_apikey + local url = url..'?q='..location + local url = url..'&lang=de&units=metric&cnt='..days..'&APPID='..apikey + + local b, c, h = http.request(url) + if c ~= 200 then return nil end + + local weather = json:decode(b) + local city = weather.city.name + if weather.city.country == "" then + country = '' + else + country = ' ('..weather.city.country..')' + end + local header = 'Vorhersage für '..city..country..':\n' + + local text = 'Heute: '..get_temp(weather, 1)..get_condition_symbol(weather, 1) + + if days > 1 then + text = text..'\nMorgen: '..get_temp(weather, 2)..get_condition_symbol(weather, 2) + end + if days > 2 then + text = text..'\nÜbermorgen: '..get_temp(weather, 3)..get_condition_symbol(weather, 3) + end + + if days > 3 then + for day in pairs(weather.list) do + if day > 3 then + local actual_day = day-1 + text = text..'\n'..actual_day..' Tage: '..get_temp(weather, day)..get_condition_symbol(weather, day) + end + end + end + + return header..text +end + +local function run(msg, matches) + local user_id = msg.from.id + local city = get_location(user_id) + if not city then city = 'Berlin' end + + if tonumber(matches[1]) then + days = matches[1]+1 + else + days = 4 + end + + if matches[2] then + days = matches[1]+1 + city = matches[2] + end + + if not tonumber(matches[1]) and matches[1] ~= '!/orecast' then + city = matches[1] + end + + if days > 17 then + return 'Wettervorhersagen gehen nur von 1-16 Tagen!' + end + + local text = get_forecast(city, days) + if not text then + text = 'Konnte die Wettervorhersage für diese Stadt nicht bekommen.' + end + return text +end + +return { + description = "Wettervorhersage für deinen oder einen gewählten Ort", + usage = { + "/forecast: Wettervorhersage für deine Stadt (!location set [Ort])", + "/forecast [0-16]: Wettervorhersage für X Tage für deine Stadt (!location set [Ort])", + "/forecast (Stadt): Wettervorhersage für diese Stadt", + "/forecast [0-16] (Stadt): Wettervorhersage für X Tage für diese Stadt" + }, + patterns = { + "^/forecast$", + "^/forecast (%d+) (.*)$", + "^/forecast (%d+)", + "^/forecast (.*)$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/ftp.lua b/plugins/ftp.lua index ac1353a..75b7242 100644 --- a/plugins/ftp.lua +++ b/plugins/ftp.lua @@ -1,7 +1,5 @@ do -local ftp = (loadfile "./libs/ftp.lua")() - local function send_ftp_data (text_file, receiver) local BASE_URL = cred_data.ftp_site local username = cred_data.ftp_username @@ -16,6 +14,10 @@ local function send_ftp_data (text_file, receiver) end local function run(msg, matches) + if not ftp then + print('ftp Library wird zum ersten Mal geladen...') + ftp = (loadfile "./libs/ftp.lua")() + end local text_file = matches[1] local receiver = get_receiver(msg) send_ftp_data(text_file, receiver) diff --git a/plugins/gamesdb.lua b/plugins/gamesdb.lua new file mode 100644 index 0000000..6b44f17 --- /dev/null +++ b/plugins/gamesdb.lua @@ -0,0 +1,139 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local xml = require("xml") + +local BASE_URL = 'http://thegamesdb.net/api' + +local makeOurDate = function(dateString) + local pattern = "(%d+)%/(%d+)%/(%d+)" + local month, day, year = dateString:match(pattern) + return day..'.'..month..'.'..year +end + +local function get_game_id(game) + local url = BASE_URL..'/GetGamesList.php?name='..game + local res,code = http.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local result = xml.load(res) + if xml.find(result, 'id') then + local game = xml.find(result, 'id')[1] + return game + else + return nil + end +end + +local function send_game_photo(result, receiver) + local BASE_URL = xml.find(result, 'baseImgUrl')[1] + local images = {} + + --[[if xml.find(result, 'fanart') then + local fanart = xml.find(result, 'fanart')[1] + local fanrt_url = BASE_URL..fanart[1] + table.insert(images, fanrt_url) + end]] + + if xml.find(result, 'boxart', 'side', 'front') then + local boxart = xml.find(result, 'boxart', 'side', 'front')[1] + local boxart_url = BASE_URL..boxart + table.insert(images, boxart_url) + end + + local i = 0 + for k, v in pairs(images) do + i = i+1 + local file = download_to_file(v, 'game'..i..'.jpg') + send_photo(receiver, file, ok_cb, false) + end +end + +local function send_game_data(game_id, receiver) + local url = BASE_URL..'/GetGame.php?id='..game_id + local res,code = http.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local result = xml.load(res) + + local title = xml.find(result, 'GameTitle')[1] + local platform = xml.find(result, 'Platform')[1] + + if xml.find(result, 'ReleaseDate') then + date = makeOurDate(xml.find(result, 'ReleaseDate')[1]) + else + date = '' + end + + if xml.find(result, 'Overview') then + desc = '\n'..string.sub(xml.find(result, 'Overview')[1], 1, 200) .. '...' + else + desc = '' + end + + if xml.find(result, 'Genres') then + local genres = xml.find(result, 'Genres') + local genre_count = tablelength(genres)-1 + if genre_count == 1 then + genre = '\nGenre: '..genres[1][1] + else + local genre_loop = '\nGenres: ' + for v in pairs(genres) do + if v == 'xml' then break; end + if v < genre_count then + genre_loop = genre_loop..genres[v][1]..', ' + else + genre_loop = genre_loop..genres[v][1] + end + end + genre = genre_loop + end + else + genre = '' + end + + if xml.find(result, 'Players') then + players = '\nSpieler: '..xml.find(result, 'Players')[1] + else + players = '' + end + + if xml.find(result, 'Youtube') then + video = '\nVideo: '..xml.find(result, 'Youtube')[1] + else + video = '' + end + + if xml.find(result, 'Publisher') then + publisher = '\nPublisher: '..xml.find(result, 'Publisher')[1] + else + publisher = '' + end + + local text = 'Titel: '..title..'\nPlattform: '..platform..'\nRelease: '..date..genre..players..publisher..video..'\nBeschreibung: '..desc + send_msg(receiver, text, ok_cb, false) + + if xml.find(result, 'fanrt') or xml.find(result, 'boxart') then + send_game_photo(result, receiver) + end +end + +local function run(msg, matches) + local game = URL.escape(matches[1]) + local receiver = get_receiver(msg) + local game_id = get_game_id(game) + if not game_id then + return "Spiel nicht gefunden!" + else + send_game_data(game_id, receiver) + end +end + +return { + description = "Sendet Infos zu einem Spiel.", + usage = "/game [Spiel]: Sendet Infos zum Spiel", + patterns = {"^/game (.+)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/gay.lua b/plugins/gay.lua index 7dafbb0..9954a30 100644 --- a/plugins/gay.lua +++ b/plugins/gay.lua @@ -22,30 +22,45 @@ function getGay(text) end -- Random image from table - local i = math.random(#data.results) - return data.results[i].url + -- local i = math.random(#data.results) + -- return data.results[i].url + return data.results end function run(msg, matches) local receiver = get_receiver(msg) local text = matches[1] - local url = getGay(text) - if not url then + local results = getGay(text) + if not results then return "Kein Bild gefunden." end + local i = math.random(#results) + local url = nil; - if string.ends(url, ".svg") then - return "Fehler beim laden des Bildes." + local failed = true + local nofTries = 0 + while failed and nofTries < #results do + url = results[i].url; + print("Bilder-URL: ", url) + + if string.ends(url, ".gif") then + failed = not send_document_from_url(receiver, url, nil, nil, true) + elseif string.ends(url, ".jpg") or string.ends(url, ".jpeg") or string.ends(url, ".png") then + failed = not send_photo_from_url(receiver, url, nil, nil, true) + end + + nofTries = nofTries + 1 + i = i+1 + if i > #results then + i = 1 + end end - - print("Bilder-URL: ", url) - if string.ends(url, ".gif") then - send_document_from_url(receiver, url) - return "Source: "..url + + if failed then + return "Fehler beim Laden des Bildes." else - send_photo_from_url(receiver, url) - return "Source: "..url + return "Source: "..url end end diff --git a/plugins/gdrive.lua b/plugins/gdrive.lua new file mode 100644 index 0000000..b053262 --- /dev/null +++ b/plugins/gdrive.lua @@ -0,0 +1,82 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://www.googleapis.com/drive/v2' + +local function get_drive_document_data (docid) + local apikey = cred_data.google_apikey + local url = BASE_URL..'/files/'..docid..'?key='..apikey..'&fields=id,title,mimeType,ownerNames,exportLinks,fileExtension' + local res,code = https.request(url) + local res = string.gsub(res, 'image/', '') + local res = string.gsub(res, 'application/', '') + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +local function send_drive_document_data(data, receiver) + local title = data.title + local mimetype = data.mimeType + local id = data.id + local owner = data.ownerNames[1] + local text = '"'..title..'", freigegeben von '..owner + if data.exportLinks then + if data.exportLinks.png then + local image_url = data.exportLinks.png + local cb_extra = { + receiver=receiver, + url=image_url + } + send_msg(receiver, text, send_photo_from_url_callback, cb_extra) + else + local pdf_url = data.exportLinks.pdf + send_msg(receiver, text, ok_cb, false) + send_document_from_url(receiver, pdf_url) + end + else + local get_file_url = 'https://drive.google.com/uc?id='..id + local ext = data.fileExtension + if mimetype == "png" or mimetype == "jpg" or mimetype == "jpeg" or mimetype == "gif" or mimetype == "webp" then + local respbody = {} + local options = { + url = get_file_url, + sink = ltn12.sink.table(respbody), + redirect = false + } + local response = {https.request(options)} -- luasec doesn't support 302 redirects, so we must contact gdrive again + local code = response[2] + local headers = response[3] + local file_url = headers.location + if ext == "jpg" or ext == "jpeg" or ext == "png" then + send_photo_from_url(receiver, file_url) + else + send_document_from_url(receiver, file_url) + end + else + local text = '"'..title..'", freigegeben von '..owner..'\nDirektlink: '..get_file_url + send_msg(receiver, text, ok_cb, false) + end + end +end + +local function run(msg, matches) + local docid = matches[2] + local data = get_drive_document_data(docid) + local receiver = get_receiver(msg) + send_drive_document_data(data, receiver) +end + +return { + description = "Sendet Google-Drive-Info und PDF", + usage = "URL zu Google-Drive-Dateien", + patterns = { + "docs.google.com/(.*)/d/([A-Za-z0-9-_-]+)", + "drive.google.com/(.*)/d/([A-Za-z0-9-_-]+)", + "drive.google.com/(open)%?id=([A-Za-z0-9-_-]+)" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/gender.lua b/plugins/gender.lua new file mode 100644 index 0000000..cb58c1f --- /dev/null +++ b/plugins/gender.lua @@ -0,0 +1,46 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://gender-api.com/get' + +function get_gender_data (name) + local apikey = cred_data.gender_apikey + local url = BASE_URL..'?name='..name..'&key='..apikey + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +function send_gender_data(data, receiver) + if data.gender == "female" then + gender = 'weiblich' + end + if data.gender == "male" then + gender = 'männlich' + end + if data.gender == "unknown" then + gender = 'unbekanntem Geschlechts' + end + local accuracy = data.accuracy + local text = name..' ist zu '..accuracy..'% '..gender + send_msg(receiver, text, ok_cb, false) +end + +function run(msg, matches) + name = matches[1] + local data = get_gender_data(name) + local receiver = get_receiver(msg) + send_gender_data(data, receiver) +end + +return { + description = "Sendet Geschlecht", + usage = {"/geschlecht [Name]","/gender [Name]"}, + patterns = {"^/geschlecht (.*)$","^/gender (.*)$"}, + run = run +} + +end diff --git a/plugins/get.lua b/plugins/get.lua index cba79a2..f00a4cb 100644 --- a/plugins/get.lua +++ b/plugins/get.lua @@ -1,37 +1,34 @@ -local function get_variables_hash(msg) - if msg.to.type == 'chat' then - return 'chat:'..msg.to.id..':variables' - end - if msg.to.type == 'user' then - return 'user:'..msg.from.id..':variables' - end -end - -local function list_variables(msg) - local hash = get_variables_hash(msg) - - if hash then - local names = redis:hkeys(hash) - local text = '' - for i=1, #names do - text = text..names[i]..'\n' - end - return text - end -end - local function get_value(msg, var_name) - local hash = get_variables_hash(msg) + local hash = get_redis_hash(msg, 'variables') if hash then local value = redis:hget(hash, var_name) if not value then - return'Not found, use "/get" to list variables' + return'Nicht gefunden, benutze "!get", um alle Variablen aufzulisten.' else - return var_name..' => '..value + return var_name..' = '..value end end end +local function list_variables(msg) + local hash = get_redis_hash(msg, 'variables') + + if hash then + print('Getting variable from redis hash '..hash) + local names = redis:hkeys(hash) + local text = '' + for i=1, #names do + variables = get_value(msg, names[i]) + text = text..variables.."\n" + end + if text == '' or text == nil then + return 'Keine Variablen vorhanden!' + else + return text + end + end +end + local function run(msg, matches) if matches[2] then return get_value(msg, matches[2]) @@ -41,9 +38,14 @@ local function run(msg, matches) end return { - description = "Bekommt Variable, die mit /set gesetzt wurde", - usage = {"/get (Variable)"}, - patterns = {"^/get (%a+)$","^/get$"}, - run = run, - pre_process = lex -} + description = "Bekommt Variablen, die mit !set gesetzt wurden", + usage = { + "/get: Gibt alle Variablen aus", + "/get (Variable): Gibt die Variable aus." + }, + patterns = { + "^(/get) (.+)$", + "^/get$" + }, + run = run +} \ No newline at end of file diff --git a/plugins/get_data.lua b/plugins/get_data.lua new file mode 100644 index 0000000..2abd563 --- /dev/null +++ b/plugins/get_data.lua @@ -0,0 +1,21 @@ +local function run(msg, matches) + local receiver = get_receiver(msg) + local url = matches[1] + if string.match(msg.text, "[Gg][Ee][Tt][Dd][Aa][Tt][Aa]") or string.match(msg.text, "[Ww][Gg][Ee][Tt]") then + send_document_from_url(receiver, url) + elseif string.match(msg.text, "[Gg][Ee][Tt][Ii][Mm][Gg]") then + send_photo_from_url(receiver, url) + end +end + +return { + description = "Ein plugin, um Bilder oder Dateien zu downloaden", + usage = {"/getdata [URL]", + "/getimg [URL]"}, + patterns = {"^/getdata (.*)$", + "/[Ww][Gg][Ee][Tt]", + "^/getimg (.*)$" + }, + run = run +} +--by Akamaru [https://ponywave.de] \ No newline at end of file diff --git a/plugins/get_txt.lua b/plugins/get_txt.lua new file mode 100644 index 0000000..873ab32 --- /dev/null +++ b/plugins/get_txt.lua @@ -0,0 +1,13 @@ +local function run(msg, matches) + local res,code = http.request(matches[1]..'.txt') + if code ~= 200 then return nil end + return res +end + + +return { + description = "", + usage = "", + patterns = {"^(.*).txt$"}, + run = run +} \ No newline at end of file diff --git a/plugins/gfycat.lua b/plugins/gfycat.lua new file mode 100644 index 0000000..2d9a62d --- /dev/null +++ b/plugins/gfycat.lua @@ -0,0 +1,46 @@ +do + +local function send_gfycat_gif(name, receiver) + local BASE_URL = "https://gfycat.com" + local url = BASE_URL..'/cajax/get/'..name + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res).gfyItem + local file = download_to_file(data.gifUrl) + local cb_extra = {file_path=file} + if file == nil then + send_msg(receiver, 'Fehler beim Herunterladen von '..name, ok_cb, false) + else + send_document(receiver, file, rmtmp_cb, cb_extra) + end +end + +local function send_gfycat_thumb(name, receiver) + local BASE_URL = "https://thumbs.gfycat.com" + local url = BASE_URL..'/'..name..'-poster.jpg' + local file = download_to_file(url) + local cb_extra = {file_path=file} + if file == nil then + print('Fehler beim Herunterladen des Thumbnails von '..name) + else + send_photo(receiver, file, rmtmp_cb, cb_extra) + end +end + +local function run(msg, matches) + local name = matches[1] + local receiver = get_receiver(msg) + send_gfycat_gif(name, receiver) + send_gfycat_thumb(name, receiver) +end + +return { + description = "Postet Gfycat-Video", + usage = "gfycat-Link: Postet Gfycat-Video", + patterns = { + "gfycat.com/([A-Za-z0-9-_-]+)", + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/github.lua b/plugins/github.lua new file mode 100644 index 0000000..8f146d5 --- /dev/null +++ b/plugins/github.lua @@ -0,0 +1,76 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://api.github.com' + +function get_gh_data() + if gh_commit_sha == nil then + url = BASE_URL..'/repos/'..gh_code + else + url = BASE_URL..'/repos/'..gh_code..'/git/commits/'..gh_commit_sha + end + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +function send_github_data(data, receiver) + local name = data.name + local description = data.description + local owner = data.owner.login + local clone_url = data.clone_url + if data.language == nil or data.language == "" then + language = '' + else + language = '\nSprache: '..data.language + end + if data.open_issues_count == 0 then + issues = '' + else + issues = '\nOffene Bugreports: '..data.open_issues_count + end + if data.homepage == nil or data.homepage == "" then + homepage = '' + else + homepage = '\nHomepage: '..data.homepage + end + local text = name..' von '..owner..'\n'..description..'\ngit clone '..clone_url..language..issues..homepage + send_msg(receiver, text, ok_cb, false) +end + +function send_gh_commit_data(data, receiver) + local committer = data.committer.name + local message = data.message + local text = gh_code..'@'..gh_commit_sha..' von '..committer..':\n'..message + send_msg(receiver, text, ok_cb, false) +end + +function run(msg, matches) + gh_code = matches[1]..'/'..matches[2] + gh_commit_sha = matches[3] + local data = get_gh_data() + local receiver = get_receiver(msg) + if not gh_commit_sha then + send_github_data(data, receiver) + else + send_gh_commit_data(data, receiver) + end +end + +return { + description = "Sendet GitHub-Info.", + usage = { + "Link zu GitHub-Repo", + "Link zu GitHub-Commit" + }, + patterns = { + "github.com/([A-Za-z0-9-_-.-.]+)/([A-Za-z0-9-_-.-.]+)/commit/([a-z0-9-]+)", + "github.com/([A-Za-z0-9-_-.-.]+)/([A-Za-z0-9-_-.-.]+)/?$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/googl.lua b/plugins/googl.lua new file mode 100644 index 0000000..94b1871 --- /dev/null +++ b/plugins/googl.lua @@ -0,0 +1,38 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://www.googleapis.com/urlshortener/v1' + +local function get_shortlink_data (shorturl) + local apikey = cred_data.google_apikey + local url = BASE_URL..'/url?key='..apikey..'&shortUrl=http://goo.gl/'..shorturl..'&projection=FULL' + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +local function send_shortlink_data(data, receiver) + local longUrl = data.longUrl + local shortUrlClicks = data.analytics.allTime.shortUrlClicks + local text = longUrl..'\n'..shortUrlClicks..' mal geklickt' + send_msg(receiver, text, ok_cb, false) +end + +local function run(msg, matches) + local shorturl = matches[1] + local data = get_shortlink_data(shorturl) + local receiver = get_receiver(msg) + send_shortlink_data(data, receiver) +end + +return { + description = "Sendet Goo.gl-Info.", + usage = {"goo.gl URL"}, + patterns = {"goo.gl/([A-Za-z0-9-_-]+)"}, + run = run +} + +end diff --git a/plugins/google.lua b/plugins/google.lua index 70c1092..f159fcd 100644 --- a/plugins/google.lua +++ b/plugins/google.lua @@ -1,38 +1,53 @@ -local function googlethat(query) - local api = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&safe=active&hl=de&" +function googlethat(query) + local BASE_URL = 'https://www.googleapis.com/customsearch/v1' + local apikey = cred_data.google_apikey + local cseid = cred_data.google_cse_id + local number = 5 -- Set number of results + + local api = BASE_URL.."/?key="..apikey.."&cx="..cseid.."&gl=de&num="..number.."&safe=medium&fields=searchInformation%28formattedSearchTime,formattedTotalResults%29,items%28title,link%29&" local parameters = "q=".. (URL.escape(query) or "") -- Do the request local res, code = https.request(api..parameters) - if code ~=200 then return nil end + if code ~=200 then return nil end local data = json:decode(res) + if data.searchInformation.formattedTotalResults == "0" then return nil end - local results = {} - for key,result in ipairs(data.responseData.results) do + local results={} + for key,result in ipairs(data.items) do table.insert(results, { - result.titleNoFormatting, - result.unescapedUrl or result.url + result.title, + result.link }) end - return results + + local stats = data.searchInformation.formattedTotalResults..' Ergebnisse, gefunden in '..data.searchInformation.formattedSearchTime..' Sekunden' + return results, stats end -local function stringlinks(results) +function stringlinks(results, stats) local stringresults="" for key,val in ipairs(results) do stringresults=stringresults..val[1].." - "..val[2].."\n" end - return stringresults + return stringresults..stats end -local function run(msg, matches) - local results = googlethat(matches[1]) - return stringlinks(results) +function run(msg, matches) + local results, stats = googlethat(matches[1]) + if results == nil then + return 'Nichts gefunden!' + else + return stringlinks(results, stats) + end end return { - description = "Durchsucht Google und sendet die ersten 5 Ergebnisse", - usage = {"/google [Begriff]","/Google [Begriff]"}, - patterns = {"^/google (.*)$","^/Google (.*)$"}, - run = run + description = "Durchsucht Google", + usage = "/google [Suchbegriff]: Durchsucht Google", + patterns = { + "^/[Gg][Oo][Oo][Gg][Ll][Ee] (.*)$", + "^%.[Gg][Oo][Oo][Gg][Ll][Ee] (.*)$" + }, + run = run } \ No newline at end of file diff --git a/plugins/google_books.lua b/plugins/google_books.lua new file mode 100644 index 0000000..147daab --- /dev/null +++ b/plugins/google_books.lua @@ -0,0 +1,70 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://www.googleapis.com/books/v1' + + +local function get_books_data (term) + local url = BASE_URL..'/volumes?q='..term..'&maxResults=3&fields=totalItems,items%28volumeInfo%28title,authors,publisher,publishedDate,pageCount,canonicalVolumeLink%29,saleInfo%28country,listPrice%29%29' + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + return data +end + +local function send_books_data(data, receiver) + local text = "" + for book in pairs(data.items) do + text = text..'"'..data.items[book].volumeInfo.title..'"' + + if data.items[book].volumeInfo.publisher == nil and data.items[book].volumeInfo.pageCount ~= nil then + text = text..'\n'..data.items[book].volumeInfo.authors[1]..', ' + else + text = text..'\n'..data.items[book].volumeInfo.authors[1]..'' + end + + if data.items[book].volumeInfo.authors[1] == data.items[book].volumeInfo.publisher then data.items[book].volumeInfo.publisher = nil end + + if data.items[book].volumeInfo.publisher then + if data.items[book].volumeInfo.pageCount then + text = text..', '..data.items[book].volumeInfo.publisher..', ' + else + text = text..', '..data.items[book].volumeInfo.publisher + end + end + + if data.items[book].volumeInfo.pageCount then + text = text..data.items[book].volumeInfo.pageCount..' Seiten' + end + + if data.items[book].volumeInfo.publishedDate then + text = text..', erschienen '..data.items[book].volumeInfo.publishedDate + end + + if data.items[book].saleInfo.listPrice then + text = text..'\nPreis: '..data.items[book].saleInfo.listPrice.amount..' '..data.items[book].saleInfo.listPrice.currencyCode + end + + text = text..'\n'..data.items[book].volumeInfo.canonicalVolumeLink + text = text..'\n\n' + end + send_large_msg(receiver, text, ok_cb, false) +end + +local function run(msg, matches) + local term = URL.escape(matches[1]) + local data = get_books_data(term) + local receiver = get_receiver(msg) + send_books_data(data, receiver) +end + +return { + description = "Sucht nach Bchern in Google Books.", + usage = "/books [Suchbegriff]: Sucht nach Bchern in Google Books", + patterns = {"^/books (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/gronkh_soundboard.lua b/plugins/gronkh_soundboard.lua new file mode 100644 index 0000000..e2d00bc --- /dev/null +++ b/plugins/gronkh_soundboard.lua @@ -0,0 +1,33 @@ +#Sounds from http://xufox.bplaced.net/GronkhSoundboard/ + +do + +local function run(msg, matches) + local receiver = get_receiver(msg) + local url = "http://code.ponywave.de/workspace/mikubot/gronkhsounds/"..matches[1]..".mp3" + local file = download_to_file(url) + local cb_extra = {file_path=file} + + if string.match(msg.text, "[Ll][Ii][Ss][Tt][Ee]") then + return "PonyWave.de/a/gronkhsounds" + end + + if not file then + return "Nichts gefunden!" + else + print('Sende Sound') + send_document(receiver, file, rmtmp_cb, cb_extra) + end + +end + +return { + description = "Gronkh Soundboard", + usage = {"/gronkh [Name]"}, + patterns = { + "^/gronkh (.*)$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/help.lua b/plugins/help.lua index 15249ed..dceecba 100644 --- a/plugins/help.lua +++ b/plugins/help.lua @@ -39,7 +39,7 @@ do end else plugin = plugins[name] - if not plugin then return nil end + if not plugin then return 'Dieses Plugin existiert nicht.' end end local text = "" diff --git a/plugins/images.lua b/plugins/images.lua index 9541474..c988997 100644 --- a/plugins/images.lua +++ b/plugins/images.lua @@ -10,7 +10,7 @@ end return { description = "Wenn ein Link zu einem Bild gesendet wird, läd und sendet der Bot das Bild.", usage = {"Link zum Bild"}, - patterns = {"^(https?://[%w-_%.%?%.:/%+=&]+%.png)$","^(https?://[%w-_%.%?%.:/%+=&]+%.jpg)$","^(https?://[%w-_%.%?%.:/%+=&]+%.jpeg)$",}, + patterns = {"^(https?://[%w-_%.%?%.:/%+=&]+%.png)","^(https?://[%w-_%.%?%.:/%+=&]+%.jpg)","^(https?://[%w-_%.%?%.:/%+=&]+%.jpeg)",}, run = run } diff --git a/plugins/imdb.lua b/plugins/imdb.lua index 1387e4d..d6eaefe 100644 --- a/plugins/imdb.lua +++ b/plugins/imdb.lua @@ -1,10 +1,14 @@ do -local BASE_URL = 'http://www.imdbapi.com' +local BASE_URL = 'https://www.omdbapi.com' -function get_imdb_data (movie) - local url = BASE_URL..'/?t='..movie - local res,code = http.request(url) +function get_imdb_data (movie, id) + if id then + url = BASE_URL..'/?i=tt'..movie + else + url = BASE_URL..'/?t='..movie + end + local res,code = https.request(url) if code ~= 200 then return "HTTP-FEHLER" end local data = json:decode(res) return data @@ -17,6 +21,7 @@ function send_imdb_data(data, receiver) else local title = data.Title local release = data.Released + if data.Type ~= "movie" then Type = '\nTyp: '..data.Type else Type = '' end if data.Runtime ~= "N/A" then runtime = '\nLaufzeit: '..data.Runtime else runtime = '' end if data.Genre ~= "N/A" then genre = '\nGenre: '..data.Genre else genre = '' end local director = data.Director @@ -33,8 +38,8 @@ function send_imdb_data(data, receiver) score = '' end local link = 'http://imdb.com/title/'..data.imdbID - local text = title..'\nErscheinungsdatm: '..release..runtime..genre..'\nDirector: '..director..writer..'\nSchauspieler: '..actors..plot..score..'\n-- '..link - if data.Poster ~= "N/A" then + local text = title..Type..'\nErscheinungsdatm: '..release..runtime..genre..'\nDirector: '..director..writer..'\nSchauspieler: '..actors..plot..score..'\n-- '..link + if data.Poster ~= "N/A" then local image_url = data.Poster local cb_extra = { receiver=receiver, @@ -48,8 +53,13 @@ function send_imdb_data(data, receiver) end function run(msg, matches) - local movie = matches[1]:gsub(' ', '+') - local data = get_imdb_data(movie) + if matches[2] then + local movie = matches[2] + data = get_imdb_data(movie, true) + else + local movie = url_encode(matches[1]:gsub(' ', '+')) + data = get_imdb_data(movie, nil) + end local receiver = get_receiver(msg) send_imdb_data(data, receiver) end @@ -57,7 +67,9 @@ end return { description = "Zeigt Info zu einem Film (von IMDB, englisch)", usage = "/imdb [Film]: Zeigt Info zu Film", - patterns = {"^/imdb (.+)"}, + patterns = {"^/imdb (.+)", + "imdb.com/title/(tt)(%d+[%d%.,]*)" + }, run = run } diff --git a/plugins/img_google.lua b/plugins/img_google.lua index 750629e..dcc654a 100644 --- a/plugins/img_google.lua +++ b/plugins/img_google.lua @@ -1,6 +1,8 @@ do -function getGoogleImage(text) +local _blacklist + +local function getGoogleImage(text) local text = URL.escape(text) local api = "https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&safe=active&q=" local res, code = http.request(api..text) @@ -21,38 +23,144 @@ function getGoogleImage(text) return nil end - -- Random image from table - local i = math.random(#data.results) - return data.results[i].url + return data.results end -function run(msg, matches) +local function is_blacklisted(msg) + local var = false + for v,word in pairs(_blacklist) do + if string.find(string.lower(msg), string.lower(word)) then + print("Wort steht auf der Blacklist!") + var = true + break + end + end + return var +end + +local function show_blacklist() + if not _blacklist[1] then + return "Keine Wörter geblacklisted!\nBlackliste welche mit /imgblacklist add [Wort]" + else + local sort_alph = function( a,b ) return a < b end + table.sort( _blacklist, sort_alph ) + local blacklist = "Folgende Wörter stehen auf der Blacklist:\n" + for v,word in pairs(_blacklist) do + blacklist = blacklist..'- '..word..'\n' + end + return blacklist + end +end + +local function add_blacklist() + print('Blacklisting '..word..' - saving to redis set telegram:img_blacklist') + if redis:sismember("telegram:img_blacklist", word) == true then + return '"'..word..'" steht schon auf der Blacklist.' + else + redis:sadd("telegram:img_blacklist", word) + return '"'..word..'" blacklisted!' + end +end + +local function remove_blacklist() + print('De-blacklisting '..word..' - removing from redis set telegram:img_blacklist') + if redis:sismember("telegram:img_blacklist", word) == true then + redis:srem("telegram:img_blacklist", word) + return '"'..word..'" erfolgreich von der Blacklist gelöscht!' + else + return '"'..word..'" steht nicht auf der Blacklist.' + end +end + +local function run(msg, matches) local receiver = get_receiver(msg) local text = matches[1] - local url = getGoogleImage(text) + if matches[2] then word = string.lower(matches[2]) end - if not url then - return "Kein Bild gefunden." + _blacklist = redis:smembers("telegram:img_blacklist") + + if text == "/imgblacklist show" then + if is_sudo(msg) then + return show_blacklist() + else + return "Du bist kein Superuser. Dieser Vorfall wird gemeldet!" + end end - if string.ends(url, ".svg") then - return "Fehler beim laden des Bildes." + if text == "/imgblacklist add" and word == nil then + return "Benutzung: /imgblacklist add [Wort]" + elseif text == "/imgblacklist add" and word then + if is_sudo(msg) then + return add_blacklist() + else + return "Du bist kein Superuser. Dieser Vorfall wird gemeldet!" + end end - - print("Bilder-URL: ", url) - if string.ends(url, ".gif") then - send_document_from_url(receiver, url) - return "Source: "..url + + if text == "/imgblacklist del" and word == nil then + return "Benutzung: /imgblacklist del [Wort]" + elseif text == "/imgblacklist del" and word then + if is_sudo(msg) then + return remove_blacklist() + else + return "Du bist kein Superuser. Dieser Vorfall wird gemeldet!" + end + end + + print ('Checking if search contains blacklisted words: '..text) + if is_blacklisted(text) then + return "Vergiss es ._." + end + + local results = getGoogleImage(text) + if not results then + return "Kein Bild gefunden!" + end + + -- Random image from table + local i = math.random(#results) + local url = nil; + + local failed = true + local nofTries = 0 + while failed and nofTries < #results do + url = results[i].url; + print("Bilder-URL: ", url) + + if string.ends(url, ".gif") then + failed = not send_document_from_url(receiver, url, nil, nil, true) + elseif string.ends(url, ".jpg") or string.ends(url, ".jpeg") or string.ends(url, ".png") then + failed = not send_photo_from_url(receiver, url, nil, nil, true) + end + + nofTries = nofTries + 1 + i = i+1 + if i > #results then + i = 1 + end + end + + if failed then + return "Fehler beim Herunterladen eines Bildes." else - send_photo_from_url(receiver, url) - return "Source: "..url + return "Source: "..url end end return { - description = "Sucht Bild mit Google-API und versendet es (SafeSearch aktiv)", - usage = {"/img [Suchbegriff]"}, - patterns = {"^/img (.*)$"}, - run = run + description = "Sucht Bild mit Google-API und versendet es (SafeSearch aktiv)", + usage = { + "/img [Suchbegriff]", + "/imgblacklist show: Zeigt Blacklist (nur Superuser)", + "/imgblacklist add [Wort]: Fügt Wort der Blacklist hinzu (nur Superuser)", + "/imgblacklist del [Wort]: Entfernt Wort aus der Blacklist (nur Superuser)" + }, + patterns = { + "^/img (.*)$", + "^(/imgblacklist show)$", + "^(/imgblacklist add) (.*)$", + "^(/imgblacklist del) (.*)$" + }, + run = run } -end +end \ No newline at end of file diff --git a/plugins/img_google_nsfw.lua b/plugins/img_google_nsfw.lua index 182e4fe..7732690 100644 --- a/plugins/img_google_nsfw.lua +++ b/plugins/img_google_nsfw.lua @@ -1,6 +1,8 @@ do -function getGoogleImage2(text) +local _blacklist + +local function getNSFWImage(text) local text = URL.escape(text) local api = "https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&q=" local res, code = http.request(api..text) @@ -21,38 +23,139 @@ function getGoogleImage2(text) return nil end - -- Random image from table - local i = math.random(#data.results) - return data.results[i].url + return data.results end -function run(msg, matches) +local function is_blacklisted(msg) + local var = false + for v,word in pairs(_blacklist) do + if string.find(string.lower(msg), string.lower(word)) then + print("Wort steht auf der Blacklist!") + var = true + break + end + end + return var +end + +local function show_blacklist() + if not _blacklist[1] then + return "Keine Wörter geblacklisted!\nBlackliste welche mit /imgblacklist add [Wort]" + else + local sort_alph = function( a,b ) return a < b end + table.sort( _blacklist, sort_alph ) + local blacklist = "Folgende Wörter stehen auf der Blacklist:\n" + for v,word in pairs(_blacklist) do + blacklist = blacklist..'- '..word..'\n' + end + return blacklist + end +end + +local function add_blacklist() + print('Blacklisting '..word..' - saving to redis set telegram:img_blacklist') + if redis:sismember("telegram:img_blacklist", word) == true then + return '"'..word..'" steht schon auf der Blacklist.' + else + redis:sadd("telegram:img_blacklist", word) + return '"'..word..'" blacklisted!' + end +end + +local function remove_blacklist() + print('De-blacklisting '..word..' - removing from redis set telegram:img_blacklist') + if redis:sismember("telegram:img_blacklist", word) == true then + redis:srem("telegram:img_blacklist", word) + return '"'..word..'" erfolgreich von der Blacklist gelöscht!' + else + return '"'..word..'" steht nicht auf der Blacklist.' + end +end + +local function run(msg, matches) local receiver = get_receiver(msg) local text = matches[1] - local url = getGoogleImage2(text) + if matches[2] then word = string.lower(matches[2]) end - if not url then - return "Kein Bild gefunden." + _blacklist = redis:smembers("telegram:img_blacklist") + + if text == "/imgblacklist show" then + if is_sudo(msg) then + return show_blacklist() + else + return "Du bist kein Superuser. Dieser Vorfall wird gemeldet!" + end end - if string.ends(url, ".svg") then - return "Fehler beim laden des Bildes." + if text == "/imgblacklist add" and word == nil then + return "Benutzung: /imgblacklist add [Wort]" + elseif text == "/imgblacklist add" and word then + if is_sudo(msg) then + return add_blacklist() + else + return "Du bist kein Superuser. Dieser Vorfall wird gemeldet!" + end end - - print("Bilder-URL: ", url) - if string.ends(url, ".gif") then - send_document_from_url(receiver, url) - return "Source: "..url + + if text == "/imgblacklist del" and word == nil then + return "Benutzung: /imgblacklist del [Wort]" + elseif text == "/imgblacklist del" and word then + if is_sudo(msg) then + return remove_blacklist() + else + return "Du bist kein Superuser. Dieser Vorfall wird gemeldet!" + end + end + + print ('Checking if search contains blacklisted words: '..text) + if is_blacklisted(text) then + return "Vergiss es ._." + end + + local results = getNSFWImage(text) + if not results then + return "Kein Bild gefunden!" + end + + -- Random image from table + local i = math.random(#results) + local url = nil; + + local failed = true + local nofTries = 0 + while failed and nofTries < #results do + url = results[i].url; + print("Bilder-URL: ", url) + + if string.ends(url, ".gif") then + failed = not send_document_from_url(receiver, url, nil, nil, true) + elseif string.ends(url, ".jpg") or string.ends(url, ".jpeg") or string.ends(url, ".png") then + failed = not send_photo_from_url(receiver, url, nil, nil, true) + end + + nofTries = nofTries + 1 + i = i+1 + if i > #results then + i = 1 + end + end + + if failed then + return "Fehler beim Herunterladen eines Bildes." else - send_photo_from_url(receiver, url) - return "Source: "..url + return "Source: "..url end end return { description = "Sucht Bild mit Google-API und versendet es [NSFW]", usage = {"/img2 [Suchbegriff]","/nsfwimg [Suchbegriff]"}, - patterns = {"^/img2 (.*)$","^/nsfwimg (.*)$"}, + patterns = {"^/img2 (.*)$", + "^/nsfwimg (.*)$"--[[, + "^(/imgblacklist show)$", + "^(/imgblacklist add) (.*)$", + "^(/imgblacklist del) (.*)$"]] + }, run = run } end \ No newline at end of file diff --git a/plugins/instagram.lua b/plugins/instagram.lua new file mode 100644 index 0000000..d2e5f61 --- /dev/null +++ b/plugins/instagram.lua @@ -0,0 +1,72 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://api.instagram.com/v1' +local access_token = cred_data.instagram_access_token + +function get_insta_data (insta_code) + local url = BASE_URL..'/media/shortcode/'..insta_code..'?access_token='..access_token + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res).data + return data +end + +function send_instagram_data(data, receiver) + -- Header + local username = data.user.username + local full_name = data.user.full_name + if username == full_name then + header = full_name..' hat ein' + else + header = full_name..' ('..username..') hat ein' + end + if data.type == 'video' then + header = header..' Video gepostet' + else + header = header..' Foto gepostet' + end + + -- Caption + if data.caption == nil then + caption = '' + else + caption = ':\n'..data.caption.text + end + + -- Footer + local comments = comma_value(data.comments.count) + local likes = comma_value(data.likes.count) + local footer = '\n'..likes..' Likes, '..comments..' Kommentare' + if data.type == 'video' then + footer = '\n'..data.videos.standard_resolution.url..footer + end + + -- Image + local image_url = data.images.standard_resolution.url + local cb_extra = { + receiver=receiver, + url=image_url + } + + -- Send all + send_msg(receiver, header..caption..footer, send_photo_from_url_callback, cb_extra) +end + +function run(msg, matches) + local insta_code = matches[1] + local data = get_insta_data(insta_code) + local receiver = get_receiver(msg) + send_instagram_data(data, receiver) +end + +return { + description = "Sendet Instagram-Info.", + usage = "URL zu Instagram-Post", + patterns = {"instagram.com/p/([A-Za-z0-9-_-]+)"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/ip_info.lua b/plugins/ip_info.lua new file mode 100644 index 0000000..666e893 --- /dev/null +++ b/plugins/ip_info.lua @@ -0,0 +1,63 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'http://ipinfo.io' + +function get_ip_data (ip) + local url = BASE_URL..'/'..ip..'/json' + local res,code = http.request(url) + if code == 404 then return "Diese IP gibt es nicht!" end + if code ~= 200 then return "HTTP-FEHLER: "..code end + local data = json:decode(res) + + if data.hostname == "No Hostname" then + hostname = "" + else + hostname = ' ('..data.hostname..')' + end + + if data.org then + org = data.org + else + org = 'Unbekannt' + end + + + if data.city == "" or data.city == nil then + city = "Unbekannt" + else + city = data.city + end + + local country = data.country + + if data.region == "" or data.region == nil then + region = "" + else + region = ', '..data.region + end + + if data.postal then + postal = ' (PLZ: '..data.postal..')' + else + postal = '' + end + local text = 'Der Server von '..ip..' gehört zu '..org..hostname..' und steht in '..country..', genauer in '..city..region..postal + return text +end + +function run(msg, matches) + local ip = matches[1] + return get_ip_data(ip) +end + +return { + description = "Sendet IP-Info", + usage = "/ip [IP]: Sendet Server-Infos", + patterns = {"^/ip (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/isup.lua b/plugins/isup.lua index ea9b2dc..8b46232 100644 --- a/plugins/isup.lua +++ b/plugins/isup.lua @@ -1,25 +1,79 @@ do -local socket = require("socket") -local cronned = load_from_file('data/isup.lua') -local function save_cron(msg, url, delete) - local origin = get_receiver(msg) - if not cronned[origin] then - cronned[origin] = {} - end - if not delete then - table.insert(cronned[origin], url) - else - for k,v in pairs(cronned[origin]) do - if v == url then - table.remove(cronned[origin], k) - end - end - end - serialize_to_file(cronned, 'data/isup.lua') - return 'Gespeichert!' +local function prot_url(url) + local url, h = string.gsub(url, "http://", "") + local url, hs = string.gsub(url, "https://", "") + local protocol = "http" + if hs == 1 then + protocol = "https" + end + return url, protocol end +local function get_base_redis(id, option, extra) + local ex = '' + if option ~= nil then + ex = ex .. ':' .. option + if extra ~= nil then + ex = ex .. ':' .. extra + end + end + return 'isup:' .. id .. ex +end + +local function print_cron(id) + local uhash = get_base_redis(id) + local subs = redis:smembers(uhash) + local text = id .. ' prüft folgende Webseiten:\n---------\n' + for k,v in pairs(subs) do + text = text .. k .. ") " .. v .. '\n' + end + return text +end + +local function save_cron(id, url) + local url = string.lower(url) + local baseurl, protocol = prot_url(url) + local prothash = get_base_redis(baseurl, "protocol") + local checkhash = get_base_redis(baseurl, "check") + local uhash = get_base_redis(id) + + if redis:sismember(uhash, baseurl) then + return url..' wird bereits geprüft.' + end + + print('Saving...') + redis:set(prothash, protocol) + redis:sadd(checkhash, id) + redis:sadd(uhash, baseurl) + return url.." wird jetzt alle fünf Minuten geprüft!" +end + +local function delete_cron(id, n) + n = tonumber(n) + + local uhash = get_base_redis(id) + local subs = redis:smembers(uhash) + if n < 1 or n > #subs then + return "ID zu hoch!" + end + local sub = subs[n] + local lhash = get_base_redis(sub, "check") + + redis:srem(uhash, sub) + redis:srem(lhash, id) + + local left = redis:smembers(lhash) + if #left < 1 then -- no one subscribed, remove it + local prothash = get_base_redis(sub, "protocol") + local downhash = get_base_redis(sub, "down") + redis:del(prothash) + redis:del(downhash) + end + + return sub.." wird nicht mehr geprüft." +end + local function is_up_socket(ip, port) print('Connect to', ip, port) local c = socket.try(socket.tcp()) @@ -73,56 +127,96 @@ local function isup(url) else result = is_up_http(url) end - return result end local function cron() - for chan, urls in pairs(cronned) do - for k,url in pairs(urls) do - print('Checking', url) - if not isup(url) then - local text = url..' scheint DOWN zu sein' - send_msg(chan, text, ok_cb, false) - end + local keys = redis:keys(get_base_redis("*", "check")) + for k,v in pairs(keys) do + local base = string.match(v, "isup:(.+):check") -- Get the URL base + + print('ISUP: '..base) + local prot = redis:get(get_base_redis(base, "protocol")) + local url = prot .. "://" .. base + local hash = 'isup:'..base..':down' + local isdown = redis:get(hash) + if not isup(url) then + if isdown ~= 'true' then + redis:set(hash, 'true') + local text = url..' ist DOWN! ❌' + for e, receiver in pairs(redis:smembers(v)) do + send_msg(receiver, text, ok_cb, false) + end + else + print(base..' ist immer noch down') + end + else + if isdown == 'true' then + redis:set(hash, 'false') + local text = url..' ist wieder UP! ✅' + for e, receiver in pairs(redis:smembers(v)) do + send_msg(receiver, text, ok_cb, false) + end + end end end end local function run(msg, matches) - if matches[1] == 'remove' then - if not is_sudo(msg) then - return 'Du darfst diesen Befehl nicht nutzen!' - end - return save_cron(msg, matches[2], true) + local id = "user#id" .. msg.from.id - elseif matches[1] == 'save' then + if is_chat_msg(msg) then + id = "chat#id" .. msg.to.id + end + + + if matches[1] == 'cron show' then if not is_sudo(msg) then - return 'Du darfst diesen Befehl nicht nutzen!' + return 'Du darfst diesen Befehl nicht benutzen!' end - return save_cron(msg, matches[2]) + return print_cron(id) + + elseif matches[1] == 'cron check' then + if not is_sudo(msg) then + return 'Du darfst diesen Befehl nicht benutzen!' + end + return cron() + + elseif matches[1] == 'cron delete' then + if not is_sudo(msg) then + return 'Du darfst diesen Befehl nicht benutzen!' + end + return delete_cron(id, matches[2]) + + elseif matches[1] == 'cron' then + if not is_sudo(msg) then + return 'Du darfst diesen Befehl nicht benutzen!' + end + return save_cron(id, matches[2]) elseif isup(matches[1]) then - return matches[1]..' ist UP ✔' + return matches[1]..' ist UP! ✅' else - return matches[1]..' scheint DOWN zu sein ❌' + return matches[1]..' ist DOWN! ❌' end end return { - description = "Check if a website or server is up.", + description = "Checkt, ob eine Webseite up ist.", usage = { - "/isup [host]: Performs a HTTP request or Socket (ip:port) connexion", - "/isup save [host]: Every 5mins check if host is up. (Requires privileged user)", - "/isup remove [host]: Disable checking that host." + "/isup [Host]: Checkt, ob die Seite up ist", + "/isup cron [Host]: Checkt diese Seite alle 5 Minuten (nur Superuser)", + "/isup cron check: Prüfe alle Seiten jetzt", + "/isup cron show: Listet alle zu prüfenden Seiten auf", + "/isup cron delete [ID]: Checkt diese Seite nicht mehr" }, patterns = { - "^/isup (remove) (.*)$", - "^/isup (save) (.*)$", + "^/isup (cron check)$", + "^/isup (cron show)$", + "^/isup (cron delete) (%d+)$", + "^/isup (cron) (.*)$", "^/isup (.*)$", - "^/ping (.*)$", - "^/ping (remove) (.*)$", - "^/ping (save) (.*)$" + "^/ping (.*)$" }, run = run, cron = cron diff --git a/plugins/konachan.lua b/plugins/konachan.lua new file mode 100644 index 0000000..f79ef04 --- /dev/null +++ b/plugins/konachan.lua @@ -0,0 +1,32 @@ +do + +local function get_kc(tag) + local url = 'http://konachan.net/post.json?tags='..tag..' -rating:explicit' + local b,c = http.request(url) + if c ~= 200 then return nil end + local kc = json:decode(b) + -- truly randomize + math.randomseed(os.time()) + -- random max json table size + local i = math.random(#kc) + local link_image = kc[i].file_url + return link_image +end + +local function run(msg, matches) + local tag = matches[1] + local tag = string.gsub(tag, " ", '_' ) + local receiver = get_receiver(msg) + local url = get_kc(tag) + send_photo_from_url(receiver, url, send_title, {receiver, title}) + return "Source: "..url +end + +return { + description = "Sendet ein Bild von Konachan.net [SFW]", + usage = {"/konachan [Tags]","/kc"}, + patterns = {"^/konachan (.*)$","^/kc (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/konachan_nsfw.lua b/plugins/konachan_nsfw.lua new file mode 100644 index 0000000..0c718ad --- /dev/null +++ b/plugins/konachan_nsfw.lua @@ -0,0 +1,32 @@ +do + +local function get_kc2(tag) + local url = 'http://konachan.com/post.json?tags='..tag..'' + local b,c = http.request(url) + if c ~= 200 then return nil end + local kc = json:decode(b) + -- truly randomize + math.randomseed(os.time()) + -- random max json table size + local i = math.random(#kc) + local link_image = kc[i].file_url + return link_image +end + +local function run(msg, matches) + local tag = matches[1] + local tag = string.gsub(tag, " ", '_' ) + local receiver = get_receiver(msg) + local url = get_kc2(tag) + send_photo_from_url(receiver, url, send_title, {receiver, title}) + return "Source: "..url +end + +return { + description = "Sendet Bild von Konachan.com [NSFW]", + usage = {"/konansfw [Tags]","/kcn [Tags]"}, + patterns = {"^/konansfw (.*)$","^/kcn (.*)$"}, + run = run +} + +end \ No newline at end of file diff --git a/plugins/location_manager.lua b/plugins/location_manager.lua new file mode 100644 index 0000000..0831b16 --- /dev/null +++ b/plugins/location_manager.lua @@ -0,0 +1,63 @@ +-- This file is part of the Telegram Bot "Brawlbot" (telegram.me/Brawlbot) by Andreas Bielawski (telegram.me/Brawl) +-- Released unter the MPLv2 + +do + +local function set_location(user_id, location) + local hash = 'user:'..user_id + local set_location = get_location(user_id) + if set_location == location then + return 'Dieser Ort wurde bereits gesetzt' + else + print('Setting location in redis hash '..hash..' to location') + redis:hset(hash, 'location', location) + return 'Dein Wohnort wurde auf "'..location..'" festgelegt.' + end +end + +local function del_location(user_id) + local hash = 'user:'..user_id + local set_location = get_location(user_id) + if not set_location then + return 'Du hast keinen Ort gesetzt' + else + print('Setting location in redis hash '..hash..' to false') + -- We set the location to false, because deleting the value blocks redis for a few milliseconds + redis:hset(hash, 'location', false) + return 'Dein Wohnort "'..set_location..'" wurde gelscht!' + end +end + +local function run(msg, matches) + local user_id = msg.from.id + + if matches[1] == 'set' then + return set_location(user_id, matches[2]) + elseif matches[1] == 'del' then + return del_location(user_id) + else + local set_location = get_location(user_id) + if not set_location then + return 'Du hast keinen Ort gesetzt' + else + return 'Gesetzter Wohnort: '..set_location + end + end +end + +return { + description = "Orte-Manager", + usage = { + "/location: Gibt deinen gesetzten Wohnort aus", + "/location set (Ort): Setzt deinen Wohnort auf diesen Ort", + "/location del: Lscht deinen angegebenen Wohnort" + }, + patterns = { + "^/location (set) (.*)$", + "^/location (del)$", + "^/location$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/love_calculator.lua b/plugins/love_calculator.lua new file mode 100644 index 0000000..c1674cd --- /dev/null +++ b/plugins/love_calculator.lua @@ -0,0 +1,37 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +local BASE_URL = 'https://love-calculator.p.mashape.com' + +local function love_result(first_name, second_name) + local apikey = cred_data.x_mashape_key + local url = BASE_URL..'/getPercentage?mashape-key='..apikey..'&fname='..first_name..'&sname='..second_name + print(url) + local res,code = https.request(url) + if code ~= 200 then return "HTTP-FEHLER" end + local data = json:decode(res) + local first_name = data.fname + local second_name = data.sname + local percentage = data.percentage + local result = data.result + return first_name..' und '..second_name..' lieben sich zu '..percentage..'% ❤️\n'..result +end + +local function run(msg, matches) + local first_name = url_encode(matches[1]) + local second_name = url_encode(matches[2]) + return love_result(first_name, second_name) +end + +return { + description = "Liebestest ❤️ (kommt sogar ohne Jamba-Abo!)", + usage = "/love [Erster Name]+[Zweiter Name]: Führt einen Liebestest durch ❤️", + patterns = { + "^/love (.+)+(.+)$" + }, + run = run +} + +end \ No newline at end of file diff --git a/plugins/manager.lua b/plugins/manager.lua index f8079a9..e400b7a 100644 --- a/plugins/manager.lua +++ b/plugins/manager.lua @@ -30,6 +30,34 @@ local function is_banned(user_id, chat_id) return banned or false end +local function makesudo(user_id, msg, delete) + local set = 'telegram:sudo_users' + local is_sudo = redis:sismember(set, user_id) + if delete then + if not is_sudo then + return user_id..' ist kein Superuser.' + else + if string.match(user_id, msg.from.id) then + return 'Das Löschen deiner User-ID aus den Superusern wird feige verweigert.' + else + print('deleting user id '..user_id..' from redis set '..set) + redis:srem(set, user_id) + sudo_users = load_sudo_users() + return user_id..' ist jetzt kein Superuser mehr.' + end + end + else + if not is_sudo then + print('saving user id '..user_id..' to redis set '..set) + redis:sadd(set, user_id) + sudo_users = load_sudo_users() + return user_id..' ist jetzt ein Superuser.' + else + return user_id..' ist bereits ein Superuser.' + end + end +end + local function pre_process(msg) -- SERVICE MESSAGE @@ -86,6 +114,10 @@ local function pre_process(msg) else --print ('Chat '..msg.to.id..' whitelisted :)') end + else + local user_name = get_name(msg) + local receiver = get_receiver(msg) + send_msg(receiver, "Hey "..user_name..", dies ist der Bot von @Akamaru und kann nur nach Freischaltung durch ihn benutzt werden." , ok_cb, false) end else --print('User '..msg.from.id..' allowed :)') @@ -127,6 +159,16 @@ local function run(msg, matches) return 'Das hier ist keine Chat-Gruppe' end end + + if matches[1] == 'makesudo' then + local user_id = matches[3] + if matches[2] == 'user' then + return makesudo(user_id) + end + if matches[2] == 'delete' then + return makesudo(user_id, msg, true) + end + end if matches[1] == 'kick' then if msg.to.type == 'chat' then @@ -183,16 +225,18 @@ local function run(msg, matches) end return { - description = "Plugin to manage bans, kicks and white/black lists.", + description = "Manager-Plugin für Whitelist, Kicks und Banns (nur Superuser)", usage = { - "/whitelist /: Enable or disable whitelist mode", - "/whitelist user : Allow user to use the bot when whitelist mode is enabled", - "/whitelist chat: Allow everybody on current chat to use the bot when whitelist mode is enabled", - "/whitelist delete user : Remove user from whitelist", - "/whitelist delete chat: Remove chat from whitelist", - "/ban user : Kick user from chat and kicks it if joins chat again", - "/ban delete : Unban user", - "/kick Kick user from chat group" + "/whitelist /: Aktiviert/deaktiviert Whitelist", + "/whitelist user : Whiteliste User", + "/whitelist chat: Whiteliste ganze Gruppe", + "/whitelist delete user : Lösche User von der Whitelist", + "/whitelist delete chat: Lösche ganze Gruppe von der Whitelist", + "/ban user : Kicke User vom Chat und kicke ihn, wenn er erneut beitritt", + "/ban delete : Entbanne User", + "/kick : Kicke User vom Chat", + "/makesudo user : Macht User zum Superuser", + "/makesudo delete : Macht User zum Superuser" }, patterns = { "^/(whitelist) (enable)$", @@ -205,6 +249,8 @@ return { "^/(ban) (delete) (%d+)$", "^/(kick) (%d+)$", --"^//tgservice (.+)$", + "^/(makesudo) (user) (%d+)$", + "^/(makesudo) (delete) (%d+)$" }, run = run, pre_process = pre_process, diff --git a/plugins/media_download.lua b/plugins/media_download.lua index 1425480..76e7485 100644 --- a/plugins/media_download.lua +++ b/plugins/media_download.lua @@ -36,5 +36,6 @@ return { usage = {'Irgendeine Datei'}, run = run, patterns = {'%[(document)%]','%[(photo)%]','%[(video)%]','%[(audio)%]'}, - pre_process = pre_process + pre_process = pre_process, + notyping = true } diff --git a/plugins/miiverse.lua b/plugins/miiverse.lua new file mode 100644 index 0000000..4b52fa8 --- /dev/null +++ b/plugins/miiverse.lua @@ -0,0 +1,89 @@ +-- This is a proprietary plugin, property of Andreas Bielawski, (c) 2015 +-- DO NOT USE WITHOUT PERMISSION + +do + +-- TODO: Language selector (for e.g. this post: https://miiverse.nintendo.net/posts/AYMHAAACAAADVHkSrNJ-9Q) +-- + +local function get_miiverse_data(res, post) + username = string.match(res, "

(.-)") + userid = string.match(res, "(.-)") + timestamp = string.match(res, "(.-)") + if string.starts(timestamp, 'Vor') then + timestamp = string.gsub(timestamp, "Vor","") + timestamp = "vor"..timestamp + else + timestamp = "am "..timestamp + end + community = string.match(res, "

(.-)
") + --community = string.gsub(community, "™","") + yeahs = string.match(res, "(.-)") + if yeahs == "1" then yeahs = "1 Yeah" else yeahs = yeahs.." Yeahs" end + replys = string.match(res, "(.-)") + if replys == "1" then replys = "1 Kommentar" else replys = replys.." Kommentaren" end + youtube_link = string.match(res, "