Merge pull request #1 from Brawl345/master
Übernehme änderungen von Brawlbot
This commit is contained in:
		
							
								
								
									
										35
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| language: erlang | ||||
|  | ||||
| before_install: | ||||
|   - CURDIR=$(pwd) | ||||
|   - sudo apt-get update -qq | ||||
|   - sudo apt-get install -qq libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev make unzip git libjansson-dev | ||||
|   - THIS_DIR=$(cd $(dirname $0); pwd) | ||||
|   - cd $THIS_DIR | ||||
|   - git clone https://github.com/keplerproject/luarocks.git | ||||
|   - cd luarocks | ||||
|   - git checkout tags/v2.3.0 | ||||
|   - PREFIX="$THIS_DIR/.luarocks" | ||||
|   - ./configure --prefix=$PREFIX --sysconfdir=$PREFIX/luarocks --force-config | ||||
|   - make build && make install | ||||
|   - cd .. | ||||
|   - rm -rf luarocks | ||||
|   - ./.luarocks/bin/luarocks install luasocket | ||||
|   - ./.luarocks/bin/luarocks install luasec | ||||
|   - ./.luarocks/bin/luarocks install multipart-post | ||||
|   - ./.luarocks/bin/luarocks install dkjson | ||||
|   - ./.luarocks/bin/luarocks install oauth | ||||
|   - ./.luarocks/bin/luarocks install redis-lua | ||||
|   - ./.luarocks/bin/luarocks install lua-cjson | ||||
|   - ./.luarocks/bin/luarocks install fakeredis | ||||
|   - ./.luarocks/bin/luarocks install xml | ||||
|   - ./.luarocks/bin/luarocks install feedparser | ||||
|   - ./.luarocks/bin/luarocks install serpent | ||||
|   - ./.luarocks/bin/luarocks install sha1 | ||||
|   - ./.luarocks/bin/luarocks install lpeg | ||||
|   - cd $CURDIR | ||||
|    | ||||
|  | ||||
| script:  | ||||
|   - luac -p otouto/*.lua | ||||
|   - luac -p otouto/plugins/*.lua | ||||
							
								
								
									
										31
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,4 +1,6 @@ | ||||
| # Brawlbot v2 | ||||
| [](https://travis-ci.org/Brawl345/Brawlbot-v2) | ||||
|  | ||||
| The plugin-wielding, multipurpose Telegram bot. | ||||
|  | ||||
| [Public Bot](http://telegram.me/mokubot) | [Official Channel](http://telegram.me/otouto) | [Development Group](http://telegram.me/BotDevelopment) | ||||
| @@ -11,11 +13,10 @@ otouto is free software; you are free to redistribute it and/or modify it under | ||||
|  | ||||
| | For Users                                     | For Coders                    | | ||||
| |:----------------------------------------------|:------------------------------| | ||||
| | [Setup](#setup)                               | [Introduction](#introduction) | | ||||
| | [Control plugins](#control-plugins)           | [Plugins](#plugins)           | | ||||
| | [Group Administration](#group-administration) | [Bindings](#bindings)         | | ||||
| | [List of plugins](#list-of-plugins)           | [Output style](#output-style) | | ||||
| |                                               | [Contributors](#contributors) | | ||||
| | [Setup](#setup)                               | [Plugins](#plugins)           | | ||||
| | [Control plugins](#control-plugins)           | [Bindings](#bindings)         | | ||||
| | [Group Administration](#group-administration) | [Output style](#output-style) | | ||||
| | [List of plugins](#list-of-plugins)           | [Contributors](#contributors) | | ||||
|  | ||||
| ## Setup | ||||
| You _must_ have Lua (5.2+), luasocket, luasec, multipart-post, and dkjson installed. You should also have lpeg, though it is not required. It is recommended you install these with LuaRocks. | ||||
| @@ -194,9 +195,6 @@ Additionally, antiflood can be configured to automatically ban a user after he h | ||||
|  | ||||
| * * * | ||||
|  | ||||
| ## Introduction | ||||
| ####todo | ||||
|  | ||||
| ## Plugins | ||||
| otouto uses a robust plugin system, similar to yagop's [Telegram-Bot](http://github.com/yagop/telegram-bot). | ||||
|  | ||||
| @@ -204,14 +202,15 @@ Most plugins are intended for public use, but a few are for other purposes, like | ||||
|  | ||||
| A plugin can have five components, and two of them are required: | ||||
|  | ||||
| | Component       | Description                                  | Required? | | ||||
| |:----------------|:---------------------------------------------|:----------| | ||||
| | plugin:action   | Main function. Expects `msg` table as an argument.   | Y | | ||||
| | plugin.triggers | Table of triggers for the plugin. Uses Lua patterns. | Y | | ||||
| | plugin:init     | Optional function run when the plugin is loaded.     | N | | ||||
| | plugin:cron     | Optional function to be called every minute.         | N | | ||||
| | plugin.command  | Basic command and syntax. Listed in the help text.   | N | | ||||
| | plugin.doc      | Usage for the plugin. Returned by "/help $command".  | N | | ||||
| | Component         | Description                                  | Required? | | ||||
| |:------------------|:---------------------------------------------|:----------| | ||||
| | `plugin:action`   | Main function. Expects `msg` table as an argument.   | Y | | ||||
| | `plugin.triggers` | Table of triggers for the plugin. Uses Lua patterns. | Y | | ||||
| | `plugin:init`     | Optional function run when the plugin is loaded.     | N | | ||||
| | `plugin:cron`     | Optional function to be called every minute.         | N | | ||||
| | `plugin.command`  | Basic command and syntax. Listed in the help text.   | N | | ||||
| | `plugin.doc`      | Usage for the plugin. Returned by "/help $command".  | N | | ||||
| | `plugin.error`    | Plugin-specific error message; false for no message. | N | | ||||
|  | ||||
| The `bot:on_msg_receive` function adds a few variables to the `msg` table for your convenience. These are self-explanatory: `msg.from.id_str`, `msg.to.id_str`, `msg.chat.id_str`, `msg.text_lower`, `msg.from.name`. | ||||
|  | ||||
|   | ||||
| @@ -22,6 +22,7 @@ Sende /hilfe, um zu starten | ||||
| 	cmd_pat = '/', | ||||
|  | ||||
| 	errors = { -- Generic error messages used in various plugins. | ||||
| 	    generic = 'An unexpected error occurred.', | ||||
| 		connection = 'Verbindungsfehler.', | ||||
| 		quotaexceeded = 'API-Quota aufgebraucht.', | ||||
| 		results = 'Keine Ergebnisse gefunden.', | ||||
| @@ -30,7 +31,7 @@ Sende /hilfe, um zu starten | ||||
| 		syntax = 'Invalide Syntax.', | ||||
| 		chatter_connection = 'Ich möchte gerade nicht reden', | ||||
| 		chatter_response = 'Ich weiß nicht, was ich darauf antworten soll.' | ||||
| 	}, | ||||
| 	} | ||||
|  | ||||
| 	plugins = { -- To enable a plugin, add its name to the list. | ||||
| 		'control', | ||||
|   | ||||
| @@ -1,22 +0,0 @@ | ||||
| package = 'otouto' | ||||
| version = 'dev-1' | ||||
|  | ||||
| source = { | ||||
|   url = 'git://github.com/topkecleon/otouto.git' | ||||
| } | ||||
|  | ||||
| description = { | ||||
|   summary = 'The plugin-wielding, multipurpose Telegram bot!', | ||||
|   detailed = 'A plugin-wielding, multipurpose bot for the Telegram API.', | ||||
|   homepage = 'http://otou.to', | ||||
|   maintainer = 'Drew <drew@otou.to>', | ||||
|   license = 'GPL-2' | ||||
| } | ||||
|  | ||||
| dependencies = { | ||||
|   'lua >= 5.2', | ||||
|   'LuaSocket ~> 3.0', | ||||
|   'LuaSec ~> 0.6', | ||||
|   'dkjson ~> 2.5', | ||||
|   'LPeg ~> 1.0' | ||||
| } | ||||
| @@ -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. | ||||
|  | ||||
| @@ -69,14 +69,28 @@ function bot:on_msg_receive(msg, config) -- The fn run whenever a message is rec | ||||
| 		msg.text_lower = msg.text:lower() | ||||
| 	end | ||||
|  | ||||
| 	for _,v in ipairs(self.plugins) do | ||||
| 		for _,w in pairs(v.triggers) do | ||||
| 			if string.match(msg.text_lower, w) then | ||||
| 	for _, plugin in ipairs(self.plugins) do | ||||
| 		for _, trigger in pairs(plugin.triggers) do | ||||
| 			if string.match(msg.text_lower, trigger) then | ||||
| 				local success, result = pcall(function() | ||||
| 					return v.action(self, msg, config) | ||||
| 				 -- trying to port matches to otouto | ||||
| 				      for k, pattern in pairs(plugin.triggers) do | ||||
| 					    matches = match_pattern(pattern, msg.text) | ||||
| 						if matches then | ||||
| 						  break; | ||||
| 						end | ||||
| 					  end | ||||
| 					return plugin.action(self, msg, config, matches) | ||||
| 				end) | ||||
| 				if not success then | ||||
| 					utilities.send_reply(self, msg, 'Sorry, an unexpected error occurred.') | ||||
| 					-- If the plugin has an error message, send it. If it does | ||||
| 					-- not, use the generic one specified in config. If it's set | ||||
| 					-- to false, do nothing. | ||||
| 					if plugin.error then | ||||
| 						utilities.send_reply(self, msg, plugin.error) | ||||
| 					elseif plugin.error == nil then | ||||
| 						utilities.send_reply(self, msg, config.errors.generic, true) | ||||
| 					end | ||||
| 					utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) | ||||
| 					return | ||||
| 				end | ||||
|   | ||||
| @@ -26,6 +26,7 @@ function ninegag:get_9GAG() | ||||
| end | ||||
|  | ||||
| function ninegag:action(msg, config) | ||||
|   utilities.send_typing(self, msg.chat.id, 'upload_photo') | ||||
|   local url, title = ninegag:get_9GAG() | ||||
|   if not url then | ||||
| 	utilities.send_reply(self, msg, config.errors.connection) | ||||
| @@ -33,9 +34,7 @@ function ninegag:action(msg, config) | ||||
|   end | ||||
|  | ||||
|   local file = download_to_file(url) | ||||
|   bindings.sendPhoto(self, {chat_id = msg.chat.id, caption = title}, {photo = file} ) | ||||
|   os.remove(file) | ||||
|   print("Deleted: "..file) | ||||
|   utilities.send_photo(self, msg.chat.id, file, title) | ||||
| end | ||||
|  | ||||
| return ninegag | ||||
|   | ||||
| @@ -71,6 +71,9 @@ function administration:init(config) | ||||
|  | ||||
| 	administration.doc = '`Returns a list of administrated groups.\nUse '..config.cmd_pat..'ahelp for more administrative commands.`' | ||||
|  | ||||
| 	-- In the worst case, don't send errors in reply to random messages. | ||||
| 	administration.error = false | ||||
|  | ||||
| end | ||||
|  | ||||
| function administration.init_flags(cmd_pat) return { | ||||
| @@ -263,9 +266,9 @@ function administration:kick_user(chat, target, reason, config) | ||||
| 	local victim = target | ||||
| 	if self.database.users[tostring(target)] then | ||||
| 		victim = utilities.build_name( | ||||
| 			self.database.users[tostring(target)].first_name, | ||||
| 			self.database.users[tostring(target)].last_name | ||||
| 		) | ||||
| 				self.database.users[tostring(target)].first_name, | ||||
| 				self.database.users[tostring(target)].last_name | ||||
| 			) .. ' [' .. victim .. ']' | ||||
| 	end | ||||
| 	local group = self.database.administration.groups[tostring(chat)].name | ||||
| 	utilities.handle_exception(self, victim..' kicked from '..group, reason, config) | ||||
| @@ -917,7 +920,9 @@ function administration.init_command(self_, config) | ||||
| 				if input then | ||||
| 					input = utilities.get_word(input, 1) | ||||
| 					input = tonumber(input) | ||||
| 					if not input or not administration.flags[input] then input = false end | ||||
| 					if not input or not administration.flags[input] then | ||||
| 						input = false | ||||
| 					end | ||||
| 				end | ||||
| 				if not input then | ||||
| 					local output = '*Flags for ' .. msg.chat.title .. ':*\n' | ||||
| @@ -1209,7 +1214,9 @@ function administration.init_command(self_, config) | ||||
| 			doc = 'Adds a group to the administration system. Pass numbers as arguments to enable those flags immediately. For example, this would add the group and enable the unlisted flag, antibot, and antiflood:\n/gadd 1 4 5', | ||||
|  | ||||
| 			action = function(self, msg, group, config) | ||||
| 				if self.database.administration.groups[msg.chat.id_str] then | ||||
| 				if msg.chat.id == msg.from.id then | ||||
| 					utilities.send_message(self, msg.chat.id, 'No.') | ||||
| 				elseif self.database.administration.groups[msg.chat.id_str] then | ||||
| 					utilities.send_reply(self, msg, 'I am already administrating this group.') | ||||
| 				else | ||||
| 					local flags = {} | ||||
| @@ -1327,6 +1334,18 @@ function administration.init_command(self_, config) | ||||
| 					end | ||||
| 				end | ||||
| 			end | ||||
| 		}, | ||||
|  | ||||
| 		{ -- /buildwall :^) | ||||
| 			triggers = utilities.triggers(self_.info.username, config.cmd_pat):t('buildwall').table, | ||||
| 			privilege = 3, | ||||
| 			interior = true, | ||||
| 			action = function(self, msg, group, config) | ||||
| 				for i = 2, 5 do | ||||
| 					group.flags[i] = true | ||||
| 				end | ||||
| 				utilities.send_message(self, msg.chat.id, 'antisquig, antisquig++, antibot, and antiflood have been enabled.') | ||||
| 			end | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										117
									
								
								otouto/plugins/app_store.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								otouto/plugins/app_store.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| local app_store = {} | ||||
|  | ||||
| local https = require('ssl.https') | ||||
| local json = require('dkjson') | ||||
| local utilities = require('otouto.utilities') | ||||
| local redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| app_store.triggers = { | ||||
| 	"itunes.apple.com/(.*)/app/(.*)/id(%d+)", | ||||
| 	"^!itunes (%d+)$", | ||||
| 	"itunes.apple.com/app/id(%d+)" | ||||
| } | ||||
| 	 | ||||
| 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 | ||||
|  | ||||
| function app_store: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 | ||||
|  | ||||
| function app_store:send_appstore_data(data)   | ||||
|   -- 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..'\n_Benö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 | ||||
|    | ||||
|   return text, image_url | ||||
| end | ||||
|  | ||||
| function app_store:action(msg, config, matches) | ||||
|   if not matches[3] then | ||||
|     appid = matches[1] | ||||
|   else | ||||
|     appid = matches[3] | ||||
|   end | ||||
|   local data = app_store:get_appstore_data() | ||||
|   if data == nil then print('Das Appstore-Plugin unterstützt nur Apps!') end | ||||
|   if data == 'HTTP-FEHLER' or data == 'NOTFOUND' then | ||||
|     utilities.send_reply(self, msg, '*App nicht gefunden!*', true) | ||||
|     return | ||||
|   else | ||||
|     local output, image_url = app_store:send_appstore_data(data) | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
| 	if image_url then | ||||
| 	  utilities.send_typing(self, msg.chat.id, 'upload_photo') | ||||
| 	  local file = download_to_file(image_url) | ||||
| 	  utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id) | ||||
| 	end | ||||
|   end | ||||
| end | ||||
|  | ||||
| return app_store | ||||
							
								
								
									
										48
									
								
								otouto/plugins/bitly.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								otouto/plugins/bitly.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| local bitly = {} | ||||
|  | ||||
| local https = require('ssl.https') | ||||
| local json = require('dkjson') | ||||
| local utilities = require('otouto.utilities') | ||||
| local redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| function bitly:init(config) | ||||
|   if not cred_data.bitly_access_token then | ||||
|     print('Missing config value: bitly_access_token.') | ||||
|     print('bitly.lua will not be enabled.') | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   bitly.triggers = { | ||||
| 	"bit.ly/([A-Za-z0-9-_-]+)", | ||||
| 	"bitly.com/([A-Za-z0-9-_-]+)", | ||||
| 	"j.mp/([A-Za-z0-9-_-]+)", | ||||
| 	"andib.tk/([A-Za-z0-9-_-]+)" | ||||
|   } | ||||
| end | ||||
| 	 | ||||
| local BASE_URL = 'https://api-ssl.bitly.com/v3/expand' | ||||
|  | ||||
| function bitly: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.expand[1] | ||||
|   cache_data('bitly', shorturl, data) | ||||
|   return data.long_url | ||||
| end | ||||
|  | ||||
| function bitly:action(msg, config, matches) | ||||
|   local shorturl = matches[1] | ||||
|   local hash = 'telegram:cache:bitly:'..shorturl | ||||
|   if redis:exists(hash) == false then | ||||
|     utilities.send_reply(self, msg, bitly:expand_bitly_link(shorturl)) | ||||
|     return | ||||
|   else | ||||
|     local data = redis:hgetall(hash) | ||||
| 	utilities.send_reply(self, msg, data.long_url) | ||||
| 	return | ||||
|   end | ||||
| end | ||||
|  | ||||
| return bitly | ||||
							
								
								
									
										142
									
								
								otouto/plugins/bitly_create.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								otouto/plugins/bitly_create.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | ||||
| local bitly_create = {} | ||||
|  | ||||
| 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 bitly_create:init(config) | ||||
| 	if not cred_data.bitly_client_id then | ||||
| 		print('Missing config value: bitly_client_id.') | ||||
| 		print('bitly_create.lua will not be enabled.') | ||||
| 		return | ||||
| 	elseif not cred_data.bitly_client_secret then | ||||
| 		print('Missing config value: bitly_client_secret.') | ||||
| 		print('bitly_create.lua will not be enabled.') | ||||
| 		return | ||||
| 	elseif not cred_data.bitly_redirect_uri then | ||||
| 		print('Missing config value: bitly_redirect_uri.') | ||||
| 		print('bitly_create.lua will not be enabled.') | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
|     bitly_create.triggers = { | ||||
|     "^/short (auth) (.+)$", | ||||
|     "^/short (auth)$", | ||||
| 	"^/short (unauth)$", | ||||
| 	"^/short (me)$", | ||||
|   	"^/short (j.mp) (https?://[%w-_%.%?%.:/%+=&]+)$", | ||||
| 	"^/short (bit.ly) (https?://[%w-_%.%?%.:/%+=&]+)$", | ||||
| 	"^/short (bitly.com) (https?://[%w-_%.%?%.:/%+=&]+)$", | ||||
| 	"^/short (https?://[%w-_%.%?%.:/%+=&]+)$" | ||||
| 	} | ||||
| 	bitly_create.doc = [[* | ||||
| ]]..config.cmd_pat..[[short* _<Link>_: Kürzt einen Link mit der Standard Bitly-Adresse | ||||
| *]]..config.cmd_pat..[[short* _<j.mp|bit.ly|bitly.com>_ _[Link]_: Kürzt einen Link mit der ausgewählten Kurz-URL | ||||
| *]]..config.cmd_pat..[[short* _auth_: Loggt deinen Account ein und nutzt ihn für deine Links (empfohlen!) | ||||
| *]]..config.cmd_pat..[[short* _me_: Gibt den eingeloggten Account aus | ||||
| *]]..config.cmd_pat..[[short* _unauth_: Loggt deinen Account aus | ||||
| ]] | ||||
| end | ||||
|  | ||||
| bitly_create.command = 'short <URL>' | ||||
|  | ||||
| local BASE_URL = 'https://api-ssl.bitly.com' | ||||
|  | ||||
| local client_id = cred_data.bitly_client_id | ||||
| local client_secret = cred_data.bitly_client_secret | ||||
| local redirect_uri = cred_data.bitly_redirect_uri | ||||
|  | ||||
| function bitly_create:get_bitly_access_token(hash, code) | ||||
|   local req = post_petition(BASE_URL..'/oauth/access_token', 'client_id='..client_id..'&client_secret='..client_secret..'&code='..code..'&redirect_uri='..redirect_uri) | ||||
|   if not req.access_token then return '*Fehler beim Einloggen!*' end | ||||
|    | ||||
|   local access_token = req.access_token | ||||
|   local login_name = req.login | ||||
|   redis:hset(hash, 'bitly', access_token) | ||||
|   return 'Erfolgreich als `'..login_name..'` eingeloggt!' | ||||
| end | ||||
|  | ||||
| function bitly_create:get_bitly_user_info(bitly_access_token) | ||||
|   local url = BASE_URL..'/v3/user/info?access_token='..bitly_access_token..'&format=json' | ||||
|   local res,code  = https.request(url) | ||||
|   if code == 401 then return 'Login fehlgeschlagen!' end | ||||
|   if code ~= 200 then return 'HTTP-Fehler!' end | ||||
|    | ||||
|   local data = json.decode(res).data | ||||
|    | ||||
|   if data.full_name then | ||||
|     name = '*'..data.full_name..'* (`'..data.login..'`)' | ||||
|   else | ||||
|     name = '`'..data.login..'`' | ||||
|   end | ||||
|    | ||||
|   local text = 'Eingeloggt als '..name | ||||
|    | ||||
|   return text | ||||
| end | ||||
|  | ||||
| function bitly_create:create_bitlink (long_url, domain, bitly_access_atoken) | ||||
|   local url = BASE_URL..'/v3/shorten?access_token='..bitly_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 bitly_create:action(msg, config, matches) | ||||
|   local hash = 'user:'..msg.from.id | ||||
|   bitly_access_token = redis:hget(hash, 'bitly') | ||||
|    | ||||
|   if matches[1] == 'auth' and matches[2] then | ||||
|     utilities.send_reply(self, msg, bitly_create:get_bitly_access_token(hash, matches[2]), true) | ||||
|     return | ||||
|   end | ||||
|    | ||||
|   if matches[1] == 'auth' then | ||||
|     utilities.send_reply(self, msg, 'Bitte logge dich ein und folge den Anweisungen:\n[Bei Bitly anmelden](https://bitly.com/oauth/authorize?client_id='..client_id..'&redirect_uri='..redirect_uri..')', true) | ||||
|     return | ||||
|   end | ||||
|    | ||||
|   if matches[1] == 'unauth' and bitly_access_token then | ||||
|     redis:hdel(hash, 'bitly') | ||||
| 	utilities.send_reply(self, msg, '*Erfolgreich ausgeloggt!* Du kannst den Zugriff [in deinen Kontoeinstellungen](https://bitly.com/a/settings/connected) endgültig entziehen.', true) | ||||
| 	return | ||||
|   elseif matches[1] == 'unauth' and not bitly_access_token then | ||||
|     utilities.send_reply(self, msg, 'Wie willst du dich ausloggen, wenn du gar nicht eingeloggt bist?', true) | ||||
|     return | ||||
|   end | ||||
|    | ||||
|   if matches[1] == 'me' and bitly_access_token then | ||||
|     local text = bitly_create:get_bitly_user_info(bitly_access_token) | ||||
| 	if text then | ||||
| 	  utilities.send_reply(self, msg, text, true) | ||||
| 	  return | ||||
| 	else | ||||
| 	  return | ||||
| 	end | ||||
|   elseif matches[1] == 'me' and not bitly_access_token then | ||||
|     utilities.send_reply(self, msg, 'Du bist nicht eingeloggt! Logge dich ein mit\n/short auth', true) | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   if not bitly_access_token then | ||||
|     print('Not signed in, will use global bitly access_token') | ||||
|     bitly_access_token = cred_data.bitly_access_token | ||||
|   end | ||||
|    | ||||
|   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 | ||||
|   utilities.send_reply(self, msg, bitly_create:create_bitlink(long_url, domain, bitly_access_token)) | ||||
|   return | ||||
| end | ||||
|  | ||||
| return bitly_create | ||||
							
								
								
									
										125
									
								
								otouto/plugins/creds.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								otouto/plugins/creds.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| local creds_manager = {} | ||||
|  | ||||
| local utilities = require('otouto.utilities') | ||||
| local redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| function creds_manager:init(config) | ||||
|     creds_manager.triggers = { | ||||
|       "^(/creds)$", | ||||
| 	  "^(/creds add) ([^%s]+) (.+)$", | ||||
| 	  "^(/creds del) (.+)$", | ||||
| 	  "^(/creds rename) ([^%s]+) (.+)$" | ||||
| 	} | ||||
| 	creds_manager.doc = [[* | ||||
| ]]..config.cmd_pat..[[creds*: Zeigt alle Logindaten und API-Keys | ||||
| *]]..config.cmd_pat..[[creds* _add_ _<Variable>_ _<Schlüssel>_: Speichert Schlüssel mit dieser Variable ein | ||||
| *]]..config.cmd_pat..[[creds* _del_ _<Variable>_: Löscht Schlüssel mit dieser Variable | ||||
| *]]..config.cmd_pat..[[creds* _rename_ _<Variable>_ _<Neue Variable>_: Benennt Variable um, behält Schlüssel bei | ||||
| ]] | ||||
| end | ||||
|  | ||||
| creds_manager.command = 'creds' | ||||
|  | ||||
| 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 creds_manager:reload_creds() | ||||
|   cred_data = redis:hgetall(hash) | ||||
| end | ||||
|  | ||||
| function creds_manager:list_creds() | ||||
|   creds_manager: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 creds_manager:add_creds(var, key) | ||||
|   print('Saving credential for '..var..' to redis hash '..hash) | ||||
|   redis:hset(hash, var, key) | ||||
|   creds_manager:reload_creds() | ||||
|   return 'Gespeichert!' | ||||
| end | ||||
|  | ||||
| function creds_manager:del_creds(var) | ||||
|   if redis:hexists(hash, var) == true then | ||||
|     print('Deleting credential for '..var..' from redis hash '..hash) | ||||
|     redis:hdel(hash, var) | ||||
| 	creds_manager:reload_creds() | ||||
|     return 'Key von "'..var..'" erfolgreich gelöscht!' | ||||
|   else | ||||
|     return 'Du hast keine Logininformationen für diese Variable eingespeichert.' | ||||
|   end | ||||
| end | ||||
|  | ||||
| function creds_manager: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) | ||||
| 	  creds_manager: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 creds_manager:action(msg, config, matches) | ||||
|   local receiver = msg.from.id | ||||
|   if receiver ~= config.admin then | ||||
|     utilities.send_reply(self, msg, config.errors.sudo) | ||||
| 	return | ||||
|   end | ||||
|  | ||||
|   if msg.chat.type ~= 'private' then | ||||
|     utilities.send_reply(self, msg, 'Dieses Plugin solltest du nur [privat](http://telegram.me/' .. self.info.username .. '?start=creds) verwenden!', true) | ||||
|     return | ||||
|   end | ||||
|    | ||||
|   if matches[1] == "/creds" then | ||||
|     utilities.send_reply(self, msg, creds_manager:list_creds()) | ||||
|     return | ||||
|   elseif matches[1] == "/creds add" then | ||||
|     local var = string.lower(string.sub(matches[2], 1, 50)) | ||||
|     local key = string.sub(matches[3], 1, 1000) | ||||
| 	utilities.send_reply(self, msg, creds_manager:add_creds(var, key)) | ||||
|     return | ||||
|   elseif matches[1] == "/creds del" then | ||||
|     local var = string.lower(matches[2]) | ||||
| 	utilities.send_reply(self, msg, creds_manager:del_creds(var)) | ||||
|     return | ||||
|   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)) | ||||
| 	utilities.send_reply(self, msg, creds_manager:rename_creds(var, newvar)) | ||||
|     return | ||||
|   end | ||||
| end | ||||
|  | ||||
| return creds_manager | ||||
| @@ -1,56 +0,0 @@ | ||||
| local dice = {} | ||||
|  | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| dice.command = 'roll <nDr>' | ||||
|  | ||||
| function dice:init(config) | ||||
| 	dice.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('roll', true).table | ||||
| 	dice.doc = [[``` | ||||
| ]]..config.cmd_pat..[[roll <nDr> | ||||
| Returns a set of dice rolls, where n is the number of rolls and r is the range. If only a range is given, returns only one roll. | ||||
| ```]] | ||||
| end | ||||
|  | ||||
| function dice:action(msg) | ||||
|  | ||||
| 	local input = utilities.input(msg.text_lower) | ||||
| 	if not input then | ||||
| 		utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true) | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	local count, range | ||||
| 	if input:match('^[%d]+d[%d]+$') then | ||||
| 		count, range = input:match('([%d]+)d([%d]+)') | ||||
| 	elseif input:match('^d?[%d]+$') then | ||||
| 		count = 1 | ||||
| 		range = input:match('^d?([%d]+)$') | ||||
| 	else | ||||
| 		utilities.send_message(self, msg.chat.id, dice.doc, true, msg.message_id, true) | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	count = tonumber(count) | ||||
| 	range = tonumber(range) | ||||
|  | ||||
| 	if range < 2 then | ||||
| 		utilities.send_reply(self, msg, 'The minimum range is 2.') | ||||
| 		return | ||||
| 	end | ||||
| 	if range > 1000 or count > 1000 then | ||||
| 		utilities.send_reply(self, msg, 'The maximum range and count are 1000.') | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	local output = '*' .. count .. 'd' .. range .. '*\n`' | ||||
| 	for _ = 1, count do | ||||
| 		output = output .. math.random(range) .. '\t' | ||||
| 	end | ||||
| 	output = output .. '`' | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) | ||||
|  | ||||
| end | ||||
|  | ||||
| return dice | ||||
							
								
								
									
										37
									
								
								otouto/plugins/expand.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								otouto/plugins/expand.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| local expand = {} | ||||
|  | ||||
| local http = require('socket.http') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function expand:init(config) | ||||
|   expand.triggers = { | ||||
|     "^/expand (https?://[%w-_%.%?%.:/%+=&]+)$" | ||||
|   } | ||||
|  | ||||
|   expand.doc = [[* | ||||
| ]]..config.cmd_pat..[[expand* _<Kurz-URL>_: Verlängert Kurz-URL (301er/302er)]] | ||||
| end | ||||
|  | ||||
| expand.command = 'expand <Kurz-URL>' | ||||
|  | ||||
| function expand:action(msg, config, matches) | ||||
|    local response_body = {} | ||||
|    local request_constructor = { | ||||
|       url = matches[1], | ||||
|       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 | ||||
|       utilities.send_reply(self, msg, response_headers.location) | ||||
|       return | ||||
|    else | ||||
|       utilities.send_reply(self, msg, "Fehler beim Erweitern der URL.") | ||||
|       return | ||||
|    end | ||||
| end | ||||
|  | ||||
| return expand | ||||
							
								
								
									
										180
									
								
								otouto/plugins/facebook.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								otouto/plugins/facebook.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| local facebook = {} | ||||
|  | ||||
| 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 redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| function facebook:init(config) | ||||
| 	if not cred_data.fb_access_token then | ||||
| 		print('Missing config value: fb_access_token.') | ||||
| 		print('facebook.lua will not be enabled.') | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
|     facebook.triggers = { | ||||
| 	  "facebook.com/([A-Za-z0-9-._-]+)/(posts)/(%d+)", | ||||
| 	  "facebook.com/(permalink).php%?(story_fbid)=(%d+)&id=(%d+)", | ||||
|       "facebook.com/(photo).php%?fbid=(%d+)", | ||||
|       "facebook.com/([A-Za-z0-9-._-]+)/(photos)/a.(%d+[%d%.]*)/(%d+)", | ||||
|       "facebook.com/(video).php%?v=(%d+)", | ||||
| 	  "facebook.com/([A-Za-z0-9-._-]+)/(videos)/(%d+[%d%.]*)", | ||||
| 	  "facebook.com/([A-Za-z0-9-._-]+)" | ||||
| 	} | ||||
| end | ||||
|  | ||||
| local BASE_URL = 'https://graph.facebook.com/v2.5' | ||||
| local fb_access_token = cred_data.fb_access_token | ||||
|  | ||||
| local makeOurDate = function(dateString) | ||||
|   local pattern = "(%d+)%/(%d+)%/(%d+)" | ||||
|   local month, day, year = dateString:match(pattern) | ||||
|   return day..'.'..month..'.'..year | ||||
| end | ||||
|  | ||||
| function facebook:get_fb_id(name) | ||||
|   local url = BASE_URL..'/'..name..'?access_token='..fb_access_token..'&locale=de_DE' | ||||
|   local res,code  = https.request(url) | ||||
|   if code ~= 200 then return nil end | ||||
|   local data = json.decode(res) | ||||
|   return data.id | ||||
| end | ||||
|  | ||||
| function facebook:fb_post (id, story_id) | ||||
|   local url = BASE_URL..'/'..id..'_'..story_id..'?access_token='..fb_access_token..'&locale=de_DE&fields=from,name,story,message,link' | ||||
|   local res,code  = https.request(url) | ||||
|   if code ~= 200 then return nil end | ||||
|   local data = json.decode(res) | ||||
|    | ||||
|   local from = data.from.name | ||||
|   local message = data.message | ||||
|   local name = data.name | ||||
|   if data.link then | ||||
|     link = '\n'..data.name..':\n'..data.link | ||||
|   else | ||||
|     link = "" | ||||
|   end | ||||
|    | ||||
|   if data.story then | ||||
|     story = ' ('..data.story..')' | ||||
|   else | ||||
|     story = "" | ||||
|   end | ||||
|    | ||||
|   local text = '*'..from..'*'..story..':\n'..message..'\n'..link | ||||
|   return text | ||||
| end | ||||
|  | ||||
| function facebook:send_facebook_photo(photo_id, receiver) | ||||
|   local url = BASE_URL..'/'..photo_id..'?access_token='..fb_access_token..'&locale=de_DE&fields=images,from,name' | ||||
|   local res,code  = https.request(url) | ||||
|   if code ~= 200 then return nil end | ||||
|   local data = json.decode(res) | ||||
|    | ||||
|   local from = '*'..data.from.name..'*' | ||||
|   if data.name then | ||||
|     text = from..' hat ein Bild gepostet:\n'..data.name | ||||
|   else | ||||
|     text = from..' hat ein Bild gepostet:' | ||||
|   end | ||||
|   local image_url = data.images[1].source | ||||
|   return text, image_url | ||||
| end | ||||
|  | ||||
| function facebook:send_facebook_video(video_id) | ||||
|   local url = BASE_URL..'/'..video_id..'?access_token='..fb_access_token..'&locale=de_DE&fields=description,from,source,title' | ||||
|   local res,code  = https.request(url) | ||||
|   if code ~= 200 then return nil end | ||||
|   local data = json.decode(res) | ||||
|  | ||||
|   local from = '*'..data.from.name..'*' | ||||
|   local description = data.description | ||||
|   local source = data.source | ||||
|   if data.title then | ||||
|     text = from..' hat ein Video gepostet:\n'..description..'\n['..data.title..']('..source..')' | ||||
|   else | ||||
|     text = from..' hat ein Video gepostet:\n'..description..'\n'..utilities.md_escape(source) | ||||
|   end | ||||
|   return text | ||||
| end | ||||
|  | ||||
| function facebook:facebook_info(name) | ||||
|   local url = BASE_URL..'/'..name..'?access_token='..fb_access_token..'&locale=de_DE&fields=about,name,birthday,category,founded,general_info,is_verified' | ||||
|   local res,code  = https.request(url) | ||||
|   if code ~= 200 then return nil end | ||||
|   local data = json.decode(res) | ||||
|    | ||||
|   local name = data.name | ||||
|   if data.is_verified then | ||||
|     name = name..' ✅' | ||||
|   end | ||||
|    | ||||
|   local category = data.category | ||||
|    | ||||
|   if data.about then | ||||
|     about = '\n'..data.about | ||||
|   else | ||||
|     about = "" | ||||
|   end | ||||
|    | ||||
|   if data.general_info then | ||||
|     general_info = '\n'..data.general_info | ||||
|   else | ||||
|     general_info = "" | ||||
|   end | ||||
|    | ||||
|   if data.birthday and data.founded then | ||||
|     birth = '\nGeburtstag: '..makeOurDate(data.birthday) | ||||
|   elseif data.birthday and not data.founded then | ||||
|     birth = '\nGeburtstag: '..makeOurDate(data.birthday) | ||||
|   elseif data.founded and not data.birthday then | ||||
|     birth = '\nGegründet: '..data.founded | ||||
|   else | ||||
|     birth = "" | ||||
|   end | ||||
|    | ||||
|   local text = '*'..name..'* ('..category..')_'..about..'_'..general_info..birth | ||||
|   return text | ||||
| end | ||||
|  | ||||
| function facebook:action(msg, config, matches) | ||||
|   if matches[1] == 'permalink' or matches[2] == 'posts' then | ||||
|     story_id = matches[3] | ||||
|     if not matches[4] then | ||||
| 	  id = facebook:get_fb_id(matches[1]) | ||||
| 	else | ||||
| 	  id = matches[4] | ||||
| 	end | ||||
| 	utilities.send_reply(self, msg, facebook:fb_post(id, story_id), true) | ||||
|     return | ||||
|   elseif matches[1] == 'photo' or matches[2] == 'photos' then | ||||
|     if not matches[4] then | ||||
|       photo_id = matches[2] | ||||
|     else | ||||
|       photo_id = matches[4] | ||||
|     end | ||||
| 	utilities.send_typing(self, msg.chat.id, 'upload_photo') | ||||
|     local text, image_url = facebook:send_facebook_photo(photo_id, receiver) | ||||
| 	local file = download_to_file(image_url) | ||||
| 	utilities.send_reply(self, msg, text, true) | ||||
| 	utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id) | ||||
| 	return | ||||
|   elseif matches[1] == 'video' or matches[2] == 'videos' then | ||||
|     if not matches[3] then | ||||
|       video_id = matches[2] | ||||
|     else | ||||
|       video_id = matches[3] | ||||
|     end | ||||
|     local output = facebook:send_facebook_video(video_id) | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
| 	return | ||||
|   else | ||||
|     utilities.send_reply(self, msg, facebook:facebook_info(matches[1]), true) | ||||
|     return | ||||
|   end | ||||
| end | ||||
|  | ||||
| return facebook | ||||
							
								
								
									
										222
									
								
								otouto/plugins/forecast.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								otouto/plugins/forecast.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,222 @@ | ||||
| local forecast = {} | ||||
|  | ||||
| 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 redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| function forecast:init(config) | ||||
| 	if not cred_data.forecastio_apikey then | ||||
| 		print('Missing config value: forecastio_apikey.') | ||||
| 		print('weather.lua will not be enabled.') | ||||
| 		return | ||||
| 	elseif not cred_data.google_apikey then | ||||
| 		print('Missing config value: google_apikey.') | ||||
| 		print('weather.lua will not be enabled.') | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
|    forecast.triggers = { | ||||
|     "^(/f)$", | ||||
| 	"^(/f) (.*)$", | ||||
| 	"^(/fh)$", | ||||
| 	"^(/fh) (.*)$", | ||||
| 	"^(/forecast)$", | ||||
| 	"^(/forecast) (.*)$", | ||||
| 	"^(/forecasth)$", | ||||
| 	"^(/forecasth) (.*)$" | ||||
| 	} | ||||
| 	forecast.doc = [[* | ||||
| ]]..config.cmd_pat..[[f*:  Wettervorhersage für deinen Wohnort _(/location set <Ort>)_ | ||||
| *]]..config.cmd_pat..[[f* _<Ort>_: Wettervorhersage für diesen Ort | ||||
| *]]..config.cmd_pat..[[fh*: 24-Stunden-Wettervorhersage für deine Stadt _(/location set [Ort]_ | ||||
| *]]..config.cmd_pat..[[fh* _<Ort>_: 24-Stunden-Wettervorhersage für diesen Ort | ||||
| ]] | ||||
| end | ||||
|  | ||||
| forecast.command = 'forecast' | ||||
|  | ||||
| local BASE_URL = "https://api.forecast.io/forecast" | ||||
| local apikey = cred_data.forecastio_apikey | ||||
| local google_apikey = cred_data.google_apikey | ||||
|  | ||||
| function get_city_name(lat, lng) | ||||
|   local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng) | ||||
|   if city then return city end | ||||
|   local url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='..lat..','..lng..'&result_type=political&language=de&key='..google_apikey | ||||
|   local res, code = HTTPS.request(url) | ||||
|   if code ~= 200 then return 'Unbekannte Stadt' end | ||||
|   local data = JSON.decode(res).results[1] | ||||
|   local city = data.formatted_address | ||||
|   print('Setting '..lat..','..lng..' in redis hash telegram:cache:weather:pretty_names to "'..city..'"') | ||||
|   redis:hset('telegram:cache:weather:pretty_names', lat..','..lng, city) | ||||
|   return city | ||||
| end | ||||
|  | ||||
| function get_condition_symbol(weather, n) | ||||
|   if weather.data[n].icon == 'clear-day' then | ||||
| 	return '☀️' | ||||
|   elseif weather.data[n].icon == 'clear-night' then | ||||
|     return '🌙' | ||||
|   elseif weather.data[n].icon == 'rain' then | ||||
|     return '☔️' | ||||
|   elseif weather.data[n].icon == 'snow' then | ||||
| 	return '❄️' | ||||
|   elseif weather.data[n].icon == 'sleet' then | ||||
|     return '🌨' | ||||
|   elseif weather.data[n].icon == 'wind' then | ||||
|     return '💨' | ||||
|   elseif weather.data[n].icon == 'fog' then | ||||
|     return '🌫' | ||||
|   elseif weather.data[n].icon == 'cloudy' then | ||||
|     return '☁️☁️' | ||||
|   elseif weather.data[n].icon == 'partly-cloudy-day' then | ||||
|     return '🌤' | ||||
|   elseif weather.data[n].icon == 'partly-cloudy-night' then | ||||
|     return '🌙☁️' | ||||
|   else | ||||
|     return '' | ||||
|   end | ||||
| end | ||||
|  | ||||
| function get_temp(weather, n, hourly) | ||||
|   if hourly then | ||||
|     local temperature = string.gsub(round(weather.data[n].temperature, 1), "%.", ",") | ||||
| 	local condition = weather.data[n].summary | ||||
| 	return temperature..'°C | '..get_condition_symbol(weather, n)..' '..condition | ||||
|   else | ||||
|     local day = string.gsub(round(weather.data[n].temperatureMax, 1), "%.", ",") | ||||
|     local night = string.gsub(round(weather.data[n].temperatureMin, 1), "%.", ",") | ||||
|     local condition = weather.data[n].summary | ||||
|     return '☀️ '..day..'°C | 🌙 '..night..'°C | '..get_condition_symbol(weather, n)..' '..condition | ||||
|   end | ||||
| end | ||||
|  | ||||
| function forecast:get_forecast(lat, lng) | ||||
|   print('Finde Wetter in '..lat..', '..lng) | ||||
|   local text = redis:get('telegram:cache:forecast:'..lat..','..lng) | ||||
|   if text then print('...aus dem Cache..') return text end | ||||
|  | ||||
|   local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,hourly,alerts,flags' | ||||
|    | ||||
|   local response_body = {} | ||||
|   local request_constructor = { | ||||
|       url = url, | ||||
|       method = "GET", | ||||
|       sink = ltn12.sink.table(response_body) | ||||
|    } | ||||
|   local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor) | ||||
|   if not ok then return nil end | ||||
|   local data = JSON.decode(table.concat(response_body)) | ||||
|   local ttl = string.sub(response_headers["cache-control"], 9) | ||||
|  | ||||
|    | ||||
|   local weather = data.daily | ||||
|   local city = get_city_name(lat, lng) | ||||
|    | ||||
|   local header = '*Vorhersage für '..city..':*\n_'..weather.summary..'_\n' | ||||
|    | ||||
|   local text = '*Heute:* '..get_temp(weather, 1) | ||||
|   local text = text..'\n*Morgen:* '..get_temp(weather, 2) | ||||
|    | ||||
|   for day in pairs(weather.data) do | ||||
| 	if day > 2 then  | ||||
| 	  text = text..'\n*'..convert_timestamp(weather.data[day].time, '%a, %d.%m')..'*: '..get_temp(weather, day) | ||||
|     end | ||||
|   end | ||||
|    | ||||
|   local text = string.gsub(text, "Mon", "Mo") | ||||
|   local text = string.gsub(text, "Tue", "Di") | ||||
|   local text = string.gsub(text, "Wed", "Mi") | ||||
|   local text = string.gsub(text, "Thu", "Do") | ||||
|   local text = string.gsub(text, "Fri", "Fr") | ||||
|   local text = string.gsub(text, "Sat", "Sa") | ||||
|   local text = string.gsub(text, "Sun", "So") | ||||
|    | ||||
|   cache_data('forecast', lat..','..lng, header..text, tonumber(ttl), 'key') | ||||
|    | ||||
|   return header..text | ||||
| end | ||||
|  | ||||
| function forecast:get_forecast_hourly(lat, lng) | ||||
|   print('Finde stündliches Wetter in '..lat..', '..lng) | ||||
|   local text = redis:get('telegram:cache:forecast:'..lat..','..lng..':hourly') | ||||
|   if text then print('...aus dem Cache..') return text end | ||||
|  | ||||
|   local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,daily,alerts,flags' | ||||
|    | ||||
|   local response_body = {} | ||||
|   local request_constructor = { | ||||
|       url = url, | ||||
|       method = "GET", | ||||
|       sink = ltn12.sink.table(response_body) | ||||
|    } | ||||
|   local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor) | ||||
|   if not ok then return nil end | ||||
|   local data = JSON.decode(table.concat(response_body)) | ||||
|   local ttl = string.sub(response_headers["cache-control"], 9) | ||||
|  | ||||
|    | ||||
|   local weather = data.hourly | ||||
|   local city = get_city_name(lat, lng) | ||||
|    | ||||
|   local header = '*24-Stunden-Vorhersage für '..city..':*\n_'..weather.summary..'_' | ||||
|   local text = "" | ||||
|    | ||||
|   for hour in pairs(weather.data) do | ||||
| 	if hour < 26 then  | ||||
| 	  text = text..'\n*'..convert_timestamp(weather.data[hour].time, '%H:%M Uhr')..'* | '..get_temp(weather, hour, true) | ||||
| 	end | ||||
|   end | ||||
|    | ||||
|   cache_data('forecast', lat..','..lng..':hourly', header..text, tonumber(ttl), 'key') | ||||
|    | ||||
|   return header..text | ||||
| end | ||||
|  | ||||
| function forecast:action(msg, config, matches) | ||||
|   local user_id = msg.from.id | ||||
|   local city = get_location(user_id) | ||||
|    | ||||
|   if matches[2] then | ||||
|     city = matches[2] | ||||
|   else | ||||
|     local set_location = get_location(user_id) | ||||
| 	if not set_location then | ||||
| 	  city = 'Berlin, Deutschland' | ||||
| 	else | ||||
| 	  city = set_location | ||||
| 	end | ||||
|   end | ||||
|    | ||||
|   local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') | ||||
|   local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng') | ||||
|   if not lat and not lng then | ||||
|     print('Koordinaten nicht eingespeichert, frage Google...') | ||||
|     coords = utilities.get_coords(city, config) | ||||
| 	lat = coords.lat | ||||
| 	lng = coords.lon | ||||
|   end | ||||
|    | ||||
|   if not lat and not lng then | ||||
|     utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true) | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat) | ||||
|   redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng) | ||||
|    | ||||
|   if matches[1] == '/forecasth' or matches[1] == '/fh' then | ||||
|     text = forecast:get_forecast_hourly(lat, lng) | ||||
|   else | ||||
|     text = forecast:get_forecast(lat, lng) | ||||
|   end | ||||
|   if not text then | ||||
|     text = '*Konnte die Wettervorhersage für diese Stadt nicht bekommen.*' | ||||
|   end | ||||
|   utilities.send_reply(self, msg, text, true) | ||||
| end | ||||
|  | ||||
| return forecast | ||||
| @@ -21,11 +21,10 @@ function gImages:init(config) | ||||
| 	end | ||||
|  | ||||
| 	gImages.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('img', true):t('i', true):t('insfw', true).table | ||||
| 	gImages.doc = [[``` | ||||
| ]]..config.cmd_pat..[[img <Suchbegriff> | ||||
| 	gImages.doc = [[* | ||||
| ]]..config.cmd_pat..[[img* _<Suchbegriff>_ | ||||
| Sucht Bild mit Google und versendet es (SafeSearch aktiv) | ||||
| Alias: ]]..config.cmd_pat..[[i | ||||
| ```]] | ||||
| Alias: *]]..config.cmd_pat..[[i*]] | ||||
| end | ||||
|  | ||||
| gImages.command = 'img <Suchbegriff>' | ||||
| @@ -47,6 +46,7 @@ function gImages:action(msg, config) | ||||
| 	return | ||||
|   end | ||||
|  | ||||
|   utilities.send_typing(self, msg.chat.id, 'upload_photo') | ||||
|   local apikey = cred_data.google_apikey | ||||
|   local cseid = cred_data.google_cse_id | ||||
|   local BASE_URL = 'https://www.googleapis.com/customsearch/v1' | ||||
| @@ -68,9 +68,7 @@ function gImages:action(msg, config) | ||||
|   local img_url = jdat.items[i].link | ||||
|    | ||||
|   local file = download_to_file(img_url) | ||||
|   bindings.sendPhoto(self, {chat_id = msg.chat.id, caption = img_url}, {photo = file} ) | ||||
|   os.remove(file) | ||||
|   print("Deleted: "..file) | ||||
|   utilities.send_photo(self, msg.chat.id, file, img_url) | ||||
| end | ||||
|  | ||||
| return gImages | ||||
|   | ||||
							
								
								
									
										77
									
								
								otouto/plugins/github.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								otouto/plugins/github.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| local github = {} | ||||
|  | ||||
| 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 redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| function github:init(config) | ||||
|     github.triggers = { | ||||
|       "github.com/([A-Za-z0-9-_-.-._.]+)/([A-Za-z0-9-_-.-._.]+)/commit/([a-z0-9-]+)", | ||||
|       "github.com/([A-Za-z0-9-_-.-._.]+)/([A-Za-z0-9-_-.-._.]+)/?$" | ||||
| 	} | ||||
| end | ||||
|  | ||||
| local BASE_URL = 'https://api.github.com' | ||||
|  | ||||
| function github:get_gh_data(gh_code, gh_commit_sha) | ||||
|   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 github:send_github_data(data) | ||||
|   if not data.owner then return nil end | ||||
|   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 = '\n[Homepage besuchen]('..data.homepage..')' | ||||
|   end | ||||
|   local text = name..' von '..owner..'\n'..description..'\n`git clone '..clone_url..'`'..language..issues..homepage | ||||
|   return text | ||||
| end | ||||
|  | ||||
| function github:send_gh_commit_data(gh_code, gh_commit_sha, data) | ||||
|   if not data.committer then return nil end | ||||
|   local committer = data.committer.name | ||||
|   local message = data.message | ||||
|   local text = '`'..gh_code..'@'..gh_commit_sha..'` von *'..committer..'*:\n'..message | ||||
|   return text | ||||
| end | ||||
|  | ||||
| function github:action(msg, config, matches) | ||||
|   local gh_code = matches[1]..'/'..matches[2] | ||||
|   local gh_commit_sha = matches[3] | ||||
|   local data = github:get_gh_data(gh_code, gh_commit_sha) | ||||
|   if not gh_commit_sha then | ||||
|     output = github:send_github_data(data) | ||||
|   else | ||||
|     output = github:send_gh_commit_data(gh_code, gh_commit_sha, data) | ||||
|   end | ||||
|   utilities.send_reply(self, msg, output, true) | ||||
| end | ||||
|  | ||||
| return github | ||||
							
								
								
									
										16
									
								
								otouto/plugins/images.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								otouto/plugins/images.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| local images = {} | ||||
|  | ||||
| local utilities = require('otouto.utilities') | ||||
| images.triggers = { | ||||
|   "(https?://[%w-_%%%.%?%.:,/%+=~&%[%]]+%.[Pp][Nn][Gg])$", | ||||
|   "(https?://[%w-_%%%.%?%.:,/%+=~&%[%]]+%.[Jj][Pp][Ee]?[Gg])$" | ||||
| } | ||||
|  | ||||
| function images:action(msg) | ||||
|    utilities.send_typing(self, msg.chat.id, 'upload_photo') | ||||
|    local url = matches[1] | ||||
|    local file = download_to_file(url) | ||||
|    utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id) | ||||
| end | ||||
|  | ||||
| return images | ||||
| @@ -51,7 +51,7 @@ function imdb:action(msg, config) | ||||
| 	 | ||||
| 	if jdat.Poster ~= "N/A" then | ||||
| 	  local file = download_to_file(jdat.Poster) | ||||
|       bindings.sendPhoto(self, {chat_id = msg.chat.id}, {photo = file} ) | ||||
| 	  utilities.send_photo(self, msg.chat.id, file) | ||||
| 	end | ||||
|  | ||||
| end | ||||
|   | ||||
							
								
								
									
										69
									
								
								otouto/plugins/location_manager.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								otouto/plugins/location_manager.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| local loc_manager = {} | ||||
|  | ||||
| local utilities = require('otouto.utilities') | ||||
| local redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| function loc_manager:init(config) | ||||
|     loc_manager.triggers = { | ||||
| 	  "^/location (set) (.*)$", | ||||
|       "^/location (del)$", | ||||
| 	  "^/location$" | ||||
| 	} | ||||
| 	loc_manager.doc = [[* | ||||
| ]]..config.cmd_pat..[[location*: Gibt deinen gesetzten Wohnort aus | ||||
| *]]..config.cmd_pat..[[location* _set_ _<Ort>_: Setzt deinen Wohnort auf diesen Ort | ||||
| *]]..config.cmd_pat..[[location* _del_: Löscht deinen angegebenen Wohnort | ||||
| ]] | ||||
| end | ||||
|  | ||||
| loc_manager.command = 'location' | ||||
|  | ||||
| function loc_manager: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 | ||||
|  | ||||
| function loc_manager: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 gelöscht!' | ||||
|   end | ||||
| end | ||||
|  | ||||
| function loc_manager:action(msg, config, matches) | ||||
|   local user_id = msg.from.id | ||||
|    | ||||
|   if matches[1] == 'set' then | ||||
| 	utilities.send_reply(self, msg, loc_manager:set_location(user_id, matches[2]), true) | ||||
| 	return | ||||
|   elseif matches[1] == 'del' then | ||||
|     utilities.send_reply(self, msg, loc_manager:del_location(user_id), true) | ||||
|     return | ||||
|   else | ||||
|     local set_location = get_location(user_id) | ||||
|     if not set_location then | ||||
| 	  utilities.send_reply(self, msg, '*Du hast keinen Ort gesetzt!*', true) | ||||
|       return | ||||
|     else | ||||
| 	  local coords = utilities.get_coords(set_location, config) | ||||
| 	  utilities.send_location(self, msg.chat.id, coords.lat, coords.lon, msg.message_id) | ||||
| 	  utilities.send_reply(self, msg, 'Gesetzter Wohnort: *'..set_location..'*', true) | ||||
| 	  return | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| return loc_manager | ||||
							
								
								
									
										67
									
								
								otouto/plugins/media.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								otouto/plugins/media.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| local media = {} | ||||
|  | ||||
| local utilities = require('otouto.utilities') | ||||
| local mimetype = (loadfile "./otouto/mimetype.lua")() | ||||
|  | ||||
| media.triggers = { | ||||
|     	"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(gif))$", | ||||
|     	"^(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(mp4))$", | ||||
|     	"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(pdf))$", | ||||
|     	"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(ogg))$", | ||||
|     	"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(zip))$", | ||||
|         "(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(tar.gz))$", | ||||
|         "(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(7z))$", | ||||
|     	"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(mp3))$", | ||||
|     	"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(rar))$", | ||||
|     	"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(wmv))$", | ||||
|     	"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(doc))$", | ||||
|     	"^(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(avi))$", | ||||
| 		"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(wav))$", | ||||
| 		"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(apk))$", | ||||
| 		"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(webm))$", | ||||
| 		"^(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(ogv))$", | ||||
| 		"(https?://[%w-_%.%?%.:,/%+=&%[%]]+%.(webp))$" | ||||
| } | ||||
|  | ||||
| function media:action(msg) | ||||
|   local url = matches[1] | ||||
|   local ext = matches[2] | ||||
|   local receiver = msg.chat.id | ||||
|  | ||||
|   utilities.send_typing(self, receiver, 'upload_document') | ||||
|   local file = download_to_file(url) | ||||
|   local mime_type = mimetype.get_content_type_no_sub(ext) | ||||
|  | ||||
|   if ext == 'gif' then | ||||
|     print('send gif') | ||||
|     utilities.send_document(self, receiver, file, nil, msg.message_id) | ||||
| 	return | ||||
|  | ||||
|   elseif mime_type == 'text' then | ||||
|     print('send_document') | ||||
|     utilities.send_document(self, receiver, file, nil, msg.message_id) | ||||
| 	return | ||||
|    | ||||
|   elseif mime_type == 'image' then | ||||
|     print('send_photo') | ||||
|     utilities.send_photo(self, receiver, file, nil, msg.message_id) | ||||
| 	return | ||||
|    | ||||
|   elseif mime_type == 'audio' then | ||||
|     print('send_audio') | ||||
|     utilities.send_audio(self, receiver, file, nil, msg.message_id) | ||||
| 	return | ||||
|  | ||||
|   elseif mime_type == 'video' then | ||||
|     print('send_video') | ||||
| 	utilities.send_video(self, receiver, file, nil, msg.message_id) | ||||
| 	return | ||||
|    | ||||
|   else | ||||
|     print('send_file') | ||||
|     utilities.send_document(self, receiver, file, nil, msg.message_id) | ||||
| 	return | ||||
|   end | ||||
| end | ||||
|  | ||||
| return media | ||||
							
								
								
									
										45
									
								
								otouto/plugins/pasteee.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								otouto/plugins/pasteee.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| local pasteee = {} | ||||
|  | ||||
| local bot = require('otouto.bot') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function pasteee:init(config) | ||||
| 	if not cred_data.pasteee_key then | ||||
| 		print('Missing config value: pasteee_key.') | ||||
| 		print('pasteee.lua will not be enabled, listquotes won\'t be available.') | ||||
| 		return | ||||
| 	end | ||||
| 	 | ||||
|     pasteee.triggers = { | ||||
|     "^/pasteee (.*)$" | ||||
| 	} | ||||
| 	pasteee.doc = [[* | ||||
| ]]..config.cmd_pat..[[pasteee* _<Text>_: Postet Text auf Paste.ee]] | ||||
| end | ||||
|  | ||||
| pasteee.command = 'pasteee <Text>' | ||||
|  | ||||
| local key = cred_data.pasteee_key | ||||
|  | ||||
| function upload(text, noraw) | ||||
|   local url = "https://paste.ee/api" | ||||
|   local pet = post_petition(url, 'key='..key..'&paste='..text..'&format=json') | ||||
|   if pet.status ~= 'success' then return 'Ein Fehler ist aufgetreten: '..pet.error, true end | ||||
|   if noraw then | ||||
|     return pet.paste.link | ||||
|   else | ||||
|     return pet.paste.raw | ||||
|   end | ||||
| end | ||||
|  | ||||
| function pasteee:action(msg, config, matches) | ||||
|   local text = matches[1] | ||||
|   local link, iserror = upload(text) | ||||
|   if iserror then | ||||
|     utilities.send_reply(self, msg, link) | ||||
| 	return | ||||
|   end | ||||
|   utilities.send_reply(self, msg, '[Text auf Paste.ee ansehen]('..link..')', true) | ||||
| end | ||||
|  | ||||
| return pasteee | ||||
| @@ -7,7 +7,7 @@ patterns.triggers = { | ||||
| } | ||||
|  | ||||
| function patterns:action(msg) | ||||
| 	if not msg.reply_to_message then return end | ||||
| 	if not msg.reply_to_message then return true end | ||||
| 	local output = msg.reply_to_message.text | ||||
| 	if msg.reply_to_message.from.id == self.info.id then | ||||
| 		output = output:gsub('Did you mean:\n"', '') | ||||
| @@ -22,8 +22,7 @@ function patterns:action(msg) | ||||
| 		end | ||||
| 	) | ||||
| 	if res == false then | ||||
| 		output = 'Malformed pattern!' | ||||
| 		utilities.send_reply(self, msg, output) | ||||
| 		utilities.send_reply(self, msg, 'Malformed pattern!') | ||||
| 	else | ||||
| 		output = output:sub(1, 4000) | ||||
| 		output = 'Did you mean:\n"' .. output .. '"' | ||||
|   | ||||
							
								
								
									
										111
									
								
								otouto/plugins/quotes.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								otouto/plugins/quotes.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| local quotes = {} | ||||
|  | ||||
| local bot = require('otouto.bot') | ||||
| local utilities = require('otouto.utilities') | ||||
| local redis = (loadfile "./otouto/redis.lua")() | ||||
| require("./otouto/plugins/pasteee") | ||||
|  | ||||
| function quotes:init(config) | ||||
|     quotes.triggers = { | ||||
|     "^/(delquote) (.+)$", | ||||
|     "^/(addquote) (.+)$", | ||||
|     "^/(quote)$", | ||||
| 	"^/(listquotes)$" | ||||
| 	} | ||||
| 	quotes.doc = [[* | ||||
| ]]..config.cmd_pat..[[addquote* _<Zitat>_: Fügt Zitat hinzu. | ||||
| *]]..config.cmd_pat..[[delquote* _<Zitat>_: Löscht das Zitat (nur Superuser) | ||||
| *]]..config.cmd_pat..[[quote*: Gibt zufälliges Zitat aus | ||||
| *]]..config.cmd_pat..[[listquotes*: Listet alle Zitate auf | ||||
| ]] | ||||
| end | ||||
|  | ||||
| quotes.command = 'quote' | ||||
|  | ||||
| function quotes:save_quote(msg) | ||||
|   if msg.text:sub(11):isempty() then | ||||
|     return "Benutzung: /addquote [Zitat]" | ||||
|   end | ||||
|    | ||||
|   local quote = msg.text:sub(11) | ||||
|   local hash = get_redis_hash(msg, 'quotes') | ||||
|   print('Saving quote to redis set '..hash) | ||||
|   redis:sadd(hash, quote) | ||||
|   return '*Gespeichert!*' | ||||
| end | ||||
|  | ||||
| function quotes:delete_quote(msg) | ||||
|   if msg.text:sub(11):isempty() then | ||||
|     return "Benutzung: /delquote [Zitat]" | ||||
|   end | ||||
|    | ||||
|   local quote = msg.text:sub(11) | ||||
|   local hash = get_redis_hash(msg, 'quotes') | ||||
|   print('Deleting quote from redis set '..hash) | ||||
|   if redis:sismember(hash, quote) == true then | ||||
|     redis:srem(hash, quote) | ||||
|     return '*Zitat erfolgreich gelöscht!*' | ||||
|   else | ||||
|     return 'Dieses Zitat existiert nicht.' | ||||
|   end | ||||
| end | ||||
|  | ||||
| function quotes:get_quote(msg) | ||||
|   local hash = get_redis_hash(msg, 'quotes') | ||||
|    | ||||
|   if hash then | ||||
|     print('Getting quote from redis set '..hash) | ||||
|   	local quotes_table = redis:smembers(hash) | ||||
| 	if not quotes_table[1] then | ||||
| 	  return 'Es wurden noch keine Zitate gespeichert.\nSpeichere doch welche mit /addquote [Zitat]' | ||||
| 	else | ||||
| 	  return quotes_table[math.random(1,#quotes_table)] | ||||
| 	end | ||||
|   end | ||||
| end | ||||
|  | ||||
| function quotes:list_quotes(msg) | ||||
|   local hash = get_redis_hash(msg, 'quotes') | ||||
|    | ||||
|   if hash then | ||||
|     print('Getting quotes from redis set '..hash) | ||||
|     local quotes_table = redis:smembers(hash) | ||||
| 	local text = "" | ||||
|     for num,quote in pairs(quotes_table) do | ||||
|       text = text..num..") "..quote..'\n' | ||||
|     end | ||||
| 	if not text or text == "" then | ||||
| 	  return 'Es wurden noch keine Zitate gespeichert.\nSpeichere doch welche mit !addquote [Zitat]' | ||||
| 	else | ||||
| 	  return upload(text) | ||||
| 	end | ||||
|   end | ||||
| end | ||||
|  | ||||
| function quotes:action(msg, config, matches) | ||||
|   if matches[1] == "quote" then | ||||
|     utilities.send_message(self, msg.chat.id, quotes:get_quote(msg), true) | ||||
|     return | ||||
|   elseif matches[1] == "addquote" and matches[2] then | ||||
|     utilities.send_reply(self, msg, quotes:save_quote(msg), true) | ||||
|     return | ||||
|   elseif matches[1] == "delquote" and matches[2] then | ||||
|     if msg.from.id ~= config.admin then | ||||
|       utilities.send_reply(self, msg, config.errors.sudo) | ||||
| 	  return | ||||
|     end | ||||
| 	  utilities.send_reply(self, msg, quotes:delete_quote(msg), true) | ||||
| 	  return | ||||
|   elseif matches[1] == "listquotes" then | ||||
|     local link, iserror = quotes:list_quotes(msg) | ||||
| 	if iserror then | ||||
|       utilities.send_reply(self, msg, link) | ||||
| 	  return | ||||
|     end | ||||
|     utilities.send_reply(self, msg, '[Lise aller Zitate auf Paste.ee ansehen]('..link..')', true) | ||||
|     return | ||||
|   end | ||||
|   utilities.send_reply(self, msg, quotes.doc, true) | ||||
| end | ||||
|  | ||||
| return quotes | ||||
							
								
								
									
										86
									
								
								otouto/plugins/respond.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								otouto/plugins/respond.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| local respond = {} | ||||
|  | ||||
| local https = require('ssl.https') | ||||
| local utilities = require('otouto.utilities') | ||||
| local bindings = require('otouto.bindings') | ||||
|  | ||||
| function respond:init(config) | ||||
|     respond.triggers = { | ||||
| 	"([Ff][Gg][Tt].? [Ss][Ww][Ii][Ff][Tt])", | ||||
| 	"([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee][Ss])", | ||||
| 	"([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee][Rr])", | ||||
| 	"([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee])", | ||||
| 	"^[Bb][Oo][Tt]%??$", | ||||
| 	"^/([Ll][Oo][Dd])$", | ||||
| 	"^/([Ll][Ff])$", | ||||
| 	"^/([Kk][Aa])$", | ||||
| 	"^/([Ii][Dd][Kk])$", | ||||
| 	"^/([Nn][Bb][Cc])$", | ||||
| 	"^/([Ii][Dd][Cc])$", | ||||
| 	"^%*([Ff][Rr][Oo][Ss][Cc][Hh])%*", | ||||
| 	"^/([Ff][Rr][Oo][Ss][Cc][Hh])$", | ||||
| 	"^%(([Ii][Nn][Ll][Oo][Vv][Ee])%)$", | ||||
| 	"^/[Ww][Aa][Tt]$" | ||||
| 	} | ||||
| end | ||||
|  | ||||
| respond.command = 'lod, /lf, /nbc, /wat' | ||||
|  | ||||
| function respond:action(msg, config, matches) | ||||
|   local user_name = get_name(msg) | ||||
|   local receiver = msg.chat.id | ||||
|   local GDRIVE_URL = 'https://de2319bd4b4b51a5ef2939a7638c1d35646f49f8.googledrive.com/host/0B_mfIlDgPiyqU25vUHZqZE9IUXc' | ||||
|   if user_name == "DefenderX" then user_name = "Deffu" end | ||||
| 	 | ||||
|   if string.match(msg.text, "[Ff][Gg][Tt].? [Ss][Ww][Ii][Ff][Tt]") then | ||||
|     utilities.send_message(self, receiver, 'Dünnes Eis, '..user_name..'!') | ||||
| 	return | ||||
|   elseif string.match(msg.text, "([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee][Ss])") then | ||||
|     utilities.send_message(self, receiver, '*einziges') | ||||
| 	return | ||||
|   elseif string.match(msg.text, "([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee][Rr])") then | ||||
|     utilities.send_message(self, receiver, '*einziger') | ||||
|     return | ||||
|   elseif string.match(msg.text, "([Ee][Ii][Nn][Zz][Ii][Gg][Ss][Tt][Ee])") then | ||||
|     utilities.send_message(self, receiver, '*einzige') | ||||
| 	return | ||||
|   elseif string.match(msg.text, "[Bb][Oo][Tt]%??") then | ||||
|     utilities.send_reply(self, msg, '*Ich bin da, '..user_name..'!*', true) | ||||
|     return | ||||
|   elseif string.match(msg.text, "[Ll][Oo][Dd]") then | ||||
|     utilities.send_message(self, receiver,  'ಠ_ಠ') | ||||
|     return | ||||
|   elseif string.match(msg.text, "[Ll][Ff]") then | ||||
|     utilities.send_message(self, receiver,  '( ͡° ͜ʖ ͡°)') | ||||
|     return | ||||
|   elseif string.match(msg.text, "[Nn][Bb][Cc]") or string.match(msg.text, "[Ii][Dd][Cc]") or string.match(msg.text, "[Kk][Aa]") or string.match(msg.text, "[Ii][Dd][Kk]")  then | ||||
|     utilities.send_message(self, receiver,  [[¯\_(ツ)_/¯]]) | ||||
| 	return | ||||
|   elseif string.match(msg.text, "[Ff][Rr][Oo][Ss][Cc][Hh]") then | ||||
|     utilities.send_message(self, receiver,  '🐸🐸🐸') | ||||
|     return | ||||
|   elseif string.match(msg.text, "[Ii][Nn][Ll][Oo][Vv][Ee]") then | ||||
|     local file = download_to_file(GDRIVE_URL..'/inlove.gif') | ||||
|     utilities.send_document(self, receiver, file) | ||||
|     return | ||||
|   elseif string.match(msg.text, "[Ww][Aa][Tt]") then | ||||
|     local WAT_URL = GDRIVE_URL..'/wat' | ||||
|     local wats = { | ||||
|       "/wat1.jpg", | ||||
|       "/wat2.jpg", | ||||
|       "/wat3.jpg", | ||||
| 	  "/wat4.jpg", | ||||
| 	  "/wat5.jpg", | ||||
| 	  "/wat6.jpg", | ||||
| 	  "/wat7.jpg", | ||||
| 	  "/wat8.jpg" | ||||
|     } | ||||
|   	local random_wat = math.random(5) | ||||
| 	local file = download_to_file(WAT_URL..wats[random_wat]) | ||||
|     utilities.send_photo(self, receiver, file) | ||||
| 	return | ||||
|   end | ||||
|    | ||||
| end | ||||
|  | ||||
| return respond | ||||
							
								
								
									
										31
									
								
								otouto/plugins/roll.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								otouto/plugins/roll.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| local roll = {} | ||||
|  | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| roll.command = 'roll' | ||||
|  | ||||
| function roll:init(config) | ||||
| 	roll.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('roll', true).table | ||||
| 	roll.doc = [[* | ||||
| ]]..config.cmd_pat..[[roll*: Werfe einen Würfel]] | ||||
| end | ||||
|  | ||||
| local canroll = { | ||||
|     "1", | ||||
|     "2", | ||||
|     "3", | ||||
|     "4", | ||||
|     "5", | ||||
|     "6" | ||||
| } | ||||
|  | ||||
| function roll:roll_dice() | ||||
|     local randomroll = math.random(6) | ||||
|     return canroll[randomroll] | ||||
| end | ||||
|  | ||||
| function roll:action(msg) | ||||
|   utilities.send_reply(self, msg, 'Du hast eine *'..roll:roll_dice()..'* gewürfelt.', true) | ||||
| end | ||||
|  | ||||
| return roll | ||||
							
								
								
									
										112
									
								
								otouto/plugins/tagesschau_eil.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								otouto/plugins/tagesschau_eil.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| local tagesschau_eil = {} | ||||
|  | ||||
| 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 redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| tagesschau_eil.command = 'eil <sub/del>' | ||||
|  | ||||
| function tagesschau_eil:init(config) | ||||
| 	tagesschau_eil.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('eil', true).table | ||||
| 	tagesschau_eil.doc = [[* | ||||
| ]]..config.cmd_pat..[[eil* _sub_: Eilmeldungen abonnieren | ||||
| *]]..config.cmd_pat..[[eil* _del_: Eilmeldungen deabonnieren | ||||
| *]]..config.cmd_pat..[[eil* _sync_: Nach neuen Eilmeldungen prüfen (nur Superuser)]] | ||||
| end | ||||
|  | ||||
| local makeOurDate = function(dateString) | ||||
|   local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+)%:(%d+)%:(%d+)" | ||||
|   local year, month, day, hours, minutes, seconds = dateString:match(pattern) | ||||
|   return day..'.'..month..'.'..year..' um '..hours..':'..minutes..':'..seconds | ||||
| end | ||||
|  | ||||
| local url = 'http://www.tagesschau.de/api' | ||||
| local hash = 'telegram:tagesschau' | ||||
|  | ||||
| function tagesschau_eil:abonnieren(id) | ||||
|   if redis:sismember(hash..':subs', id) == false then | ||||
|     redis:sadd(hash..':subs', id) | ||||
| 	return '*Eilmeldungen abonniert.*' | ||||
|   else | ||||
|     return 'Die Eilmeldungen wurden hier bereits abonniert.' | ||||
|   end | ||||
| end | ||||
|  | ||||
| function tagesschau_eil:deabonnieren(id) | ||||
|   if redis:sismember(hash..':subs', id) == true then | ||||
|     redis:srem(hash..':subs', id) | ||||
| 	return '*Eilmeldungen deabonniert.*' | ||||
|   else | ||||
|     return 'Die Eilmeldungen wurden hier noch nicht abonniert.' | ||||
|   end | ||||
| end | ||||
|  | ||||
| function tagesschau_eil:action(msg, config) | ||||
|   local input = utilities.input(msg.text) | ||||
|    | ||||
|   if not input then | ||||
|     if msg.reply_to_message and msg.reply_to_message.text then | ||||
|       input = msg.reply_to_message.text | ||||
|     else | ||||
| 	  utilities.send_message(self, msg.chat.id, tagesschau_eil.doc, true, msg.message_id, true) | ||||
| 	  return | ||||
| 	end | ||||
|   end | ||||
|  | ||||
|   local id = "user#id" .. msg.from.id | ||||
|   if msg.chat.type == 'channel' then | ||||
|     print('Kanäle werden momentan nicht unterstützt') | ||||
|   end | ||||
|   if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then | ||||
|     id = 'chat#id'..msg.chat.id | ||||
|   end | ||||
|  | ||||
|   if input:match('(sub)$') then | ||||
| 	local output = tagesschau_eil:abonnieren(id) | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|   elseif input:match('(del)$') then | ||||
| 	local output = tagesschau_eil:deabonnieren(id) | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|   elseif input:match('(sync)$') then | ||||
|     if msg.from.id ~= config.admin then | ||||
|       utilities.send_reply(self, msg, config.errors.sudo) | ||||
| 	  return | ||||
|     end | ||||
| 	tagesschau_eil:cron(self) | ||||
|   end | ||||
|    | ||||
|   return | ||||
| end | ||||
|  | ||||
| function tagesschau_eil:cron(self_plz) | ||||
|    if not self.BASE_URL then | ||||
|      self = self_plz | ||||
|    end | ||||
|   -- print('EIL: Prüfe...') | ||||
|   local last_eil = redis:get(hash..':last_entry') | ||||
|   local res,code  = http.request(url) | ||||
|   local data = json.decode(res) | ||||
|   if code ~= 200 then return end | ||||
|   if not data then return end | ||||
|   if data.breakingnews[1] then | ||||
|     if data.breakingnews[1].details ~= last_eil then | ||||
|       local title = '#EIL: *'..data.breakingnews[1].headline..'*' | ||||
|       local news = data.breakingnews[1].shorttext | ||||
|       local posted_at = makeOurDate(data.breakingnews[1].date)..' Uhr' | ||||
| 	  local post_url = string.gsub(data.breakingnews[1].details, '/api/', '/') | ||||
| 	  local post_url = string.gsub(post_url, '.json', '.html') | ||||
|       local eil = title..'\n_'..posted_at..'_\n'..news..'\n[Artikel aufrufen]('..post_url..')' | ||||
|       redis:set(hash..':last_entry', data.breakingnews[1].details) | ||||
| 	  for _,user in pairs(redis:smembers(hash..':subs')) do | ||||
| 	    local user = string.gsub(user, 'chat%#id', '') | ||||
| 		local user = string.gsub(user, 'user%#id', '') | ||||
| 	    utilities.send_message(self, user, eil, true, nil, true) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| return tagesschau_eil | ||||
| @@ -140,15 +140,11 @@ function twitter:action(msg) | ||||
|   utilities.send_reply(self, msg, header .. "\n" .. text.."\n"..footer) | ||||
|   for k, v in pairs(images) do | ||||
|     local file = download_to_file(v) | ||||
|     bindings.sendPhoto(self, {chat_id = msg.chat.id}, {photo = file} ) | ||||
| 	os.remove(file) | ||||
|     print("Deleted: "..file) | ||||
| 	utilities.send_photo(self, msg.chat.id, file, nil, msg.message_id) | ||||
|   end | ||||
|   for k, v in pairs(videos) do | ||||
|     local file = download_to_file(v) | ||||
| 	bindings.sendVideo(self, {chat_id = msg.chat.id}, {video = file} ) | ||||
| 	os.remove(file) | ||||
|     print("Deleted: "..file) | ||||
| 	utilities.send_video(self, msg.chat.id, file, nil, msg.message_id) | ||||
|   end | ||||
| end | ||||
|  | ||||
|   | ||||
							
								
								
									
										349
									
								
								otouto/plugins/twitter_send.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								otouto/plugins/twitter_send.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -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* _<Text>_: 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_ _<user>_: Schaltet User für die Tweet-Funktion frei | ||||
| *]]..config.cmd_pat..[[twwitelist* _del_ _<user>_: Entfernt User von der Tweet-Whitelist | ||||
| *]]..config.cmd_pat..[[tw* _auth_ _<PIN>_: Meldet mit dieser PIN an (Setup) | ||||
| *]]..config.cmd_pat..[[tw* _unauth_: Meldet Twitter-Account ab | ||||
| ]] | ||||
| end | ||||
|  | ||||
| twitter_send.command = 'tw <Tweet>' | ||||
|  | ||||
| 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 | ||||
| @@ -1,63 +1,150 @@ | ||||
| local weather = {} | ||||
|  | ||||
| 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 redis = (loadfile "./otouto/redis.lua")() | ||||
|  | ||||
| function weather:init(config) | ||||
| 	if not config.owm_api_key then | ||||
| 		print('Missing config value: owm_api_key.') | ||||
| 	if not cred_data.forecastio_apikey then | ||||
| 		print('Missing config value: forecastio_apikey.') | ||||
| 		print('weather.lua will not be enabled.') | ||||
| 		return | ||||
| 	elseif not cred_data.google_apikey then | ||||
| 		print('Missing config value: google_apikey.') | ||||
| 		print('weather.lua will not be enabled.') | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	weather.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('weather', true).table | ||||
| 	weather.doc = [[``` | ||||
| ]]..config.cmd_pat..[[weather <location> | ||||
| Returns the current weather conditions for a given location. | ||||
| ```]] | ||||
|    weather.triggers = { | ||||
|       "^/wetter$", | ||||
| 	  "^/wetter (.*)$", | ||||
| 	  "^/w$", | ||||
| 	  "^/w (.*)$" | ||||
| 	} | ||||
| 	weather.doc = [[* | ||||
| ]]..config.cmd_pat..[[wetter*:  Wetter für deinen Wohnort _(/location set [Ort])_ | ||||
| *]]..config.cmd_pat..[[wetter* _<Ort>_: Wetter für diesen Ort | ||||
| ]] | ||||
| end | ||||
|  | ||||
| weather.command = 'weather <location>' | ||||
| weather.command = 'wetter' | ||||
|  | ||||
| function weather:action(msg, config) | ||||
| local BASE_URL = "https://api.forecast.io/forecast" | ||||
| local apikey = cred_data.forecastio_apikey | ||||
| local google_apikey = cred_data.google_apikey | ||||
|  | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	if not input then | ||||
| 		if msg.reply_to_message and msg.reply_to_message.text then | ||||
| 			input = msg.reply_to_message.text | ||||
| 		else | ||||
| 			utilities.send_message(self, msg.chat.id, weather.doc, true, msg.message_id, true) | ||||
| 			return | ||||
| 		end | ||||
| function get_city_name(lat, lng) | ||||
|   local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng) | ||||
|   if city then return city end | ||||
|   local url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='..lat..','..lng..'&result_type=political&language=de&key='..google_apikey | ||||
|   local res, code = HTTPS.request(url) | ||||
|   if code ~= 200 then return 'Unbekannte Stadt' end | ||||
|   local data = JSON.decode(res).results[1] | ||||
|   local city = data.formatted_address | ||||
|   print('Setting '..lat..','..lng..' in redis hash telegram:cache:weather:pretty_names to "'..city..'"') | ||||
|   redis:hset('telegram:cache:weather:pretty_names', lat..','..lng, city) | ||||
|   return city | ||||
| end | ||||
|  | ||||
| function weather:get_weather(lat, lng) | ||||
|   print('Finde Wetter in '..lat..', '..lng) | ||||
|   local text = redis:get('telegram:cache:weather:'..lat..','..lng) | ||||
|   if text then print('...aus dem Cache') return text end | ||||
|  | ||||
|   local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=minutely,hourly,daily,alerts,flags' | ||||
|    | ||||
|   local response_body = {} | ||||
|   local request_constructor = { | ||||
|       url = url, | ||||
|       method = "GET", | ||||
|       sink = ltn12.sink.table(response_body) | ||||
|    } | ||||
|   local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor) | ||||
|   if not ok then return nil end | ||||
|   local data = JSON.decode(table.concat(response_body)) | ||||
|   local ttl = string.sub(response_headers["cache-control"], 9) | ||||
|  | ||||
|    | ||||
|   local weather = data.currently | ||||
|   local city = get_city_name(lat, lng) | ||||
|   local temperature = string.gsub(round(weather.temperature, 1), "%.", ",") | ||||
|   local feelslike = string.gsub(round(weather.apparentTemperature, 1), "%.", ",") | ||||
|   local temp = '*Wetter in '..city..':*\n'..temperature..' °C' | ||||
|   local conditions = ' | '..weather.summary | ||||
|   if weather.icon == 'clear-day' then | ||||
| 	conditions = conditions..' ☀️' | ||||
|   elseif weather.icon == 'clear-night' then | ||||
| 	conditions = conditions..' 🌙' | ||||
|   elseif weather.icon == 'rain' then | ||||
|     conditions = conditions..' ☔️' | ||||
|   elseif weather.icon == 'snow' then | ||||
| 	 conditions = conditions..' ❄️' | ||||
|   elseif weather.icon == 'sleet' then | ||||
|      conditions = conditions..' 🌨' | ||||
|   elseif weather.icon == 'wind' then | ||||
|      conditions = conditions..' 💨' | ||||
|   elseif weather.icon == 'fog' then | ||||
|      conditions = conditions..' 🌫' | ||||
|   elseif weather.icon == 'cloudy' then | ||||
|      conditions = conditions..' ☁️☁️' | ||||
|   elseif weather.icon == 'partly-cloudy-day' then | ||||
|      conditions = conditions..' 🌤' | ||||
|   elseif weather.icon == 'partly-cloudy-night' then | ||||
|      conditions = conditions..' 🌙☁️' | ||||
|   else | ||||
|      conditions = conditions..'' | ||||
|   end | ||||
|   local windspeed = ' | 💨 '..string.gsub(round(weather.windSpeed, 1), "%.", ",")..' m/s' | ||||
|    | ||||
|   local text = temp..conditions..windspeed | ||||
|    | ||||
|   if temperature ~= feelslike then | ||||
|     text = text..'\n(gefühlt: '..feelslike..' °C)' | ||||
|   end | ||||
|    | ||||
|   cache_data('weather', lat..','..lng, text, tonumber(ttl), 'key') | ||||
|   return text | ||||
| end | ||||
|  | ||||
| function weather:action(msg, config, matches) | ||||
|   local user_id = msg.from.id | ||||
|  | ||||
|   if matches[1] ~= '/wetter' and matches[1] ~= '/w' then  | ||||
|     city = matches[1] | ||||
|   else | ||||
|     local set_location = get_location(user_id) | ||||
| 	if not set_location then | ||||
| 	  city = 'Berlin, Deutschland' | ||||
| 	else | ||||
| 	  city = set_location | ||||
| 	end | ||||
|   end | ||||
|    | ||||
|   local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') | ||||
|   local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng') | ||||
|   if not lat and not lng then | ||||
|     print('Koordinaten nicht eingespeichert, frage Google...') | ||||
|     coords = utilities.get_coords(city, config) | ||||
| 	lat = coords.lat | ||||
| 	lng = coords.lon | ||||
|   end | ||||
|    | ||||
|   if not lat and not lng then | ||||
|     utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true) | ||||
|     return | ||||
|   end | ||||
|  | ||||
| 	local coords = utilities.get_coords(input, config) | ||||
| 	if type(coords) == 'string' then | ||||
| 		utilities.send_reply(self, msg, coords) | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	local url = 'http://api.openweathermap.org/data/2.5/weather?APPID=' .. config.owm_api_key .. '&lat=' .. coords.lat .. '&lon=' .. coords.lon | ||||
|  | ||||
| 	local jstr, res = HTTP.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	local jdat = JSON.decode(jstr) | ||||
| 	if jdat.cod ~= 200 then | ||||
| 		utilities.send_reply(self, msg, 'Error: City not found.') | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	local celsius = string.format('%.2f', jdat.main.temp - 273.15) | ||||
| 	local fahrenheit = string.format('%.2f', celsius * (9/5) + 32) | ||||
| 	local output = '`' .. celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.`' | ||||
|  | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|  | ||||
|   redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat) | ||||
|   redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng) | ||||
|    | ||||
|   local text = weather:get_weather(lat, lng) | ||||
|   if not text then | ||||
|     text = 'Konnte das Wetter von dieser Stadt nicht bekommen.' | ||||
|   end | ||||
|   utilities.send_reply(self, msg, text, true) | ||||
| end | ||||
|  | ||||
| return weather | ||||
|   | ||||
| @@ -8,10 +8,10 @@ local utilities = require('otouto.utilities') | ||||
| wikipedia.command = 'wiki <Begriff>' | ||||
|  | ||||
| function wikipedia:init(config) | ||||
| 	wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true):t('w', true).table | ||||
| 	wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true).table | ||||
| 	wikipedia.doc = [[* | ||||
| ]]..config.cmd_pat..[[wiki* _<Begriff>_: Gibt Wikipedia-Artikel aus | ||||
| Aliase: ]]..config.cmd_pat..[[w, ]]..config.cmd_pat..[[wikipedia]] | ||||
| Alias: ]]..config.cmd_pat..[[wikipedia]] | ||||
| end | ||||
|  | ||||
| local get_title = function(search) | ||||
|   | ||||
| @@ -142,9 +142,7 @@ function send_youtube_data(data, msg, self, link, sendpic) | ||||
|       text = text..'\nACHTUNG, In Deutschland gesperrt!' | ||||
|     end | ||||
|     local file = download_to_file(image_url) | ||||
|     bindings.sendPhoto(self, {chat_id = msg.chat.id, reply_to_message_id = msg.message_id, caption = text }, {photo = file} ) | ||||
|     os.remove(file) | ||||
|     print("Deleted: "..file) | ||||
| 	utilities.send_photo(self, msg.chat.id, file, text, msg.message_id) | ||||
|   else | ||||
|     utilities.send_reply(self, msg, text, true) | ||||
|   end | ||||
|   | ||||
| @@ -17,7 +17,8 @@ function yt_search:init(config) | ||||
| 	end | ||||
| 	 | ||||
| 	yt_search.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('yt', true):t('youtube', true).table | ||||
| 	yt_search.doc = [[*]]..config.cmd_pat..[[yt* _<Suchbegriff>_: Sucht nach einem YouTube-Video]] | ||||
| 	yt_search.doc = [[* | ||||
| ]]..config.cmd_pat..[[yt* _<Suchbegriff>_: Sucht nach einem YouTube-Video]] | ||||
| end | ||||
|  | ||||
| local BASE_URL = 'https://www.googleapis.com/youtube/v3' | ||||
|   | ||||
| @@ -8,6 +8,8 @@ local ltn12 = require('ltn12') | ||||
| local HTTPS = require('ssl.https') | ||||
| local URL = require('socket.url') | ||||
| local JSON = require('dkjson') | ||||
| local http = require('socket.http') | ||||
| local https = require('ssl.https') | ||||
| local serpent = require("serpent") | ||||
| local bindings = require('otouto.bindings') | ||||
| local redis = (loadfile "./otouto/redis.lua")() | ||||
| @@ -34,6 +36,107 @@ function utilities:send_reply(old_msg, text, use_markdown) | ||||
| 		parse_mode = use_markdown and 'Markdown' or nil | ||||
| 	} ) | ||||
| end | ||||
|  | ||||
| -- NOTE: Telegram currently only allows file uploads up to 50 MB | ||||
| -- https://core.telegram.org/bots/api#sendphoto | ||||
| function utilities:send_photo(chat_id, file, text, reply_to_message_id) | ||||
| 	local output = bindings.request(self, 'sendPhoto', { | ||||
| 		chat_id = chat_id, | ||||
| 		caption = text or nil, | ||||
| 		reply_to_message_id = reply_to_message_id | ||||
| 	}, {photo = file} ) | ||||
| 	os.remove(file) | ||||
| 	print("Deleted: "..file) | ||||
| 	return output | ||||
| end | ||||
|  | ||||
| -- https://core.telegram.org/bots/api#sendaudio | ||||
| function utilities:send_audio(chat_id, file, text, reply_to_message_id, duration, performer, title) | ||||
| 	local output = bindings.request(self, 'sendAudio', { | ||||
| 		chat_id = chat_id, | ||||
| 		caption = text or nil, | ||||
| 		duration = duration or nil, | ||||
| 		performer = performer or nil, | ||||
| 		title = title or nil, | ||||
| 		reply_to_message_id = reply_to_message_id | ||||
| 	}, {audio = file} ) | ||||
| 	os.remove(file) | ||||
| 	print("Deleted: "..file) | ||||
| 	return output | ||||
| end | ||||
|  | ||||
| -- https://core.telegram.org/bots/api#senddocument | ||||
| function utilities:send_document(chat_id, file, text, reply_to_message_id) | ||||
| 	local output = bindings.request(self, 'sendDocument', { | ||||
| 		chat_id = chat_id, | ||||
| 		caption = text or nil, | ||||
| 		reply_to_message_id = reply_to_message_id | ||||
| 	}, {document = file} ) | ||||
| 	os.remove(file) | ||||
| 	print("Deleted: "..file) | ||||
| 	return output | ||||
| end | ||||
|  | ||||
| -- https://core.telegram.org/bots/api#sendvideo | ||||
| function utilities:send_video(chat_id, file, text, reply_to_message_id, duration, width, height) | ||||
| 	local output = bindings.request(self, 'sendVideo', { | ||||
| 		chat_id = chat_id, | ||||
| 		caption = text or nil, | ||||
| 		duration = duration or nil, | ||||
| 		width = width or nil, | ||||
| 		height = height or nil, | ||||
| 		reply_to_message_id = reply_to_message_id | ||||
| 	}, {video = file} ) | ||||
| 	os.remove(file) | ||||
| 	print("Deleted: "..file) | ||||
| 	return output | ||||
| end | ||||
|  | ||||
| -- NOTE: Voice messages are .ogg files encoded with OPUS | ||||
| -- https://core.telegram.org/bots/api#sendvoice | ||||
| function utilities:send_voice(chat_id, file, text, reply_to_message_id, duration) | ||||
| 	local output = bindings.request(self, 'sendVoice', { | ||||
| 		chat_id = chat_id, | ||||
| 		duration = duration or nil, | ||||
| 		reply_to_message_id = reply_to_message_id | ||||
| 	}, {voice = file} ) | ||||
| 	os.remove(file) | ||||
| 	print("Deleted: "..file) | ||||
| 	return output | ||||
| end | ||||
|  | ||||
| -- https://core.telegram.org/bots/api#sendlocation | ||||
| function utilities:send_location(chat_id, latitude, longitude, reply_to_message_id) | ||||
| 	return bindings.request(self, 'sendLocation', { | ||||
| 		chat_id = chat_id, | ||||
| 		latitude = latitude, | ||||
| 		longitude = longitude, | ||||
| 		reply_to_message_id = reply_to_message_id | ||||
| 	} ) | ||||
| end | ||||
|  | ||||
| -- NOTE: Venue is different from location: it shows information, such as the street adress or | ||||
| -- title of the location with it. | ||||
| -- https://core.telegram.org/bots/api#sendvenue | ||||
| function utilities:send_venue(chat_id, latitude, longitude, reply_to_message_id, title, address) | ||||
| 	return bindings.request(self, 'sendVenue', { | ||||
| 		chat_id = chat_id, | ||||
| 		latitude = latitude, | ||||
| 		longitude = longitude, | ||||
| 		title = title, | ||||
| 		address = address, | ||||
| 		reply_to_message_id = reply_to_message_id | ||||
| 	} ) | ||||
| end | ||||
|  | ||||
| -- https://core.telegram.org/bots/api#sendchataction | ||||
| function utilities:send_typing(chat_id, action) | ||||
| 	return bindings.request(self, 'sendChatAction', { | ||||
| 		chat_id = chat_id, | ||||
| 		action = action | ||||
| 	} ) | ||||
| end | ||||
|  | ||||
|  -- get the indexed word in a string | ||||
| function utilities.get_word(s, i) | ||||
| 	s = s or '' | ||||
| @@ -110,6 +213,25 @@ local lc_list = { | ||||
| 	['!'] = 'ǃ' | ||||
| } | ||||
|  | ||||
| -- Retruns true if the string is empty | ||||
| function string:isempty() | ||||
|   return self == nil or self == '' | ||||
| end | ||||
|  | ||||
| -- Retruns true if the string is blank | ||||
| function string:isblank() | ||||
|   self = self:trim() | ||||
|   return self:isempty() | ||||
| end | ||||
|  | ||||
| function get_name(msg) | ||||
|    local name = msg.from.first_name | ||||
|    if name == nil then | ||||
|       name = msg.from.id | ||||
|    end | ||||
|    return name | ||||
| end | ||||
|  | ||||
| -- http://www.lua.org/manual/5.2/manual.html#pdf-io.popen | ||||
| function run_command(str) | ||||
|   local cmd = io.popen(str) | ||||
| @@ -118,6 +240,12 @@ function run_command(str) | ||||
|   return result | ||||
| end | ||||
|  | ||||
| function convert_timestamp(timestamp, format) | ||||
|   local converted_date = run_command('date -d @'..timestamp..' +"'..format..'"') | ||||
|   local converted_date = string.gsub(converted_date, '%\n', '') | ||||
|   return converted_date | ||||
| end | ||||
|  | ||||
| function string.starts(String, Start) | ||||
|    return Start == string.sub(String,1,string.len(Start)) | ||||
| end | ||||
| @@ -227,9 +355,9 @@ end | ||||
|  -- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua. | ||||
| function utilities.get_coords(input, config) | ||||
|  | ||||
| 	local url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) | ||||
| 	local url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) | ||||
|  | ||||
| 	local jstr, res = HTTP.request(url) | ||||
| 	local jstr, res = HTTPS.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		return config.errors.connection | ||||
| 	end | ||||
| @@ -461,6 +589,62 @@ 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 post_petition(url, arguments, headers) | ||||
|    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 = headers or {}, | ||||
|       redirect = false | ||||
|    } | ||||
|  | ||||
|    local source = arguments | ||||
|    if type(arguments) == "table" then | ||||
|       local source = helpers.url_encode_arguments(arguments) | ||||
|    end | ||||
|     | ||||
|    if not headers then | ||||
|      request_constructor.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF8" | ||||
|      request_constructor.headers["X-Accept"] = "application/json" | ||||
| 	 request_constructor.headers["Accept"] = "application/json" | ||||
|    end | ||||
|    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, response_headers | ||||
| end | ||||
|  | ||||
| function get_redis_hash(msg, var) | ||||
|   if msg.chat.type == 'group' or msg.chat.type == 'supergroup' then | ||||
|     return 'chat:'..msg.chat.id..':'..var | ||||
| @@ -481,6 +665,14 @@ function tablelength(T) | ||||
|   return count | ||||
| 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 comma_value(amount) | ||||
|   local formatted = amount | ||||
|   while true do   | ||||
| @@ -497,6 +689,16 @@ 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 | ||||
|  | ||||
| function cache_data(plugin, query, data, timeout, typ) | ||||
|   -- How to: cache_data(pluginname, query_name, data_to_cache, expire_in_seconds) | ||||
|   local hash = 'telegram:cache:'..plugin..':'..query | ||||
| @@ -619,4 +821,14 @@ function unescape(str) | ||||
|   return str | ||||
| end | ||||
|  | ||||
| function url_encode(str) | ||||
|   if (str) then | ||||
|     str = string.gsub (str, "\n", "\r\n") | ||||
|     str = string.gsub (str, "([^%w %-%_%.%~])", | ||||
|         function (c) return string.format ("%%%02X", string.byte(c)) end) | ||||
|     str = string.gsub (str, " ", "+") | ||||
|   end | ||||
|   return str | ||||
| end | ||||
|  | ||||
| return utilities | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 GitHub
						GitHub