diff --git a/otouto/bot.lua b/otouto/bot.lua index d0bd39b..ad8810c 100644 --- a/otouto/bot.lua +++ b/otouto/bot.lua @@ -5,7 +5,7 @@ local bindings -- Load Telegram bindings. local utilities -- Load miscellaneous and cross-plugin functions. local redis = (loadfile "./otouto/redis.lua")() -bot.version = '2' +bot.version = '2.0' function bot:init(config) -- The function run when the bot is started or reloaded. @@ -73,10 +73,17 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec for _,w in pairs(v.triggers) do if string.match(msg.text_lower, w) then local success, result = pcall(function() - return v.action(self, msg, config) + -- trying to port matches to otouto + for k, pattern in pairs(v.triggers) do + matches = match_pattern(pattern, msg.text) + if matches then + break; + end + end + return v.action(self, msg, config, matches) end) if not success then - utilities.send_reply(self, msg, 'Sorry, an unexpected error occurred.') + utilities.send_reply(self, msg, 'Ein unbekannter Fehler ist aufgetreten, bitte [melde diesen Bug](https://github.com/Brawl345/Brawlbot-v2/issues).', true) utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) return end diff --git a/otouto/plugins/twitter_send.lua b/otouto/plugins/twitter_send.lua new file mode 100644 index 0000000..67207be --- /dev/null +++ b/otouto/plugins/twitter_send.lua @@ -0,0 +1,349 @@ +local twitter_send = {} + +local http = require('socket.http') +local https = require('ssl.https') +local URL = require('socket.url') +local json = require('dkjson') +local utilities = require('otouto.utilities') +local bindings = require('otouto.bindings') +local OAuth = require "OAuth" +local redis = (loadfile "./otouto/redis.lua")() + +function twitter_send:init(config) + if not cred_data.tw_consumer_key then + print('Missing config value: tw_consumer_key.') + print('twitter_send.lua will not be enabled.') + return + elseif not cred_data.tw_consumer_secret then + print('Missing config value: tw_consumer_secret.') + print('twitter_send.lua will not be enabled.') + return + end + + twitter_send.triggers = { + "^/tw (auth) (%d+)", + "^/tw (unauth)$", + "^/tw (verify)$", + "^/tw (.+)", + "^/(twwhitelist add) (%d+)", + "^/(twwhitelist del) (%d+)" + } + twitter_send.doc = [[* +]]..config.cmd_pat..[[tw* __: Sendet einen Tweet an den Account, der im Chat angemeldet ist +*]]..config.cmd_pat..[[tw* _verify_: Gibt den angemeldeten User aus, inklusive Profilbild +*]]..config.cmd_pat..[[twwitelist* _add_ __: Schaltet User für die Tweet-Funktion frei +*]]..config.cmd_pat..[[twwitelist* _del_ __: Entfernt User von der Tweet-Whitelist +*]]..config.cmd_pat..[[tw* _auth_ __: Meldet mit dieser PIN an (Setup) +*]]..config.cmd_pat..[[tw* _unauth_: Meldet Twitter-Account ab +]] +end + +twitter_send.command = 'tw ' + +local consumer_key = cred_data.tw_consumer_key +local consumer_secret = cred_data.tw_consumer_secret + +function can_send_tweet(msg) + local hash = 'user:'..msg.from.id + local var = redis:hget(hash, 'can_send_tweet') + if var == "true" then + return true + else + return false + end +end + +local client = OAuth.new(consumer_key, consumer_secret, { + RequestToken = "https://api.twitter.com/oauth/request_token", + AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"}, + AccessToken = "https://api.twitter.com/oauth/access_token" +}) + +function twitter_send:do_twitter_authorization_flow(hash, is_chat) + local callback_url = "oob" + local values = client:RequestToken({ oauth_callback = callback_url }) + local oauth_token = values.oauth_token + local oauth_token_secret = values.oauth_token_secret + + -- save temporary oauth keys + redis:hset(hash, 'oauth_token', oauth_token) + redis:hset(hash, 'oauth_token_secret', oauth_token_secret) + + local auth_url = client:BuildAuthorizationUrl({ oauth_callback = callback_url, force_login = true }) + if is_chat then + return 'Bitte schließe den Vorgang ab, indem du unten auf den Link klickst und mir die angezeigte PIN per `/tw auth PIN` *im Chat von gerade* übergibst.\n[Bei Twitter anmelden]('..auth_url..')' + else + return 'Bitte schließe den Vorgang ab, indem du unten auf den Link klickst und mir die angezeigte PIN per `/tw auth PIN` übergibst.\n[Bei Twitter anmelden]('..auth_url..')' + end +end + +function twitter_send:get_twitter_access_token(hash, oauth_verifier, oauth_token, oauth_token_secret) + local oauth_verifier = tostring(oauth_verifier) -- must be a string + + -- now we'll use the tokens we got in the RequestToken call, plus our PIN + local client = OAuth.new(consumer_key, consumer_secret, { + RequestToken = "https://api.twitter.com/oauth/request_token", + AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"}, + AccessToken = "https://api.twitter.com/oauth/access_token" + }, { + OAuthToken = oauth_token, + OAuthVerifier = oauth_verifier + }) + client:SetTokenSecret(oauth_token_secret) + + local values, err, headers, status, body = client:GetAccessToken() + if err then return 'Einloggen fehlgeschlagen!' end + + -- save permanent oauth keys + redis:hset(hash, 'oauth_token', values.oauth_token) + redis:hset(hash, 'oauth_token_secret', values.oauth_token_secret) + + return 'Erfolgreich eingeloggt als "@'..values.screen_name..'" (User-ID: '..values.user_id..')' +end + +function twitter_send:reset_twitter_auth(hash, frominvalid) + redis:hdel(hash, 'oauth_token') + redis:hdel(hash, 'oauth_token_secret') + if frominvalid then + return '*Authentifizierung nicht erfolgreich, wird zurückgesetzt...*' + else + return '*Erfolgreich abgemeldet!* Entziehe den Zugriff endgültig in deinen [Twitter-Einstellungen](https://twitter.com/settings/applications)!' + end +end + +function twitter_send:resolve_url(url) + local response_body = {} + local request_constructor = { + url = url, + method = "HEAD", + sink = ltn12.sink.table(response_body), + headers = {}, + redirect = false + } + + local ok, response_code, response_headers, response_status_line = http.request(request_constructor) + if ok and response_headers.location then + return response_headers.location + else + return url + end +end + +function twitter_send:twitter_verify_credentials(oauth_token, oauth_token_secret) + local client = OAuth.new(consumer_key, consumer_secret, { + RequestToken = "https://api.twitter.com/oauth/request_token", + AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"}, + AccessToken = "https://api.twitter.com/oauth/access_token" + }, { + OAuthToken = oauth_token, + OAuthTokenSecret = oauth_token_secret + }) + + local response_code, response_headers, response_status_line, response_body = + client:PerformRequest( + "GET", "https://api.twitter.com/1.1/account/verify_credentials.json", { + include_entities = false, + skip_status = true, + include_email = false + } + ) + + local response = json.decode(response_body) + if response_code == 401 then + return twitter_send:reset_twitter_auth(hash, true) + end + if response_code ~= 200 then + return 'HTTP-Fehler '..response_code..': '..data.errors[1].message + end + + -- TODO: copied straight from the twitter_user plugin, maybe we can do it better? + local full_name = response.name + local user_name = response.screen_name + if response.verified then + user_name = user_name..' ✅' + end + if response.protected then + user_name = user_name..' 🔒' + end + local header = full_name.. " (@" ..user_name.. ")\n" + + local description = unescape(response.description) + if response.location then + location = response.location + else + location = '' + end + if response.url and response.location ~= '' then + url = ' | '..twitter_send:resolve_url(response.url)..'\n' + elseif response.url and response.location == '' then + url = twitter_send:resolve_url(response.url)..'\n' + else + url = '\n' + end + + local body = description..'\n'..location..url + + local favorites = comma_value(response.favourites_count) + local follower = comma_value(response.followers_count) + local following = comma_value(response.friends_count) + local statuses = comma_value(response.statuses_count) + local footer = statuses..' Tweets, '..follower..' Follower, '..following..' folge ich, '..favorites..' Tweets favorisiert' + + local text = 'Eingeloggter Account:\n'..header..body..footer + local pp_url = string.gsub(response.profile_image_url_https, "normal", "400x400") + + return text, pp_url +end + +function twitter_send:send_tweet(tweet, oauth_token, oauth_token_secret, hash) + local client = OAuth.new(consumer_key, consumer_secret, { + RequestToken = "https://api.twitter.com/oauth/request_token", + AuthorizeUser = {"https://api.twitter.com/oauth/authorize", method = "GET"}, + AccessToken = "https://api.twitter.com/oauth/access_token" + }, { + OAuthToken = oauth_token, + OAuthTokenSecret = oauth_token_secret + }) + + local response_code, response_headers, response_status_line, response_body = + client:PerformRequest( + "POST", "https://api.twitter.com/1.1/statuses/update.json", { + status = tweet + } + ) + + local data = json.decode(response_body) + if response_code == 401 then + return twitter_send:reset_twitter_auth(hash, true) + end + if response_code ~= 200 then + return 'HTTP-Fehler '..response_code..': '..data.errors[1].message + end + + local statusnumber = comma_value(data.user.statuses_count) + local screen_name = data.user.screen_name + local status_id = data.id_str + + return '*Tweet #'..statusnumber..' gesendet!* [Auf Twitter ansehen](https://twitter.com/statuses/'..status_id..')' +end + +function twitter_send:add_to_twitter_whitelist(user_id) + local hash = 'user:'..user_id + local whitelisted = redis:hget(hash, 'can_send_tweet') + if whitelisted ~= 'true' then + print('Setting can_send_tweet in redis hash '..hash..' to true') + redis:hset(hash, 'can_send_tweet', true) + return '*User '..user_id..' kann jetzt Tweets senden!*' + else + return '*User '..user_id..' kann schon Tweets senden.*' + end +end + +function twitter_send:del_from_twitter_whitelist(user_id) + local hash = 'user:'..user_id + local whitelisted = redis:hget(hash, 'can_send_tweet') + if whitelisted == 'true' then + print('Setting can_send_tweet in redis hash '..hash..' to false') + redis:hset(hash, 'can_send_tweet', false) + return '*User '..user_id..' kann jetzt keine Tweets mehr senden!*' + else + return '*User '..user_id..' ist nicht whitelisted.*' + end +end + +function twitter_send:action(msg, config, matches) + if matches[1] == "twwhitelist add" and matches[2] then + if msg.from.id ~= config.admin then + utilities.send_reply(self, msg, config.errors.sudo) + return + else + utilities.send_reply(self, msg, twitter_send:add_to_twitter_whitelist(matches[2]), true) + return + end + end + + if matches[1] == "twwhitelist del" and matches[2] then + if msg.from.id ~= config.admin then + utilities.send_reply(self, msg, config.errors.sudo) + return + else + utilities.send_reply(self, msg, twitter_send:del_from_twitter_whitelist(matches[2]), true) + return + end + end + + local hash = get_redis_hash(msg, 'twitter') + local oauth_token = redis:hget(hash, 'oauth_token') + local oauth_token_secret = redis:hget(hash, 'oauth_token_secret') + + -- Thanks to the great doc at https://github.com/ignacio/LuaOAuth#a-more-involved-example + if not oauth_token and not oauth_token_secret then + if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then + if msg.from.id ~= config.admin then + utilities.send_reply(self, msg, config.errors.sudo) + return + else + -- maybe we can edit the older message to update it to "logged in!"? + -- this should be interesting: https://core.telegram.org/bots/api#editmessagetext + local text = twitter_send:do_twitter_authorization_flow(hash, true) + local res = utilities.send_message(self, msg.from.id, text, true, nil, true) + if not res then + utilities.send_reply(self, msg, 'Bitte starte mich zuerst [privat](http://telegram.me/' .. self.info.username .. '?start).', true) + elseif msg.chat.type ~= 'private' then + utilities.send_message(self, msg.chat.id, '_Bitte warten, der Administrator meldet sich an..._', true, nil, true) + end + return + end + else + utilities.send_reply(self, msg, twitter_send:do_twitter_authorization_flow(hash), true) + return + end + end + + if matches[1] == 'auth' and matches[2] then + if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then + if msg.from.id ~= config.admin then + utilities.send_reply(self, msg, config.errors.sudo) + return + end + end + if string.len(matches[2]) > 7 then utilities.send_reply(self, msg, 'Invalide PIN!') return end + utilities.send_reply(self, msg, twitter_send:get_twitter_access_token(hash, matches[2], oauth_token, oauth_token_secret)) + return + end + + if matches[1] == 'unauth' then + if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then + if msg.from.id ~= config.admin then + utilities.send_reply(self, msg, config.errors.sudo) + return + end + end + utilities.send_reply(self, msg, twitter_send:reset_twitter_auth(hash), true) + return + end + + if matches[1] == 'verify' then + local text, pp_url = twitter_send:twitter_verify_credentials(oauth_token, oauth_token_secret) + local file = download_to_file(pp_url) + utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id) + utilities.send_reply(self, msg, text) + return + end + + + if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then + if not can_send_tweet(msg) then + utilities.send_reply(self, msg, '*Du darfst keine Tweets senden.* Entweder wurdest du noch gar nicht freigeschaltet oder ausgeschlossen.', true) + return + else + utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), true) + return + end + else + utilities.send_reply(self, msg, twitter_send:send_tweet(matches[1], oauth_token, oauth_token_secret, hash), true) + return + end +end + +return twitter_send \ No newline at end of file diff --git a/otouto/utilities.lua b/otouto/utilities.lua index 51fbe9d..bf86ae4 100644 --- a/otouto/utilities.lua +++ b/otouto/utilities.lua @@ -562,6 +562,18 @@ utilities.char = { em_dash = '—' } +-- Returns a table with matches or nil +--function match_pattern(pattern, text, lower_case) +function match_pattern(pattern, text) + if text then + local matches = { string.match(text, pattern) } + if next(matches) then + return matches + end + end + -- nil +end + function get_redis_hash(msg, var) if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then return 'chat:'..msg.chat.id..':'..var