tab -> 4 spaces
This commit is contained in:
		| @@ -6,4 +6,5 @@ insert_final_newline = true | ||||
|  | ||||
| [*.lua] | ||||
| charset = utf-8 | ||||
| indent_style = tab | ||||
| indent_style = space | ||||
| indent_size = 4 | ||||
|   | ||||
							
								
								
									
										62
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								README.md
									
									
									
									
									
								
							| @@ -292,26 +292,26 @@ Additionally, any method can be called as a key in the `bindings` table (for exa | ||||
|  | ||||
| ``` | ||||
| bindings.request( | ||||
| 	self, | ||||
| 	'sendMessage', | ||||
| 	{ | ||||
| 		chat_id = 987654321, | ||||
| 		text = 'Quick brown fox.', | ||||
| 		reply_to_message_id = 54321, | ||||
| 		disable_web_page_preview = false, | ||||
| 		parse_method = 'Markdown' | ||||
| 	} | ||||
|     self, | ||||
|     'sendMessage', | ||||
|     { | ||||
|         chat_id = 987654321, | ||||
|         text = 'Quick brown fox.', | ||||
|         reply_to_message_id = 54321, | ||||
|         disable_web_page_preview = false, | ||||
|         parse_method = 'Markdown' | ||||
|     } | ||||
| ) | ||||
|  | ||||
| bindings.sendMessage( | ||||
| 	self, | ||||
| 	{ | ||||
| 		chat_id = 987654321, | ||||
| 		text = 'Quick brown fox.', | ||||
| 		reply_to_message_id = 54321, | ||||
| 		disable_web_page_preview = false, | ||||
| 		parse_method = 'Markdown' | ||||
| 	} | ||||
|     self, | ||||
|     { | ||||
|         chat_id = 987654321, | ||||
|         text = 'Quick brown fox.', | ||||
|         reply_to_message_id = 54321, | ||||
|         disable_web_page_preview = false, | ||||
|         parse_method = 'Markdown' | ||||
|     } | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| @@ -342,20 +342,20 @@ Alone, the database will have this structure: | ||||
|  | ||||
| ``` | ||||
| { | ||||
| 	users = { | ||||
| 		["55994550"] = { | ||||
| 			id = 55994550, | ||||
| 			first_name = "Drew", | ||||
| 			username = "topkecleon" | ||||
| 		} | ||||
| 	}, | ||||
| 	userdata = { | ||||
| 		["55994550"] = { | ||||
| 			nickname = "Worst coder ever", | ||||
| 			lastfm = "topkecleon" | ||||
| 		} | ||||
| 	}, | ||||
| 	version = "3.11" | ||||
|     users = { | ||||
|         ["55994550"] = { | ||||
|             id = 55994550, | ||||
|             first_name = "Drew", | ||||
|             username = "topkecleon" | ||||
|         } | ||||
|     }, | ||||
|     userdata = { | ||||
|         ["55994550"] = { | ||||
|             nickname = "Worst coder ever", | ||||
|             lastfm = "topkecleon" | ||||
|         } | ||||
|     }, | ||||
|     version = "3.11" | ||||
| } | ||||
| ``` | ||||
|  | ||||
|   | ||||
							
								
								
									
										286
									
								
								config.lua
									
									
									
									
									
								
							
							
						
						
									
										286
									
								
								config.lua
									
									
									
									
									
								
							| @@ -1,159 +1,159 @@ | ||||
|  -- For details on configuration values, see README.md#configuration. | ||||
| return { | ||||
|  | ||||
| 	-- Your authorization token from the botfather. | ||||
| 	bot_api_key = nil, | ||||
| 	-- Your Telegram ID. | ||||
| 	admin = nil, | ||||
| 	-- Two-letter language code. | ||||
| 	lang = 'en', | ||||
| 	-- The channel, group, or user to send error reports to. | ||||
| 	-- If this is not set, errors will be printed to the console. | ||||
| 	log_chat = nil, | ||||
| 	-- The port used to communicate with tg for administration.lua. | ||||
| 	-- If you change this, make sure you also modify launch-tg.sh. | ||||
| 	cli_port = 4567, | ||||
| 	-- The symbol that starts a command. Usually noted as '/' in documentation. | ||||
| 	cmd_pat = '/', | ||||
| 	-- If drua is used, should a user be blocked when he's blacklisted? | ||||
| 	drua_block_on_blacklist = false, | ||||
| 	-- The filename of the database. If left nil, defaults to $username.db. | ||||
| 	database_name = nil, | ||||
| 	-- The block of text returned by /start and /about.. | ||||
| 	about_text = [[ | ||||
|     -- Your authorization token from the botfather. | ||||
|     bot_api_key = nil, | ||||
|     -- Your Telegram ID. | ||||
|     admin = nil, | ||||
|     -- Two-letter language code. | ||||
|     lang = 'en', | ||||
|     -- The channel, group, or user to send error reports to. | ||||
|     -- If this is not set, errors will be printed to the console. | ||||
|     log_chat = nil, | ||||
|     -- The port used to communicate with tg for administration.lua. | ||||
|     -- If you change this, make sure you also modify launch-tg.sh. | ||||
|     cli_port = 4567, | ||||
|     -- The symbol that starts a command. Usually noted as '/' in documentation. | ||||
|     cmd_pat = '/', | ||||
|     -- If drua is used, should a user be blocked when he's blacklisted? | ||||
|     drua_block_on_blacklist = false, | ||||
|     -- The filename of the database. If left nil, defaults to $username.db. | ||||
|     database_name = nil, | ||||
|     -- The block of text returned by /start and /about.. | ||||
|     about_text = [[ | ||||
| I am otouto, the plugin-wielding, multipurpose Telegram bot. | ||||
|  | ||||
| Send /help to get started. | ||||
| 	]], | ||||
|     ]], | ||||
|  | ||||
| 	errors = { -- Generic error messages. | ||||
| 		generic = 'An unexpected error occurred.', | ||||
| 		connection = 'Connection error.', | ||||
| 		results = 'No results found.', | ||||
| 		argument = 'Invalid argument.', | ||||
| 		syntax = 'Invalid syntax.' | ||||
| 	}, | ||||
|     errors = { -- Generic error messages. | ||||
|         generic = 'An unexpected error occurred.', | ||||
|         connection = 'Connection error.', | ||||
|         results = 'No results found.', | ||||
|         argument = 'Invalid argument.', | ||||
|         syntax = 'Invalid syntax.' | ||||
|     }, | ||||
|  | ||||
| 	-- https://datamarket.azure.com/dataset/bing/search | ||||
| 	bing_api_key = nil, | ||||
| 	-- http://console.developers.google.com | ||||
| 	google_api_key = nil, | ||||
| 	-- https://cse.google.com/cse | ||||
| 	google_cse_key = nil, | ||||
| 	-- http://openweathermap.org/appid | ||||
| 	owm_api_key = nil, | ||||
| 	-- http://last.fm/api | ||||
| 	lastfm_api_key = nil, | ||||
| 	-- http://api.biblia.com | ||||
| 	biblia_api_key = nil, | ||||
| 	-- http://thecatapi.com/docs.html | ||||
| 	thecatapi_key = nil, | ||||
| 	-- http://api.nasa.gov | ||||
| 	nasa_api_key = nil, | ||||
| 	-- http://tech.yandex.com/keys/get | ||||
| 	yandex_key = nil, | ||||
| 	-- Interval (in minutes) for hackernews.lua to update. | ||||
| 	hackernews_interval = 60, | ||||
| 	-- Whether hackernews.lua should update at load/reload. | ||||
| 	hackernews_onstart = false, | ||||
| 	-- Whether luarun should use serpent instead of dkjson for serialization. | ||||
| 	luarun_serpent = false, | ||||
|     -- https://datamarket.azure.com/dataset/bing/search | ||||
|     bing_api_key = nil, | ||||
|     -- http://console.developers.google.com | ||||
|     google_api_key = nil, | ||||
|     -- https://cse.google.com/cse | ||||
|     google_cse_key = nil, | ||||
|     -- http://openweathermap.org/appid | ||||
|     owm_api_key = nil, | ||||
|     -- http://last.fm/api | ||||
|     lastfm_api_key = nil, | ||||
|     -- http://api.biblia.com | ||||
|     biblia_api_key = nil, | ||||
|     -- http://thecatapi.com/docs.html | ||||
|     thecatapi_key = nil, | ||||
|     -- http://api.nasa.gov | ||||
|     nasa_api_key = nil, | ||||
|     -- http://tech.yandex.com/keys/get | ||||
|     yandex_key = nil, | ||||
|     -- Interval (in minutes) for hackernews.lua to update. | ||||
|     hackernews_interval = 60, | ||||
|     -- Whether hackernews.lua should update at load/reload. | ||||
|     hackernews_onstart = false, | ||||
|     -- Whether luarun should use serpent instead of dkjson for serialization. | ||||
|     luarun_serpent = false, | ||||
|  | ||||
| 	remind = { | ||||
| 		persist = true, | ||||
| 		max_length = 1000, | ||||
| 		max_duration = 526000, | ||||
| 		max_reminders_group = 10, | ||||
| 		max_reminders_private = 50 | ||||
| 	}, | ||||
|     remind = { | ||||
|         persist = true, | ||||
|         max_length = 1000, | ||||
|         max_duration = 526000, | ||||
|         max_reminders_group = 10, | ||||
|         max_reminders_private = 50 | ||||
|     }, | ||||
|  | ||||
| 	chatter = { | ||||
| 		cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=', | ||||
| 		connection = 'I don\'t feel like talking right now.', | ||||
| 		response = 'I don\'t know what to say to that.' | ||||
| 	}, | ||||
|     chatter = { | ||||
|         cleverbot_api = 'https://brawlbot.tk/apis/chatter-bot-api/cleverbot.php?text=', | ||||
|         connection = 'I don\'t feel like talking right now.', | ||||
|         response = 'I don\'t know what to say to that.' | ||||
|     }, | ||||
|  | ||||
| 	greetings = { | ||||
| 		["Hello, #NAME."] = { | ||||
| 			"hello", | ||||
| 			"hey", | ||||
| 			"hi", | ||||
| 			"good morning", | ||||
| 			"good day", | ||||
| 			"good afternoon", | ||||
| 			"good evening" | ||||
| 		}, | ||||
| 		["Goodbye, #NAME."] = { | ||||
| 			"good%-?bye", | ||||
| 			"bye", | ||||
| 			"later", | ||||
| 			"see ya", | ||||
| 			"good night" | ||||
| 		}, | ||||
| 		["Welcome back, #NAME."] = { | ||||
| 			"i'm home", | ||||
| 			"i'm back" | ||||
| 		}, | ||||
| 		["You're welcome, #NAME."] = { | ||||
| 			"thanks", | ||||
| 			"thank you" | ||||
| 		} | ||||
| 	}, | ||||
|     greetings = { | ||||
|         ["Hello, #NAME."] = { | ||||
|             "hello", | ||||
|             "hey", | ||||
|             "hi", | ||||
|             "good morning", | ||||
|             "good day", | ||||
|             "good afternoon", | ||||
|             "good evening" | ||||
|         }, | ||||
|         ["Goodbye, #NAME."] = { | ||||
|             "good%-?bye", | ||||
|             "bye", | ||||
|             "later", | ||||
|             "see ya", | ||||
|             "good night" | ||||
|         }, | ||||
|         ["Welcome back, #NAME."] = { | ||||
|             "i'm home", | ||||
|             "i'm back" | ||||
|         }, | ||||
|         ["You're welcome, #NAME."] = { | ||||
|             "thanks", | ||||
|             "thank you" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| 	reactions = { | ||||
| 		['shrug'] = '¯\\_(ツ)_/¯', | ||||
| 		['lenny'] = '( ͡° ͜ʖ ͡°)', | ||||
| 		['flip'] = '(╯°□°)╯︵ ┻━┻', | ||||
| 		['look'] = 'ಠ_ಠ', | ||||
| 		['shots'] = 'SHOTS FIRED', | ||||
| 		['facepalm'] = '(-‸ლ)' | ||||
| 	}, | ||||
|     reactions = { | ||||
|         ['shrug'] = '¯\\_(ツ)_/¯', | ||||
|         ['lenny'] = '( ͡° ͜ʖ ͡°)', | ||||
|         ['flip'] = '(╯°□°)╯︵ ┻━┻', | ||||
|         ['look'] = 'ಠ_ಠ', | ||||
|         ['shots'] = 'SHOTS FIRED', | ||||
|         ['facepalm'] = '(-‸ლ)' | ||||
|     }, | ||||
|  | ||||
| 	administration = { | ||||
| 		-- Whether moderators can set a group's message of the day. | ||||
| 		moderator_setmotd = false, | ||||
| 		-- Default antiflood values. | ||||
| 		antiflood = { | ||||
| 			text = 5, | ||||
| 			voice = 5, | ||||
| 			audio = 5, | ||||
| 			contact = 5, | ||||
| 			photo = 10, | ||||
| 			video = 10, | ||||
| 			location = 10, | ||||
| 			document = 10, | ||||
| 			sticker = 20 | ||||
| 		} | ||||
| 	}, | ||||
|     administration = { | ||||
|         -- Whether moderators can set a group's message of the day. | ||||
|         moderator_setmotd = false, | ||||
|         -- Default antiflood values. | ||||
|         antiflood = { | ||||
|             text = 5, | ||||
|             voice = 5, | ||||
|             audio = 5, | ||||
|             contact = 5, | ||||
|             photo = 10, | ||||
|             video = 10, | ||||
|             location = 10, | ||||
|             document = 10, | ||||
|             sticker = 20 | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| 	plugins = { -- To enable a plugin, add its name to the list. | ||||
| 		'about', | ||||
| 		'blacklist', | ||||
| 		'calc', | ||||
| 		'cats', | ||||
| 		'commit', | ||||
| 		'control', | ||||
| 		'currency', | ||||
| 		'dice', | ||||
| 		'echo', | ||||
| 		'eightball', | ||||
| 		'gMaps', | ||||
| 		'hackernews', | ||||
| 		'imdb', | ||||
| 		'nick', | ||||
| 		'ping', | ||||
| 		'pun', | ||||
| 		'reddit', | ||||
| 		'shout', | ||||
| 		'slap', | ||||
| 		'time', | ||||
| 		'urbandictionary', | ||||
| 		'whoami', | ||||
| 		'wikipedia', | ||||
| 		'xkcd', | ||||
| 		-- Put new plugins above this line. | ||||
| 		'help', | ||||
| 		'greetings' | ||||
| 	} | ||||
|     plugins = { -- To enable a plugin, add its name to the list. | ||||
|         'about', | ||||
|         'blacklist', | ||||
|         'calc', | ||||
|         'cats', | ||||
|         'commit', | ||||
|         'control', | ||||
|         'currency', | ||||
|         'dice', | ||||
|         'echo', | ||||
|         'eightball', | ||||
|         'gMaps', | ||||
|         'hackernews', | ||||
|         'imdb', | ||||
|         'nick', | ||||
|         'ping', | ||||
|         'pun', | ||||
|         'reddit', | ||||
|         'shout', | ||||
|         'slap', | ||||
|         'time', | ||||
|         'urbandictionary', | ||||
|         'whoami', | ||||
|         'wikipedia', | ||||
|         'xkcd', | ||||
|         -- Put new plugins above this line. | ||||
|         'help', | ||||
|         'greetings' | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| while true; do | ||||
| 	lua main.lua | ||||
| 	echo 'otouto has stopped. ^C to exit.' | ||||
| 	sleep 5s | ||||
|     lua main.lua | ||||
|     echo 'otouto has stopped. ^C to exit.' | ||||
|     sleep 5s | ||||
| done | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| --[[ | ||||
| 	bindings.lua (rev. 2016/05/28) | ||||
| 	otouto's bindings for the Telegram bot API. | ||||
| 	https://core.telegram.org/bots/api | ||||
| 	Copyright 2016 topkecleon. Published under the AGPLv3. | ||||
|     bindings.lua (rev. 2016/05/28) | ||||
|     otouto's bindings for the Telegram bot API. | ||||
|     https://core.telegram.org/bots/api | ||||
|     Copyright 2016 topkecleon. Published under the AGPLv3. | ||||
|  | ||||
| 	See the "Bindings" section of README.md for usage information. | ||||
|     See the "Bindings" section of README.md for usage information. | ||||
| ]]-- | ||||
|  | ||||
| local bindings = {} | ||||
| @@ -22,56 +22,56 @@ local MP_ENCODE = require('multipart-post').encode | ||||
|  -- response with failure. Returns false and false with a connection error. | ||||
|  -- To mimic old/normal behavior, it errs if used with an invalid method. | ||||
| function bindings:request(method, parameters, file) | ||||
| 	parameters = parameters or {} | ||||
| 	for k,v in pairs(parameters) do | ||||
| 		parameters[k] = tostring(v) | ||||
| 	end | ||||
| 	if file and next(file) ~= nil then | ||||
| 		local file_type, file_name = next(file) | ||||
| 		local file_file = io.open(file_name, 'r') | ||||
| 		local file_data = { | ||||
| 			filename = file_name, | ||||
| 			data = file_file:read('*a') | ||||
| 		} | ||||
| 		file_file:close() | ||||
| 		parameters[file_type] = file_data | ||||
| 	end | ||||
| 	if next(parameters) == nil then | ||||
| 		parameters = {''} | ||||
| 	end | ||||
| 	local response = {} | ||||
| 	local body, boundary = MP_ENCODE(parameters) | ||||
| 	local success, code = HTTPS.request{ | ||||
| 		url = self.BASE_URL .. method, | ||||
| 		method = 'POST', | ||||
| 		headers = { | ||||
| 			["Content-Type"] =	"multipart/form-data; boundary=" .. boundary, | ||||
| 			["Content-Length"] = #body, | ||||
| 		}, | ||||
| 		source = ltn12.source.string(body), | ||||
| 		sink = ltn12.sink.table(response) | ||||
| 	} | ||||
| 	local data = table.concat(response) | ||||
| 	if not success or success == 1 then | ||||
| 		print(method .. ': Connection error. [' .. code  .. ']') | ||||
| 		return false, false | ||||
| 	else | ||||
| 		local result = JSON.decode(data) | ||||
| 		if not result then | ||||
| 			return false, false | ||||
| 		elseif result.ok then | ||||
| 			return result | ||||
| 		else | ||||
| 			assert(result.description ~= 'Method not found', method .. ': Method not found.') | ||||
| 			return false, result | ||||
| 		end | ||||
| 	end | ||||
|     parameters = parameters or {} | ||||
|     for k,v in pairs(parameters) do | ||||
|         parameters[k] = tostring(v) | ||||
|     end | ||||
|     if file and next(file) ~= nil then | ||||
|         local file_type, file_name = next(file) | ||||
|         local file_file = io.open(file_name, 'r') | ||||
|         local file_data = { | ||||
|             filename = file_name, | ||||
|             data = file_file:read('*a') | ||||
|         } | ||||
|         file_file:close() | ||||
|         parameters[file_type] = file_data | ||||
|     end | ||||
|     if next(parameters) == nil then | ||||
|         parameters = {''} | ||||
|     end | ||||
|     local response = {} | ||||
|     local body, boundary = MP_ENCODE(parameters) | ||||
|     local success, code = HTTPS.request{ | ||||
|         url = self.BASE_URL .. method, | ||||
|         method = 'POST', | ||||
|         headers = { | ||||
|             ["Content-Type"] =    "multipart/form-data; boundary=" .. boundary, | ||||
|             ["Content-Length"] = #body, | ||||
|         }, | ||||
|         source = ltn12.source.string(body), | ||||
|         sink = ltn12.sink.table(response) | ||||
|     } | ||||
|     local data = table.concat(response) | ||||
|     if not success or success == 1 then | ||||
|         print(method .. ': Connection error. [' .. code  .. ']') | ||||
|         return false, false | ||||
|     else | ||||
|         local result = JSON.decode(data) | ||||
|         if not result then | ||||
|             return false, false | ||||
|         elseif result.ok then | ||||
|             return result | ||||
|         else | ||||
|             assert(result.description ~= 'Method not found', method .. ': Method not found.') | ||||
|             return false, result | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| function bindings.gen(_, key) | ||||
| 	return function(self, params, file) | ||||
| 		return bindings.request(self, key, params, file) | ||||
| 	end | ||||
|     return function(self, params, file) | ||||
|         return bindings.request(self, key, params, file) | ||||
|     end | ||||
| end | ||||
| setmetatable(bindings, { __index = bindings.gen }) | ||||
|  | ||||
|   | ||||
							
								
								
									
										312
									
								
								otouto/bot.lua
									
									
									
									
									
								
							
							
						
						
									
										312
									
								
								otouto/bot.lua
									
									
									
									
									
								
							| @@ -7,190 +7,190 @@ bot.version = '3.13' | ||||
|  -- Function to be run on start and reload. | ||||
| function bot:init(config) | ||||
|  | ||||
| 	bindings = require('otouto.bindings') | ||||
| 	utilities = require('otouto.utilities') | ||||
|     bindings = require('otouto.bindings') | ||||
|     utilities = require('otouto.utilities') | ||||
|  | ||||
| 	assert( | ||||
| 		config.bot_api_key, | ||||
| 		'You did not set your bot token in the config!' | ||||
| 	) | ||||
| 	self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/' | ||||
|     assert( | ||||
|         config.bot_api_key, | ||||
|         'You did not set your bot token in the config!' | ||||
|     ) | ||||
|     self.BASE_URL = 'https://api.telegram.org/bot' .. config.bot_api_key .. '/' | ||||
|  | ||||
| 	-- Fetch bot information. Try until it succeeds. | ||||
| 	repeat | ||||
| 		print('Fetching bot information...') | ||||
| 		self.info = bindings.getMe(self) | ||||
| 	until self.info | ||||
| 	self.info = self.info.result | ||||
|     -- Fetch bot information. Try until it succeeds. | ||||
|     repeat | ||||
|         print('Fetching bot information...') | ||||
|         self.info = bindings.getMe(self) | ||||
|     until self.info | ||||
|     self.info = self.info.result | ||||
|  | ||||
| 	-- Load the "database"! ;) | ||||
| 	self.database_name = config.database_name or self.info.username .. '.db' | ||||
| 	if not self.database then | ||||
| 		self.database = utilities.load_data(self.database_name) | ||||
| 	end | ||||
|     -- Load the "database"! ;) | ||||
|     self.database_name = config.database_name or self.info.username .. '.db' | ||||
|     if not self.database then | ||||
|         self.database = utilities.load_data(self.database_name) | ||||
|     end | ||||
|  | ||||
| 	-- Migration code 1.12 -> 1.13 | ||||
| 	-- Back to administration global ban list; copy over current blacklist. | ||||
| 	if self.database.version ~= '3.13' then | ||||
| 		if self.database.administration then | ||||
| 			self.database.administration.globalbans = self.database.administration.globalbans or self.database.blacklist or {} | ||||
| 			utilities.save_data(self.database_name, self.database) | ||||
| 			self.database = utilities.load_data(self.database_name) | ||||
| 		end | ||||
| 	end | ||||
| 	-- End migration code. | ||||
|     -- Migration code 1.12 -> 1.13 | ||||
|     -- Back to administration global ban list; copy over current blacklist. | ||||
|     if self.database.version ~= '3.13' then | ||||
|         if self.database.administration then | ||||
|             self.database.administration.globalbans = self.database.administration.globalbans or self.database.blacklist or {} | ||||
|             utilities.save_data(self.database_name, self.database) | ||||
|             self.database = utilities.load_data(self.database_name) | ||||
|         end | ||||
|     end | ||||
|     -- End migration code. | ||||
|  | ||||
| 	-- Table to cache user info (usernames, IDs, etc). | ||||
| 	self.database.users = self.database.users or {} | ||||
| 	-- Table to store userdata (nicknames, lastfm usernames, etc). | ||||
| 	self.database.userdata = self.database.userdata or {} | ||||
| 	-- Table to store the IDs of blacklisted users. | ||||
| 	self.database.blacklist = self.database.blacklist or {} | ||||
| 	-- Save the bot's version in the database to make migration simpler. | ||||
| 	self.database.version = bot.version | ||||
| 	-- Add updated bot info to the user info cache. | ||||
| 	self.database.users[tostring(self.info.id)] = self.info | ||||
|     -- Table to cache user info (usernames, IDs, etc). | ||||
|     self.database.users = self.database.users or {} | ||||
|     -- Table to store userdata (nicknames, lastfm usernames, etc). | ||||
|     self.database.userdata = self.database.userdata or {} | ||||
|     -- Table to store the IDs of blacklisted users. | ||||
|     self.database.blacklist = self.database.blacklist or {} | ||||
|     -- Save the bot's version in the database to make migration simpler. | ||||
|     self.database.version = bot.version | ||||
|     -- Add updated bot info to the user info cache. | ||||
|     self.database.users[tostring(self.info.id)] = self.info | ||||
|  | ||||
| 	-- All plugins go into self.plugins. Plugins which accept forwarded messages | ||||
| 	-- and messages from blacklisted users also go into self.panoptic_plugins. | ||||
| 	self.plugins = {} | ||||
| 	self.panoptic_plugins = {} | ||||
| 	for _, pname in ipairs(config.plugins) do | ||||
| 		local plugin = require('otouto.plugins.'..pname) | ||||
| 		table.insert(self.plugins, plugin) | ||||
| 		if plugin.init then plugin.init(self, config) end | ||||
| 		if plugin.panoptic then table.insert(self.panoptic_plugins, plugin) end | ||||
| 		if plugin.doc then plugin.doc = '```\n'..plugin.doc..'\n```' end | ||||
| 		if not plugin.triggers then plugin.triggers = {} end | ||||
| 	end | ||||
|     -- All plugins go into self.plugins. Plugins which accept forwarded messages | ||||
|     -- and messages from blacklisted users also go into self.panoptic_plugins. | ||||
|     self.plugins = {} | ||||
|     self.panoptic_plugins = {} | ||||
|     for _, pname in ipairs(config.plugins) do | ||||
|         local plugin = require('otouto.plugins.'..pname) | ||||
|         table.insert(self.plugins, plugin) | ||||
|         if plugin.init then plugin.init(self, config) end | ||||
|         if plugin.panoptic then table.insert(self.panoptic_plugins, plugin) end | ||||
|         if plugin.doc then plugin.doc = '```\n'..plugin.doc..'\n```' end | ||||
|         if not plugin.triggers then plugin.triggers = {} end | ||||
|     end | ||||
|  | ||||
| 	print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')') | ||||
|     print('@' .. self.info.username .. ', AKA ' .. self.info.first_name ..' ('..self.info.id..')') | ||||
|  | ||||
| 	-- Set loop variables. | ||||
| 	self.last_update = self.last_update or 0 -- Update offset. | ||||
| 	self.last_cron = self.last_cron or os.date('%M') -- Last cron job. | ||||
| 	self.last_database_save = self.last_database_save or os.date('%H') -- Last db save. | ||||
| 	self.is_started = true | ||||
|     -- Set loop variables. | ||||
|     self.last_update = self.last_update or 0 -- Update offset. | ||||
|     self.last_cron = self.last_cron or os.date('%M') -- Last cron job. | ||||
|     self.last_database_save = self.last_database_save or os.date('%H') -- Last db save. | ||||
|     self.is_started = true | ||||
|  | ||||
| end | ||||
|  | ||||
|  -- Function to be run on each new message. | ||||
| function bot:on_msg_receive(msg, config) | ||||
|  | ||||
| 	-- Do not process old messages. | ||||
| 	if msg.date < os.time() - 5 then return end | ||||
|     -- Do not process old messages. | ||||
|     if msg.date < os.time() - 5 then return end | ||||
|  | ||||
| 	-- plugint is the array of plugins we'll check the message against. | ||||
| 	-- If the message is forwarded or from a blacklisted user, the bot will only | ||||
| 	-- check against panoptic plugins. | ||||
| 	local plugint = self.plugins | ||||
| 	local from_id_str = tostring(msg.from.id) | ||||
|     -- plugint is the array of plugins we'll check the message against. | ||||
|     -- If the message is forwarded or from a blacklisted user, the bot will only | ||||
|     -- check against panoptic plugins. | ||||
|     local plugint = self.plugins | ||||
|     local from_id_str = tostring(msg.from.id) | ||||
|  | ||||
| 	-- Cache user info for those involved. | ||||
| 	self.database.users[from_id_str] = msg.from | ||||
| 	if msg.reply_to_message then | ||||
| 		self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from | ||||
| 	elseif msg.forward_from then | ||||
| 		-- Forwards only go to panoptic plugins. | ||||
| 		plugint = self.panoptic_plugins | ||||
| 		self.database.users[tostring(msg.forward_from.id)] = msg.forward_from | ||||
| 	elseif msg.new_chat_member then | ||||
| 		self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member | ||||
| 	elseif msg.left_chat_member then | ||||
| 		self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member | ||||
| 	end | ||||
|     -- Cache user info for those involved. | ||||
|     self.database.users[from_id_str] = msg.from | ||||
|     if msg.reply_to_message then | ||||
|         self.database.users[tostring(msg.reply_to_message.from.id)] = msg.reply_to_message.from | ||||
|     elseif msg.forward_from then | ||||
|         -- Forwards only go to panoptic plugins. | ||||
|         plugint = self.panoptic_plugins | ||||
|         self.database.users[tostring(msg.forward_from.id)] = msg.forward_from | ||||
|     elseif msg.new_chat_member then | ||||
|         self.database.users[tostring(msg.new_chat_member.id)] = msg.new_chat_member | ||||
|     elseif msg.left_chat_member then | ||||
|         self.database.users[tostring(msg.left_chat_member.id)] = msg.left_chat_member | ||||
|     end | ||||
|  | ||||
| 	-- Messages from blacklisted users only go to panoptic plugins. | ||||
| 	if self.database.blacklist[from_id_str] then | ||||
| 		plugint = self.panoptic_plugins | ||||
| 	end | ||||
|     -- Messages from blacklisted users only go to panoptic plugins. | ||||
|     if self.database.blacklist[from_id_str] then | ||||
|         plugint = self.panoptic_plugins | ||||
|     end | ||||
|  | ||||
| 	-- If no text, use captions. | ||||
| 	msg.text = msg.text or msg.caption or '' | ||||
| 	msg.text_lower = msg.text:lower() | ||||
| 	if msg.reply_to_message then | ||||
| 		msg.reply_to_message.text = msg.reply_to_message.text or msg.reply_to_message.caption or '' | ||||
| 	end | ||||
|     -- If no text, use captions. | ||||
|     msg.text = msg.text or msg.caption or '' | ||||
|     msg.text_lower = msg.text:lower() | ||||
|     if msg.reply_to_message then | ||||
|         msg.reply_to_message.text = msg.reply_to_message.text or msg.reply_to_message.caption or '' | ||||
|     end | ||||
|  | ||||
| 	-- Support deep linking. | ||||
| 	if msg.text:match('^'..config.cmd_pat..'start .+') then | ||||
| 		msg.text = config.cmd_pat .. utilities.input(msg.text) | ||||
| 		msg.text_lower = msg.text:lower() | ||||
| 	end | ||||
|     -- Support deep linking. | ||||
|     if msg.text:match('^'..config.cmd_pat..'start .+') then | ||||
|         msg.text = config.cmd_pat .. utilities.input(msg.text) | ||||
|         msg.text_lower = msg.text:lower() | ||||
|     end | ||||
|  | ||||
| 	-- If the message is forwarded or comes from a blacklisted yser, | ||||
|     -- If the message is forwarded or comes from a blacklisted yser, | ||||
|  | ||||
| 	-- Do the thing. | ||||
| 	for _, plugin in ipairs(plugint) do | ||||
| 		for _, trigger in ipairs(plugin.triggers) do | ||||
| 			if string.match(msg.text_lower, trigger) then | ||||
| 				local success, result = pcall(function() | ||||
| 					return plugin.action(self, msg, config) | ||||
| 				end) | ||||
| 				if not success then | ||||
| 					-- 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) | ||||
| 					end | ||||
| 					utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) | ||||
| 					msg = nil | ||||
| 					return | ||||
| 				-- Continue if the return value is true. | ||||
| 				elseif result ~= true then | ||||
| 					msg = nil | ||||
| 					return | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	msg = nil | ||||
|     -- Do the thing. | ||||
|     for _, plugin in ipairs(plugint) do | ||||
|         for _, trigger in ipairs(plugin.triggers) do | ||||
|             if string.match(msg.text_lower, trigger) then | ||||
|                 local success, result = pcall(function() | ||||
|                     return plugin.action(self, msg, config) | ||||
|                 end) | ||||
|                 if not success then | ||||
|                     -- 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) | ||||
|                     end | ||||
|                     utilities.handle_exception(self, result, msg.from.id .. ': ' .. msg.text, config) | ||||
|                     msg = nil | ||||
|                     return | ||||
|                 -- Continue if the return value is true. | ||||
|                 elseif result ~= true then | ||||
|                     msg = nil | ||||
|                     return | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|     end | ||||
|     msg = nil | ||||
|  | ||||
| end | ||||
|  | ||||
|  -- main | ||||
| function bot:run(config) | ||||
| 	bot.init(self, config) | ||||
| 	while self.is_started do | ||||
| 		-- Update loop. | ||||
| 		local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update + 1 } ) | ||||
| 		if res then | ||||
| 			-- Iterate over every new message. | ||||
| 			for _,v in ipairs(res.result) do | ||||
| 				self.last_update = v.update_id | ||||
| 				if v.message then | ||||
| 					bot.on_msg_receive(self, v.message, config) | ||||
| 				end | ||||
| 			end | ||||
| 		else | ||||
| 			print('Connection error while fetching updates.') | ||||
| 		end | ||||
|     bot.init(self, config) | ||||
|     while self.is_started do | ||||
|         -- Update loop. | ||||
|         local res = bindings.getUpdates(self, { timeout = 20, offset = self.last_update + 1 } ) | ||||
|         if res then | ||||
|             -- Iterate over every new message. | ||||
|             for _,v in ipairs(res.result) do | ||||
|                 self.last_update = v.update_id | ||||
|                 if v.message then | ||||
|                     bot.on_msg_receive(self, v.message, config) | ||||
|                 end | ||||
|             end | ||||
|         else | ||||
|             print('Connection error while fetching updates.') | ||||
|         end | ||||
|  | ||||
| 		-- Run cron jobs every minute. | ||||
| 		if self.last_cron ~= os.date('%M') then | ||||
| 			self.last_cron = os.date('%M') | ||||
| 			for i,v in ipairs(self.plugins) do | ||||
| 				if v.cron then -- Call each plugin's cron function, if it has one. | ||||
| 					local result, err = pcall(function() v.cron(self, config) end) | ||||
| 					if not result then | ||||
| 						utilities.handle_exception(self, err, 'CRON: ' .. i, config) | ||||
| 					end | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
|         -- Run cron jobs every minute. | ||||
|         if self.last_cron ~= os.date('%M') then | ||||
|             self.last_cron = os.date('%M') | ||||
|             for i,v in ipairs(self.plugins) do | ||||
|                 if v.cron then -- Call each plugin's cron function, if it has one. | ||||
|                     local result, err = pcall(function() v.cron(self, config) end) | ||||
|                     if not result then | ||||
|                         utilities.handle_exception(self, err, 'CRON: ' .. i, config) | ||||
|                     end | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|  | ||||
| 		-- Save the "database" every hour. | ||||
| 		if self.last_database_save ~= os.date('%H') then | ||||
| 			self.last_database_save = os.date('%H') | ||||
| 			utilities.save_data(self.database_name, self.database) | ||||
| 		end | ||||
| 	end | ||||
| 	-- Save the database before exiting. | ||||
| 	utilities.save_data(self.database_name, self.database) | ||||
| 	print('Halted.') | ||||
|         -- Save the "database" every hour. | ||||
|         if self.last_database_save ~= os.date('%H') then | ||||
|             self.last_database_save = os.date('%H') | ||||
|             utilities.save_data(self.database_name, self.database) | ||||
|         end | ||||
|     end | ||||
|     -- Save the database before exiting. | ||||
|     utilities.save_data(self.database_name, self.database) | ||||
|     print('Halted.') | ||||
| end | ||||
|  | ||||
| return bot | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| --[[ | ||||
| 	drua-tg | ||||
| 	Based on JuanPotato's lua-tg (https://github.com/juanpotato/lua-tg), | ||||
| 	modified to work more naturally from an API bot. | ||||
|     drua-tg | ||||
|     Based on JuanPotato's lua-tg (https://github.com/juanpotato/lua-tg), | ||||
|     modified to work more naturally from an API bot. | ||||
|  | ||||
| 	Usage: | ||||
| 		drua = require('drua-tg') | ||||
| 		drua.IP = 'localhost' -- 'localhost' is default | ||||
| 		drua.PORT = 4567 -- 4567 is default | ||||
| 		drua.message(chat_id, text) | ||||
|     Usage: | ||||
|         drua = require('drua-tg') | ||||
|         drua.IP = 'localhost' -- 'localhost' is default | ||||
|         drua.PORT = 4567 -- 4567 is default | ||||
|         drua.message(chat_id, text) | ||||
|  | ||||
| The MIT License (MIT) | ||||
|  | ||||
| @@ -35,150 +35,150 @@ SOFTWARE. | ||||
| local SOCKET = require('socket') | ||||
|  | ||||
| local comtab = { | ||||
| 	add = { 'chat_add_user %s %s', 'channel_invite %s %s' }, | ||||
| 	kick = { 'chat_del_user %s %s', 'channel_kick %s %s' }, | ||||
| 	rename = { 'rename_chat %s "%s"', 'rename_channel %s "%s"' }, | ||||
| 	link = { 'export_chat_link %s', 'export_channel_link %s' }, | ||||
| 	photo_set = { 'chat_set_photo %s %s', 'channel_set_photo %s %s' }, | ||||
| 	photo_get = { [0] = 'load_user_photo %s', 'load_chat_photo %s', 'load_channel_photo %s' }, | ||||
| 	info = { [0] = 'user_info %s', 'chat_info %s', 'channel_info %s' } | ||||
|     add = { 'chat_add_user %s %s', 'channel_invite %s %s' }, | ||||
|     kick = { 'chat_del_user %s %s', 'channel_kick %s %s' }, | ||||
|     rename = { 'rename_chat %s "%s"', 'rename_channel %s "%s"' }, | ||||
|     link = { 'export_chat_link %s', 'export_channel_link %s' }, | ||||
|     photo_set = { 'chat_set_photo %s %s', 'channel_set_photo %s %s' }, | ||||
|     photo_get = { [0] = 'load_user_photo %s', 'load_chat_photo %s', 'load_channel_photo %s' }, | ||||
|     info = { [0] = 'user_info %s', 'chat_info %s', 'channel_info %s' } | ||||
| } | ||||
|  | ||||
| local format_target = function(target) | ||||
| 	target = tonumber(target) | ||||
| 	if target < -1000000000000 then | ||||
| 		target = 'channel#' .. math.abs(target) - 1000000000000 | ||||
| 		return target, 2 | ||||
| 	elseif target < 0 then | ||||
| 		target = 'chat#' .. math.abs(target) | ||||
| 		return target, 1 | ||||
| 	else | ||||
| 		target = 'user#' .. target | ||||
| 		return target, 0 | ||||
| 	end | ||||
|     target = tonumber(target) | ||||
|     if target < -1000000000000 then | ||||
|         target = 'channel#' .. math.abs(target) - 1000000000000 | ||||
|         return target, 2 | ||||
|     elseif target < 0 then | ||||
|         target = 'chat#' .. math.abs(target) | ||||
|         return target, 1 | ||||
|     else | ||||
|         target = 'user#' .. target | ||||
|         return target, 0 | ||||
|     end | ||||
| end | ||||
|  | ||||
| local escape = function(text) | ||||
| 	text = text:gsub('\\', '\\\\') | ||||
| 	text = text:gsub('\n', '\\n') | ||||
| 	text = text:gsub('\t', '\\t') | ||||
| 	text = text:gsub('"', '\\"') | ||||
| 	return text | ||||
|     text = text:gsub('\\', '\\\\') | ||||
|     text = text:gsub('\n', '\\n') | ||||
|     text = text:gsub('\t', '\\t') | ||||
|     text = text:gsub('"', '\\"') | ||||
|     return text | ||||
| end | ||||
|  | ||||
| local drua = { | ||||
| 	IP = 'localhost', | ||||
| 	PORT = 4567 | ||||
|     IP = 'localhost', | ||||
|     PORT = 4567 | ||||
| } | ||||
|  | ||||
| drua.send = function(command, do_receive) | ||||
| 	local s = SOCKET.connect(drua.IP, drua.PORT) | ||||
| 	assert(s, '\nUnable to connect to tg session.') | ||||
| 	s:send(command..'\n') | ||||
| 	local output | ||||
| 	if do_receive then | ||||
| 		output = string.match(s:receive('*l'), 'ANSWER (%d+)') | ||||
| 		output = s:receive(tonumber(output)):gsub('\n$', '') | ||||
| 	end | ||||
| 	s:close() | ||||
| 	return output | ||||
|     local s = SOCKET.connect(drua.IP, drua.PORT) | ||||
|     assert(s, '\nUnable to connect to tg session.') | ||||
|     s:send(command..'\n') | ||||
|     local output | ||||
|     if do_receive then | ||||
|         output = string.match(s:receive('*l'), 'ANSWER (%d+)') | ||||
|         output = s:receive(tonumber(output)):gsub('\n$', '') | ||||
|     end | ||||
|     s:close() | ||||
|     return output | ||||
| end | ||||
|  | ||||
| drua.message = function(target, text) | ||||
| 	target = format_target(target) | ||||
| 	text = escape(text) | ||||
| 	local command = 'msg %s "%s"' | ||||
| 	command = command:format(target, text) | ||||
| 	return drua.send(command) | ||||
|     target = format_target(target) | ||||
|     text = escape(text) | ||||
|     local command = 'msg %s "%s"' | ||||
|     command = command:format(target, text) | ||||
|     return drua.send(command) | ||||
| end | ||||
|  | ||||
| drua.send_photo = function(target, photo) | ||||
| 	target = format_target(target) | ||||
| 	local command = 'send_photo %s %s' | ||||
| 	command = command:format(target, photo) | ||||
| 	return drua.send(command) | ||||
|     target = format_target(target) | ||||
|     local command = 'send_photo %s %s' | ||||
|     command = command:format(target, photo) | ||||
|     return drua.send(command) | ||||
| end | ||||
|  | ||||
| drua.add_user = function(chat, target) | ||||
| 	local a | ||||
| 	chat, a = format_target(chat) | ||||
| 	target = format_target(target) | ||||
| 	local command = comtab.add[a]:format(chat, target) | ||||
| 	return drua.send(command) | ||||
|     local a | ||||
|     chat, a = format_target(chat) | ||||
|     target = format_target(target) | ||||
|     local command = comtab.add[a]:format(chat, target) | ||||
|     return drua.send(command) | ||||
| end | ||||
|  | ||||
| drua.kick_user = function(chat, target) | ||||
| 	-- Get the group info so tg will recognize the target. | ||||
| 	drua.get_info(chat) | ||||
| 	local a | ||||
| 	chat, a = format_target(chat) | ||||
| 	target = format_target(target) | ||||
| 	local command = comtab.kick[a]:format(chat, target) | ||||
| 	return drua.send(command) | ||||
|     -- Get the group info so tg will recognize the target. | ||||
|     drua.get_info(chat) | ||||
|     local a | ||||
|     chat, a = format_target(chat) | ||||
|     target = format_target(target) | ||||
|     local command = comtab.kick[a]:format(chat, target) | ||||
|     return drua.send(command) | ||||
| end | ||||
|  | ||||
| drua.rename_chat = function(chat, name) | ||||
| 	local a | ||||
| 	chat, a = format_target(chat) | ||||
| 	local command = comtab.rename[a]:format(chat, name) | ||||
| 	return drua.send(command) | ||||
|     local a | ||||
|     chat, a = format_target(chat) | ||||
|     local command = comtab.rename[a]:format(chat, name) | ||||
|     return drua.send(command) | ||||
| end | ||||
|  | ||||
| drua.export_link = function(chat) | ||||
| 	local a | ||||
| 	chat, a = format_target(chat) | ||||
| 	local command = comtab.link[a]:format(chat) | ||||
| 	return drua.send(command, true) | ||||
|     local a | ||||
|     chat, a = format_target(chat) | ||||
|     local command = comtab.link[a]:format(chat) | ||||
|     return drua.send(command, true) | ||||
| end | ||||
|  | ||||
| drua.get_photo = function(chat) | ||||
| 	local a | ||||
| 	chat, a = format_target(chat) | ||||
| 	local command = comtab.photo_get[a]:format(chat) | ||||
| 	local output = drua.send(command, true) | ||||
| 	if output:match('FAIL') then | ||||
| 		return false | ||||
| 	else | ||||
| 		return output:match('Saved to (.+)') | ||||
| 	end | ||||
|     local a | ||||
|     chat, a = format_target(chat) | ||||
|     local command = comtab.photo_get[a]:format(chat) | ||||
|     local output = drua.send(command, true) | ||||
|     if output:match('FAIL') then | ||||
|         return false | ||||
|     else | ||||
|         return output:match('Saved to (.+)') | ||||
|     end | ||||
| end | ||||
|  | ||||
| drua.set_photo = function(chat, photo) | ||||
| 	local a | ||||
| 	chat, a = format_target(chat) | ||||
| 	local command = comtab.photo_set[a]:format(chat, photo) | ||||
| 	return drua.send(command) | ||||
|     local a | ||||
|     chat, a = format_target(chat) | ||||
|     local command = comtab.photo_set[a]:format(chat, photo) | ||||
|     return drua.send(command) | ||||
| end | ||||
|  | ||||
| drua.get_info = function(target) | ||||
| 	local a | ||||
| 	target, a = format_target(target) | ||||
| 	local command = comtab.info[a]:format(target) | ||||
| 	return drua.send(command, true) | ||||
|     local a | ||||
|     target, a = format_target(target) | ||||
|     local command = comtab.info[a]:format(target) | ||||
|     return drua.send(command, true) | ||||
| end | ||||
|  | ||||
| drua.channel_set_admin = function(chat, user, rank) | ||||
| 	chat = format_target(chat) | ||||
| 	user = format_target(user) | ||||
| 	local command = 'channel_set_admin %s %s %s' | ||||
| 	command = command:format(chat, user, rank) | ||||
| 	return drua.send(command) | ||||
|     chat = format_target(chat) | ||||
|     user = format_target(user) | ||||
|     local command = 'channel_set_admin %s %s %s' | ||||
|     command = command:format(chat, user, rank) | ||||
|     return drua.send(command) | ||||
| end | ||||
|  | ||||
| drua.channel_set_about = function(chat, text) | ||||
| 	chat = format_target(chat) | ||||
| 	text = escape(text) | ||||
| 	local command = 'channel_set_about %s "%s"' | ||||
| 	command = command:format(chat, text) | ||||
| 	return drua.send(command) | ||||
|     chat = format_target(chat) | ||||
|     text = escape(text) | ||||
|     local command = 'channel_set_about %s "%s"' | ||||
|     command = command:format(chat, text) | ||||
|     return drua.send(command) | ||||
| end | ||||
|  | ||||
| drua.block = function(user) | ||||
| 	return drua.send('block_user user#' .. user) | ||||
|     return drua.send('block_user user#' .. user) | ||||
| end | ||||
|  | ||||
| drua.unblock = function(user) | ||||
| 	return drua.send('unblock_user user#' .. user) | ||||
|     return drua.send('unblock_user user#' .. user) | ||||
| end | ||||
|  | ||||
| return drua | ||||
|   | ||||
| @@ -7,13 +7,13 @@ about.command = 'about' | ||||
| about.doc = 'Returns information about the bot.' | ||||
|  | ||||
| function about:init(config) | ||||
| 	about.text = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by topkecleon.' | ||||
| 	about.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('about'):t('start').table | ||||
|     about.text = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by topkecleon.' | ||||
|     about.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('about'):t('start').table | ||||
| end | ||||
|  | ||||
| function about:action(msg, config) | ||||
| 	utilities.send_message(self, msg.chat.id, about.text, true, nil, true) | ||||
|     utilities.send_message(self, msg.chat.id, about.text, true, nil, true) | ||||
| end | ||||
|  | ||||
| return about | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -10,47 +10,47 @@ local utilities = require('otouto.utilities') | ||||
| apod.command = 'apod [date]' | ||||
|  | ||||
| function apod:init(config) | ||||
| 	apod.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('apod', true).table | ||||
| 	apod.doc = [[ | ||||
|     apod.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('apod', true).table | ||||
|     apod.doc = [[ | ||||
| /apod [YYYY-MM-DD] | ||||
| Returns the Astronomy Picture of the Day. | ||||
| Source: nasa.gov | ||||
| 	]] | ||||
| 	apod.doc = apod.doc:gsub('/', config.cmd_pat) | ||||
| 	apod.base_url = 'https://api.nasa.gov/planetary/apod?api_key=' .. (config.nasa_api_key or 'DEMO_KEY') | ||||
|     ]] | ||||
|     apod.doc = apod.doc:gsub('/', config.cmd_pat) | ||||
|     apod.base_url = 'https://api.nasa.gov/planetary/apod?api_key=' .. (config.nasa_api_key or 'DEMO_KEY') | ||||
| end | ||||
|  | ||||
| function apod:action(msg, config) | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	local url = apod.base_url | ||||
| 	local date = os.date('%F') | ||||
| 	if input then | ||||
| 		if input:match('^(%d+)%-(%d+)%-(%d+)$') then | ||||
| 			url = url .. '&date=' .. URL.escape(input) | ||||
| 			date = input | ||||
| 		end | ||||
| 	end | ||||
|     local input = utilities.input(msg.text) | ||||
|     local url = apod.base_url | ||||
|     local date = os.date('%F') | ||||
|     if input then | ||||
|         if input:match('^(%d+)%-(%d+)%-(%d+)$') then | ||||
|             url = url .. '&date=' .. URL.escape(input) | ||||
|             date = input | ||||
|         end | ||||
|     end | ||||
|  | ||||
| 	local jstr, code = HTTPS.request(url) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local jstr, code = HTTPS.request(url) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	if data.error then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     local data = JSON.decode(jstr) | ||||
|     if data.error then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local output = string.format( | ||||
| 		'<b>%s (</b><a href="%s">%s</a><b>)</b>\n%s', | ||||
| 		utilities.html_escape(data.title), | ||||
| 		utilities.html_escape(data.hdurl or data.url), | ||||
| 		date, | ||||
| 		utilities.html_escape(data.explanation) | ||||
| 	) | ||||
| 	utilities.send_message(self, msg.chat.id, output, false, nil, 'html') | ||||
|     local output = string.format( | ||||
|         '<b>%s (</b><a href="%s">%s</a><b>)</b>\n%s', | ||||
|         utilities.html_escape(data.title), | ||||
|         utilities.html_escape(data.hdurl or data.url), | ||||
|         date, | ||||
|         utilities.html_escape(data.explanation) | ||||
|     ) | ||||
|     utilities.send_message(self, msg.chat.id, output, false, nil, 'html') | ||||
| end | ||||
|  | ||||
| return apod | ||||
|   | ||||
| @@ -5,8 +5,8 @@ local utilities = require('otouto.utilities') | ||||
| bandersnatch.command = 'bandersnatch' | ||||
|  | ||||
| function bandersnatch:init(config) | ||||
| 	bandersnatch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bandersnatch'):t('bc').table | ||||
| 	bandersnatch.doc = 'Shun the frumious Bandersnatch. \nAlias: ' .. config.cmd_pat .. 'bc' | ||||
|     bandersnatch.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bandersnatch'):t('bc').table | ||||
|     bandersnatch.doc = 'Shun the frumious Bandersnatch. \nAlias: ' .. config.cmd_pat .. 'bc' | ||||
| end | ||||
|  | ||||
| local fullnames = { "Wimbledon Tennismatch", "Rinkydink Curdlesnoot", "Butawhiteboy Cantbekhan", "Benadryl Claritin", "Bombadil Rivendell", "Wanda's Crotchfruit", "Biblical Concubine", "Syphilis Cankersore", "Buckminster Fullerene", "Bourgeoisie Capitalist" } | ||||
| @@ -17,15 +17,15 @@ local lastnames = { "Coddleswort", "Crumplesack", "Curdlesnoot", "Calldispatch", | ||||
|  | ||||
| function bandersnatch:action(msg) | ||||
|  | ||||
| 	local output | ||||
|     local output | ||||
|  | ||||
| 	if math.random(10) == 10 then | ||||
| 		output = fullnames[math.random(#fullnames)] | ||||
| 	else | ||||
| 		output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)] | ||||
| 	end | ||||
|     if math.random(10) == 10 then | ||||
|         output = fullnames[math.random(#fullnames)] | ||||
|     else | ||||
|         output = firstnames[math.random(#firstnames)] .. ' ' .. lastnames[math.random(#lastnames)] | ||||
|     end | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, '_'..output..'_', true, nil, true) | ||||
|     utilities.send_message(self, msg.chat.id, '_'..output..'_', true, nil, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -5,12 +5,12 @@ local URL = require('socket.url') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function bible:init(config) | ||||
| 	assert(config.biblia_api_key, | ||||
| 		'bible.lua requires a Biblia API key from http://api.biblia.com.' | ||||
| 	) | ||||
|     assert(config.biblia_api_key, | ||||
|         'bible.lua requires a Biblia API key from http://api.biblia.com.' | ||||
|     ) | ||||
|  | ||||
| 	bible.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bible', true):t('b', true).table | ||||
| 	bible.doc = config.cmd_pat .. [[bible <reference> | ||||
|     bible.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('bible', true):t('b', true).table | ||||
|     bible.doc = config.cmd_pat .. [[bible <reference> | ||||
| Returns a verse from the American Standard Version of the Bible, or an apocryphal verse from the King James Version. Results from biblia.com. | ||||
| Alias: ]] .. config.cmd_pat .. 'b' | ||||
| end | ||||
| @@ -19,30 +19,30 @@ bible.command = 'bible <reference>' | ||||
|  | ||||
| function bible:action(msg, config) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, bible.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, bible.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = 'http://api.biblia.com/v1/bible/content/ASV.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) | ||||
|     local url = 'http://api.biblia.com/v1/bible/content/ASV.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) | ||||
|  | ||||
| 	local output, res = HTTP.request(url) | ||||
|     local output, res = HTTP.request(url) | ||||
|  | ||||
| 	if not output or res ~= 200 or output:len() == 0 then | ||||
| 		url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) | ||||
| 		output, res = HTTP.request(url) | ||||
| 	end | ||||
|     if not output or res ~= 200 or output:len() == 0 then | ||||
|         url = 'http://api.biblia.com/v1/bible/content/KJVAPOC.txt?key=' .. config.biblia_api_key .. '&passage=' .. URL.escape(input) | ||||
|         output, res = HTTP.request(url) | ||||
|     end | ||||
|  | ||||
| 	if not output or res ~= 200  or output:len() == 0 then | ||||
| 		output = config.errors.results | ||||
| 	end | ||||
|     if not output or res ~= 200  or output:len() == 0 then | ||||
|         output = config.errors.results | ||||
|     end | ||||
|  | ||||
| 	if output:len() > 4000 then | ||||
| 		output = 'The text is too long to post here. Try being more specific.' | ||||
| 	end | ||||
|     if output:len() > 4000 then | ||||
|         output = 'The text is too long to post here. Try being more specific.' | ||||
|     end | ||||
|  | ||||
| 	utilities.send_reply(self, msg, output) | ||||
|     utilities.send_reply(self, msg, output) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -14,65 +14,65 @@ bing.command = 'bing <query>' | ||||
| bing.search_url = 'https://api.datamarket.azure.com/Data.ashx/Bing/Search/Web?Query=\'%s\'&$format=json' | ||||
|  | ||||
| function bing:init(config) | ||||
| 	assert(config.bing_api_key, | ||||
| 		'bing.lua requires a Bing API key from http://datamarket.azure.com/dataset/bing/search.' | ||||
| 	) | ||||
|     assert(config.bing_api_key, | ||||
|         'bing.lua requires a Bing API key from http://datamarket.azure.com/dataset/bing/search.' | ||||
|     ) | ||||
|  | ||||
| 	bing.headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. config.bing_api_key) } | ||||
| 	bing.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('bing', true):t('g', true):t('google', true).table | ||||
| 	bing.doc = [[ | ||||
|     bing.headers = { ["Authorization"] = "Basic " .. mime.b64(":" .. config.bing_api_key) } | ||||
|     bing.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('bing', true):t('g', true):t('google', true).table | ||||
|     bing.doc = [[ | ||||
| /bing <query> | ||||
| Returns the top web results from Bing. | ||||
| Aliases: /g, /google | ||||
| 	]] | ||||
| 	bing.doc = bing.doc:gsub('/', config.cmd_pat) | ||||
|     ]] | ||||
|     bing.doc = bing.doc:gsub('/', config.cmd_pat) | ||||
|  | ||||
| end | ||||
|  | ||||
| function bing:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, bing.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, bing.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = bing.search_url:format(URL.escape(input)) | ||||
| 	local resbody = {} | ||||
| 	local _, code = https.request{ | ||||
| 	    url = url, | ||||
| 	    headers = bing.headers, | ||||
| 	    sink = ltn12.sink.table(resbody), | ||||
| 	} | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local url = bing.search_url:format(URL.escape(input)) | ||||
|     local resbody = {} | ||||
|     local _, code = https.request{ | ||||
|         url = url, | ||||
|         headers = bing.headers, | ||||
|         sink = ltn12.sink.table(resbody), | ||||
|     } | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local data = JSON.decode(table.concat(resbody)) | ||||
| 	-- Four results in a group, eight in private. | ||||
| 	local limit = msg.chat.type == 'private' and 8 or 4 | ||||
| 	-- No more results than provided. | ||||
| 	limit = limit > #data.d.results and #data.d.results or limit | ||||
| 	if limit == 0 then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     local data = JSON.decode(table.concat(resbody)) | ||||
|     -- Four results in a group, eight in private. | ||||
|     local limit = msg.chat.type == 'private' and 8 or 4 | ||||
|     -- No more results than provided. | ||||
|     limit = limit > #data.d.results and #data.d.results or limit | ||||
|     if limit == 0 then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local reslist = {} | ||||
| 	for i = 1, limit do | ||||
| 		table.insert(reslist, string.format( | ||||
| 			'• <a href="%s">%s</a>', | ||||
| 			utilities.html_escape(data.d.results[i].Url), | ||||
| 			utilities.html_escape(data.d.results[i].Title) | ||||
| 		)) | ||||
| 	end | ||||
| 	local output = string.format( | ||||
| 		'<b>Search results for</b> <i>%s</i><b>:</b>\n%s', | ||||
| 		utilities.html_escape(input), | ||||
| 		table.concat(reslist, '\n') | ||||
| 	) | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, 'html') | ||||
|     local reslist = {} | ||||
|     for i = 1, limit do | ||||
|         table.insert(reslist, string.format( | ||||
|             '• <a href="%s">%s</a>', | ||||
|             utilities.html_escape(data.d.results[i].Url), | ||||
|             utilities.html_escape(data.d.results[i].Title) | ||||
|         )) | ||||
|     end | ||||
|     local output = string.format( | ||||
|         '<b>Search results for</b> <i>%s</i><b>:</b>\n%s', | ||||
|         utilities.html_escape(input), | ||||
|         table.concat(reslist, '\n') | ||||
|     ) | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, 'html') | ||||
| end | ||||
|  | ||||
| return bing | ||||
|   | ||||
| @@ -3,92 +3,92 @@ local utilities = require('otouto.utilities') | ||||
| local blacklist = {} | ||||
|  | ||||
| function blacklist:init(config) | ||||
| 	blacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('blacklist', true):t('unblacklist', true).table | ||||
| 	blacklist.error = false | ||||
|     blacklist.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('blacklist', true):t('unblacklist', true).table | ||||
|     blacklist.error = false | ||||
| end | ||||
|  | ||||
| function blacklist:action(msg, config) | ||||
| 	if msg.from.id ~= config.admin then return true end | ||||
| 	local targets = {} | ||||
| 	if msg.reply_to_message then | ||||
| 		table.insert(targets, { | ||||
| 			id = msg.reply_to_message.from.id, | ||||
| 			id_str = tostring(msg.reply_to_message.from.id), | ||||
| 			name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) | ||||
| 		}) | ||||
| 	else | ||||
| 		local input = utilities.input(msg.text) | ||||
| 		if input then | ||||
| 			for user in input:gmatch('%g+') do | ||||
| 				if self.database.users[user] then | ||||
| 					table.insert(targets, { | ||||
| 						id = self.database.users[user].id, | ||||
| 						id_str = tostring(self.database.users[user].id), | ||||
| 						name = utilities.build_name(self.database.users[user].first_name, self.database.users[user].last_name) | ||||
| 					}) | ||||
| 				elseif tonumber(user) then | ||||
| 					local t = { | ||||
| 						id_str = user, | ||||
| 						id = tonumber(user) | ||||
| 					} | ||||
| 					if tonumber(user) < 0 then | ||||
| 						t.name = 'Group (' .. user .. ')' | ||||
| 					else | ||||
| 						t.name = 'Unknown (' .. user .. ')' | ||||
| 					end | ||||
| 					table.insert(targets, t) | ||||
| 				elseif user:match('^@') then | ||||
| 					local u = utilities.resolve_username(self, user) | ||||
| 					if u then | ||||
| 						table.insert(targets, { | ||||
| 							id = u.id, | ||||
| 							id_str = tostring(u.id), | ||||
| 							name = utilities.build_name(u.first_name, u.last_name) | ||||
| 						}) | ||||
| 					else | ||||
| 						table.insert(targets, { err = 'Sorry, I do not recognize that username ('..user..').' }) | ||||
| 					end | ||||
| 				else | ||||
| 					table.insert(targets, { err = 'Invalid username or ID ('..user..').' }) | ||||
| 				end | ||||
| 			end | ||||
| 		else | ||||
| 			utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID, or a group or groups via ID.') | ||||
| 			return | ||||
| 		end | ||||
| 	end | ||||
| 	local output = '' | ||||
| 	if msg.text:match('^'..config.cmd_pat..'blacklist') then | ||||
| 		for _, target in ipairs(targets) do | ||||
| 			if target.err then | ||||
| 				output = output .. target.err .. '\n' | ||||
| 			elseif self.database.blacklist[target.id_str] then | ||||
| 				output = output .. target.name .. ' is already blacklisted.\n' | ||||
| 			else | ||||
| 				self.database.blacklist[target.id_str] = true | ||||
| 				output = output .. target.name .. ' is now blacklisted.\n' | ||||
| 				if config.drua_block_on_blacklist and target.id > 0 then | ||||
| 					require('otouto.drua-tg').block(target.id) | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	elseif msg.text:match('^'..config.cmd_pat..'unblacklist') then | ||||
| 		for _, target in ipairs(targets) do | ||||
| 			if target.err then | ||||
| 				output = output .. target.err .. '\n' | ||||
| 			elseif not self.database.blacklist[target.id_str] then | ||||
| 				output = output .. target.name .. ' is not blacklisted.\n' | ||||
| 			else | ||||
| 				self.database.blacklist[target.id_str] = nil | ||||
| 				output = output .. target.name .. ' is no longer blacklisted.\n' | ||||
| 				if config.drua_block_on_blacklist and target.id > 0 then | ||||
| 					require('otouto.drua-tg').unblock(target.id) | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	utilities.send_reply(self, msg, output) | ||||
|     if msg.from.id ~= config.admin then return true end | ||||
|     local targets = {} | ||||
|     if msg.reply_to_message then | ||||
|         table.insert(targets, { | ||||
|             id = msg.reply_to_message.from.id, | ||||
|             id_str = tostring(msg.reply_to_message.from.id), | ||||
|             name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) | ||||
|         }) | ||||
|     else | ||||
|         local input = utilities.input(msg.text) | ||||
|         if input then | ||||
|             for user in input:gmatch('%g+') do | ||||
|                 if self.database.users[user] then | ||||
|                     table.insert(targets, { | ||||
|                         id = self.database.users[user].id, | ||||
|                         id_str = tostring(self.database.users[user].id), | ||||
|                         name = utilities.build_name(self.database.users[user].first_name, self.database.users[user].last_name) | ||||
|                     }) | ||||
|                 elseif tonumber(user) then | ||||
|                     local t = { | ||||
|                         id_str = user, | ||||
|                         id = tonumber(user) | ||||
|                     } | ||||
|                     if tonumber(user) < 0 then | ||||
|                         t.name = 'Group (' .. user .. ')' | ||||
|                     else | ||||
|                         t.name = 'Unknown (' .. user .. ')' | ||||
|                     end | ||||
|                     table.insert(targets, t) | ||||
|                 elseif user:match('^@') then | ||||
|                     local u = utilities.resolve_username(self, user) | ||||
|                     if u then | ||||
|                         table.insert(targets, { | ||||
|                             id = u.id, | ||||
|                             id_str = tostring(u.id), | ||||
|                             name = utilities.build_name(u.first_name, u.last_name) | ||||
|                         }) | ||||
|                     else | ||||
|                         table.insert(targets, { err = 'Sorry, I do not recognize that username ('..user..').' }) | ||||
|                     end | ||||
|                 else | ||||
|                     table.insert(targets, { err = 'Invalid username or ID ('..user..').' }) | ||||
|                 end | ||||
|             end | ||||
|         else | ||||
|             utilities.send_reply(self, msg, 'Please specify a user or users via reply, username, or ID, or a group or groups via ID.') | ||||
|             return | ||||
|         end | ||||
|     end | ||||
|     local output = '' | ||||
|     if msg.text:match('^'..config.cmd_pat..'blacklist') then | ||||
|         for _, target in ipairs(targets) do | ||||
|             if target.err then | ||||
|                 output = output .. target.err .. '\n' | ||||
|             elseif self.database.blacklist[target.id_str] then | ||||
|                 output = output .. target.name .. ' is already blacklisted.\n' | ||||
|             else | ||||
|                 self.database.blacklist[target.id_str] = true | ||||
|                 output = output .. target.name .. ' is now blacklisted.\n' | ||||
|                 if config.drua_block_on_blacklist and target.id > 0 then | ||||
|                     require('otouto.drua-tg').block(target.id) | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|     elseif msg.text:match('^'..config.cmd_pat..'unblacklist') then | ||||
|         for _, target in ipairs(targets) do | ||||
|             if target.err then | ||||
|                 output = output .. target.err .. '\n' | ||||
|             elseif not self.database.blacklist[target.id_str] then | ||||
|                 output = output .. target.name .. ' is not blacklisted.\n' | ||||
|             else | ||||
|                 self.database.blacklist[target.id_str] = nil | ||||
|                 output = output .. target.name .. ' is no longer blacklisted.\n' | ||||
|                 if config.drua_block_on_blacklist and target.id > 0 then | ||||
|                     require('otouto.drua-tg').unblock(target.id) | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|     end | ||||
|     utilities.send_reply(self, msg, output) | ||||
| end | ||||
|  | ||||
| return blacklist | ||||
|   | ||||
| @@ -7,22 +7,22 @@ local utilities = require('otouto.utilities') | ||||
| calc.command = 'calc <expression>' | ||||
|  | ||||
| function calc:init(config) | ||||
| 	calc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('calc', true).table | ||||
| 	calc.doc = config.cmd_pat .. [[calc <expression> | ||||
|     calc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('calc', true).table | ||||
|     calc.doc = config.cmd_pat .. [[calc <expression> | ||||
| Returns solutions to mathematical expressions and conversions between common units. Results provided by mathjs.org.]] | ||||
| end | ||||
|  | ||||
| function calc:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, calc.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, calc.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input) | ||||
| 	local output = HTTPS.request(url) | ||||
| 	output = output and '`'..output..'`' or config.errors.connection | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|     local url = 'https://api.mathjs.org/v1/?expr=' .. URL.escape(input) | ||||
|     local output = HTTPS.request(url) | ||||
|     output = output and '`'..output..'`' or config.errors.connection | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
| end | ||||
|  | ||||
| return calc | ||||
|   | ||||
| @@ -7,22 +7,22 @@ local utilities = require('otouto.utilities') | ||||
| local catfact = {} | ||||
|  | ||||
| function catfact:init(config) | ||||
| 	catfact.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('catfact', true).table | ||||
| 	catfact.command = 'catfact' | ||||
| 	catfact.doc = 'Returns a cat fact.' | ||||
| 	catfact.url = 'http://catfacts-api.appspot.com/api/facts' | ||||
|     catfact.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('catfact', true).table | ||||
|     catfact.command = 'catfact' | ||||
|     catfact.doc = 'Returns a cat fact.' | ||||
|     catfact.url = 'http://catfacts-api.appspot.com/api/facts' | ||||
| end | ||||
|  | ||||
| function catfact:action(msg, config) | ||||
| 	local jstr, code = HTTP.request(catfact.url) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	local output = '*Cat Fact*\n_' .. data.facts[1] .. '_' | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|     local jstr, code = HTTP.request(catfact.url) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|     local data = JSON.decode(jstr) | ||||
|     local output = '*Cat Fact*\n_' .. data.facts[1] .. '_' | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
| end | ||||
|  | ||||
| return catfact | ||||
|   | ||||
| @@ -4,12 +4,12 @@ local HTTP = require('socket.http') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function cats:init(config) | ||||
| 	if not config.thecatapi_key then | ||||
| 		print('Missing config value: thecatapi_key.') | ||||
| 		print('cats.lua will be enabled, but there are more features with a key.') | ||||
| 	end | ||||
|     if not config.thecatapi_key then | ||||
|         print('Missing config value: thecatapi_key.') | ||||
|         print('cats.lua will be enabled, but there are more features with a key.') | ||||
|     end | ||||
|  | ||||
| 	cats.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cat').table | ||||
|     cats.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cat').table | ||||
| end | ||||
|  | ||||
| cats.command = 'cat' | ||||
| @@ -17,21 +17,21 @@ cats.doc = 'Returns a cat!' | ||||
|  | ||||
| function cats:action(msg, config) | ||||
|  | ||||
| 	local url = 'http://thecatapi.com/api/images/get?format=html&type=jpg' | ||||
| 	if config.thecatapi_key then | ||||
| 		url = url .. '&api_key=' .. config.thecatapi_key | ||||
| 	end | ||||
|     local url = 'http://thecatapi.com/api/images/get?format=html&type=jpg' | ||||
|     if config.thecatapi_key then | ||||
|         url = url .. '&api_key=' .. config.thecatapi_key | ||||
|     end | ||||
|  | ||||
| 	local str, res = HTTP.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local str, res = HTTP.request(url) | ||||
|     if res ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	str = str:match('<img src="(.-)">') | ||||
| 	local output = '[Cat!]('..str..')' | ||||
|     str = str:match('<img src="(.-)">') | ||||
|     local output = '[Cat!]('..str..')' | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, output, false, nil, true) | ||||
|     utilities.send_message(self, msg.chat.id, output, false, nil, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -4,9 +4,9 @@ local bindings = require('otouto.bindings') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function channel:init(config) | ||||
| 	channel.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ch', true).table | ||||
| 	channel.command = 'ch <channel> \\n <message>' | ||||
| 	channel.doc = config.cmd_pat .. [[ch <channel> | ||||
|     channel.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ch', true).table | ||||
|     channel.command = 'ch <channel> \\n <message>' | ||||
|     channel.doc = config.cmd_pat .. [[ch <channel> | ||||
| <message> | ||||
|  | ||||
| Sends a message to a channel. Channel may be specified via ID or username. Messages are markdown-enabled. Users may only send messages to channels for which they are the owner or an administrator. | ||||
| @@ -20,41 +20,41 @@ The following markdown syntax is supported: | ||||
| end | ||||
|  | ||||
| function channel:action(msg, config) | ||||
| 	-- An exercise in using zero early returns. :) | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	local output | ||||
| 	if input then | ||||
| 		local chat_id = utilities.get_word(input, 1) | ||||
| 		local admin_list, t = bindings.getChatAdministrators(self, { chat_id = chat_id } ) | ||||
| 		if admin_list then | ||||
| 			local is_admin = false | ||||
| 			for _, admin in ipairs(admin_list.result) do | ||||
| 				if admin.user.id == msg.from.id then | ||||
| 					is_admin = true | ||||
| 				end | ||||
| 			end | ||||
| 			if is_admin then | ||||
| 				local text = input:match('\n(.+)') | ||||
| 				if text then | ||||
| 					local success, result = utilities.send_message(self, chat_id, text, true, nil, true) | ||||
| 					if success then | ||||
| 						output = 'Your message has been sent!' | ||||
| 					else | ||||
| 						output = 'Sorry, I was unable to send your message.\n`' .. result.description .. '`' | ||||
| 					end | ||||
| 				else | ||||
| 					output = 'Please enter a message to be sent. Markdown is supported.' | ||||
| 				end | ||||
| 			else | ||||
| 				output = 'Sorry, you do not appear to be an administrator for that channel.' | ||||
| 			end | ||||
| 		else | ||||
| 			output = 'Sorry, I was unable to retrieve a list of administrators for that channel.\n`' .. t.description .. '`' | ||||
| 		end | ||||
| 	else | ||||
| 		output = channel.doc | ||||
| 	end | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|     -- An exercise in using zero early returns. :) | ||||
|     local input = utilities.input(msg.text) | ||||
|     local output | ||||
|     if input then | ||||
|         local chat_id = utilities.get_word(input, 1) | ||||
|         local admin_list, t = bindings.getChatAdministrators(self, { chat_id = chat_id } ) | ||||
|         if admin_list then | ||||
|             local is_admin = false | ||||
|             for _, admin in ipairs(admin_list.result) do | ||||
|                 if admin.user.id == msg.from.id then | ||||
|                     is_admin = true | ||||
|                 end | ||||
|             end | ||||
|             if is_admin then | ||||
|                 local text = input:match('\n(.+)') | ||||
|                 if text then | ||||
|                     local success, result = utilities.send_message(self, chat_id, text, true, nil, true) | ||||
|                     if success then | ||||
|                         output = 'Your message has been sent!' | ||||
|                     else | ||||
|                         output = 'Sorry, I was unable to send your message.\n`' .. result.description .. '`' | ||||
|                     end | ||||
|                 else | ||||
|                     output = 'Please enter a message to be sent. Markdown is supported.' | ||||
|                 end | ||||
|             else | ||||
|                 output = 'Sorry, you do not appear to be an administrator for that channel.' | ||||
|             end | ||||
|         else | ||||
|             output = 'Sorry, I was unable to retrieve a list of administrators for that channel.\n`' .. t.description .. '`' | ||||
|         end | ||||
|     else | ||||
|         output = channel.doc | ||||
|     end | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
| end | ||||
|  | ||||
| return channel | ||||
|   | ||||
| @@ -7,22 +7,22 @@ local utilities = require('otouto.utilities') | ||||
| local chuck = {} | ||||
|  | ||||
| function chuck:init(config) | ||||
| 	chuck.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('chuck', true):t('cn', true):t('chucknorris', true).table | ||||
| 	chuck.command = 'chuck' | ||||
| 	chuck.doc = 'Returns a fact about Chuck Norris.' | ||||
| 	chuck.url = 'http://api.icndb.com/jokes/random' | ||||
|     chuck.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('chuck', true):t('cn', true):t('chucknorris', true).table | ||||
|     chuck.command = 'chuck' | ||||
|     chuck.doc = 'Returns a fact about Chuck Norris.' | ||||
|     chuck.url = 'http://api.icndb.com/jokes/random' | ||||
| end | ||||
|  | ||||
| function chuck:action(msg, config) | ||||
| 	local jstr, code = HTTP.request(chuck.url) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	local output = '*Chuck Norris Fact*\n_' .. data.value.joke .. '_' | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|     local jstr, code = HTTP.request(chuck.url) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|     local data = JSON.decode(jstr) | ||||
|     local output = '*Chuck Norris Fact*\n_' .. data.value.joke .. '_' | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
| end | ||||
|  | ||||
| return chuck | ||||
|   | ||||
| @@ -7,30 +7,30 @@ local bindings = require('otouto.bindings') | ||||
| local cleverbot = {} | ||||
|  | ||||
| function cleverbot:init(config) | ||||
| 	cleverbot.name = '^' .. self.info.first_name:lower() .. ', ' | ||||
| 	cleverbot.username = '^@' .. self.info.username:lower() .. ', ' | ||||
| 	cleverbot.triggers = { | ||||
| 		'^' .. self.info.first_name:lower() .. ', ', | ||||
| 		'^@' .. self.info.username:lower() .. ', ' | ||||
| 	} | ||||
| 	cleverbot.url = config.chatter.cleverbot_api | ||||
| 	cleverbot.error = false | ||||
|     cleverbot.name = '^' .. self.info.first_name:lower() .. ', ' | ||||
|     cleverbot.username = '^@' .. self.info.username:lower() .. ', ' | ||||
|     cleverbot.triggers = { | ||||
|         '^' .. self.info.first_name:lower() .. ', ', | ||||
|         '^@' .. self.info.username:lower() .. ', ' | ||||
|     } | ||||
|     cleverbot.url = config.chatter.cleverbot_api | ||||
|     cleverbot.error = false | ||||
| end | ||||
|  | ||||
| function cleverbot:action(msg, config) | ||||
| 	bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' }) | ||||
| 	local input = msg.text_lower:gsub(cleverbot.name, ''):gsub(cleverbot.name, '') | ||||
| 	local jstr, code = HTTPS.request(cleverbot.url .. URL.escape(input)) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_message(self, msg.chat.id, config.chatter.connection) | ||||
| 		return | ||||
| 	end | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	if not data.clever then | ||||
| 		utilities.send_message(self, msg.chat.id, config.chatter.response) | ||||
| 		return | ||||
| 	end | ||||
| 	utilities.send_message(self, msg.chat.id, data.clever) | ||||
|     bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' }) | ||||
|     local input = msg.text_lower:gsub(cleverbot.name, ''):gsub(cleverbot.name, '') | ||||
|     local jstr, code = HTTPS.request(cleverbot.url .. URL.escape(input)) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_message(self, msg.chat.id, config.chatter.connection) | ||||
|         return | ||||
|     end | ||||
|     local data = JSON.decode(jstr) | ||||
|     if not data.clever then | ||||
|         utilities.send_message(self, msg.chat.id, config.chatter.response) | ||||
|         return | ||||
|     end | ||||
|     utilities.send_message(self, msg.chat.id, data.clever) | ||||
| end | ||||
|  | ||||
| return cleverbot | ||||
|   | ||||
| @@ -8,19 +8,19 @@ commit.command = 'commit' | ||||
| commit.doc = 'Returns a commit message from whatthecommit.com.' | ||||
|  | ||||
| function commit:init(config) | ||||
| 	commit.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('commit').table | ||||
|     commit.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('commit').table | ||||
| end | ||||
|  | ||||
| function commit:action(msg) | ||||
| 	bindings.request( | ||||
| 		self, | ||||
| 		'sendMessage', | ||||
| 		{ | ||||
| 			chat_id = msg.chat.id, | ||||
| 			text = '```\n' .. (http.request('http://whatthecommit.com/index.txt')) .. '\n```', | ||||
| 			parse_mode = 'Markdown' | ||||
| 		} | ||||
| 	) | ||||
|     bindings.request( | ||||
|         self, | ||||
|         'sendMessage', | ||||
|         { | ||||
|             chat_id = msg.chat.id, | ||||
|             text = '```\n' .. (http.request('http://whatthecommit.com/index.txt')) .. '\n```', | ||||
|             parse_mode = 'Markdown' | ||||
|         } | ||||
|     ) | ||||
| end | ||||
|  | ||||
| return commit | ||||
|   | ||||
| @@ -6,52 +6,52 @@ local utilities = require('otouto.utilities') | ||||
| local cmd_pat -- Prevents the command from being uncallable. | ||||
|  | ||||
| function control:init(config) | ||||
| 	cmd_pat = config.cmd_pat | ||||
| 	control.triggers = utilities.triggers(self.info.username, cmd_pat, | ||||
| 		{'^'..cmd_pat..'script'}):t('reload', true):t('halt').table | ||||
|     cmd_pat = config.cmd_pat | ||||
|     control.triggers = utilities.triggers(self.info.username, cmd_pat, | ||||
|         {'^'..cmd_pat..'script'}):t('reload', true):t('halt').table | ||||
| end | ||||
|  | ||||
| function control:action(msg, config) | ||||
|  | ||||
| 	if msg.from.id ~= config.admin then | ||||
| 		return | ||||
| 	end | ||||
|     if msg.from.id ~= config.admin then | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	if msg.date < os.time() - 2 then return end | ||||
|     if msg.date < os.time() - 2 then return end | ||||
|  | ||||
| 	if msg.text_lower:match('^'..cmd_pat..'reload') then | ||||
| 		for pac, _ in pairs(package.loaded) do | ||||
| 			if pac:match('^otouto%.plugins%.') then | ||||
| 				package.loaded[pac] = nil | ||||
| 			end | ||||
| 		end | ||||
| 		package.loaded['otouto.bindings'] = nil | ||||
| 		package.loaded['otouto.utilities'] = nil | ||||
| 		package.loaded['otouto.drua-tg'] = nil | ||||
| 		package.loaded['config'] = nil | ||||
| 		if not msg.text_lower:match('%-config') then | ||||
| 			for k, v in pairs(require('config')) do | ||||
| 				config[k] = v | ||||
| 			end | ||||
| 		end | ||||
| 		bot.init(self, config) | ||||
| 		utilities.send_reply(self, msg, 'Bot reloaded!') | ||||
| 	elseif msg.text_lower:match('^'..cmd_pat..'halt') then | ||||
| 		self.is_started = false | ||||
| 		utilities.send_reply(self, msg, 'Stopping bot!') | ||||
| 	elseif msg.text_lower:match('^'..cmd_pat..'script') then | ||||
| 		local input = msg.text_lower:match('^'..cmd_pat..'script\n(.+)') | ||||
| 		if not input then | ||||
| 			utilities.send_reply(self, msg, 'usage: ```\n'..cmd_pat..'script\n'..cmd_pat..'command <arg>\n...\n```', true) | ||||
| 			return | ||||
| 		end | ||||
| 		input = input .. '\n' | ||||
| 		for command in input:gmatch('(.-)\n') do | ||||
| 			command = utilities.trim(command) | ||||
| 			msg.text = command | ||||
| 			bot.on_msg_receive(self, msg, config) | ||||
| 		end | ||||
| 	end | ||||
|     if msg.text_lower:match('^'..cmd_pat..'reload') then | ||||
|         for pac, _ in pairs(package.loaded) do | ||||
|             if pac:match('^otouto%.plugins%.') then | ||||
|                 package.loaded[pac] = nil | ||||
|             end | ||||
|         end | ||||
|         package.loaded['otouto.bindings'] = nil | ||||
|         package.loaded['otouto.utilities'] = nil | ||||
|         package.loaded['otouto.drua-tg'] = nil | ||||
|         package.loaded['config'] = nil | ||||
|         if not msg.text_lower:match('%-config') then | ||||
|             for k, v in pairs(require('config')) do | ||||
|                 config[k] = v | ||||
|             end | ||||
|         end | ||||
|         bot.init(self, config) | ||||
|         utilities.send_reply(self, msg, 'Bot reloaded!') | ||||
|     elseif msg.text_lower:match('^'..cmd_pat..'halt') then | ||||
|         self.is_started = false | ||||
|         utilities.send_reply(self, msg, 'Stopping bot!') | ||||
|     elseif msg.text_lower:match('^'..cmd_pat..'script') then | ||||
|         local input = msg.text_lower:match('^'..cmd_pat..'script\n(.+)') | ||||
|         if not input then | ||||
|             utilities.send_reply(self, msg, 'usage: ```\n'..cmd_pat..'script\n'..cmd_pat..'command <arg>\n...\n```', true) | ||||
|             return | ||||
|         end | ||||
|         input = input .. '\n' | ||||
|         for command in input:gmatch('(.-)\n') do | ||||
|             command = utilities.trim(command) | ||||
|             msg.text = command | ||||
|             bot.on_msg_receive(self, msg, config) | ||||
|         end | ||||
|     end | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -6,8 +6,8 @@ local utilities = require('otouto.utilities') | ||||
| currency.command = 'cash [amount] <from> to <to>' | ||||
|  | ||||
| function currency:init(config) | ||||
| 	currency.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cash', true).table | ||||
| 	currency.doc = config.cmd_pat .. [[cash [amount] <from> to <to> | ||||
|     currency.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('cash', true).table | ||||
|     currency.doc = config.cmd_pat .. [[cash [amount] <from> to <to> | ||||
| Example: ]] .. config.cmd_pat .. [[cash 5 USD to EUR | ||||
| Returns exchange rates for various currencies. | ||||
| Source: Google Finance.]] | ||||
| @@ -15,44 +15,44 @@ end | ||||
|  | ||||
| function currency:action(msg, config) | ||||
|  | ||||
| 	local input = msg.text:upper() | ||||
| 	if not input:match('%a%a%a TO %a%a%a') then | ||||
| 		utilities.send_message(self, msg.chat.id, currency.doc, true, msg.message_id, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = msg.text:upper() | ||||
|     if not input:match('%a%a%a TO %a%a%a') then | ||||
|         utilities.send_message(self, msg.chat.id, currency.doc, true, msg.message_id, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local from = input:match('(%a%a%a) TO') | ||||
| 	local to = input:match('TO (%a%a%a)') | ||||
| 	local amount = utilities.get_word(input, 2) | ||||
| 	amount = tonumber(amount) or 1 | ||||
| 	local result = 1 | ||||
|     local from = input:match('(%a%a%a) TO') | ||||
|     local to = input:match('TO (%a%a%a)') | ||||
|     local amount = utilities.get_word(input, 2) | ||||
|     amount = tonumber(amount) or 1 | ||||
|     local result = 1 | ||||
|  | ||||
| 	local url = 'https://www.google.com/finance/converter' | ||||
|     local url = 'https://www.google.com/finance/converter' | ||||
|  | ||||
| 	if from ~= to then | ||||
|     if from ~= to then | ||||
|  | ||||
| 		url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount | ||||
| 		local str, res = HTTPS.request(url) | ||||
| 		if res ~= 200 then | ||||
| 			utilities.send_reply(self, msg, config.errors.connection) | ||||
| 			return | ||||
| 		end | ||||
|         url = url .. '?from=' .. from .. '&to=' .. to .. '&a=' .. amount | ||||
|         local str, res = HTTPS.request(url) | ||||
|         if res ~= 200 then | ||||
|             utilities.send_reply(self, msg, config.errors.connection) | ||||
|             return | ||||
|         end | ||||
|  | ||||
| 		str = str:match('<span class=bld>(.*) %u+</span>') | ||||
| 		if not str then | ||||
| 			utilities.send_reply(self, msg, config.errors.results) | ||||
| 			return | ||||
| 		end | ||||
|         str = str:match('<span class=bld>(.*) %u+</span>') | ||||
|         if not str then | ||||
|             utilities.send_reply(self, msg, config.errors.results) | ||||
|             return | ||||
|         end | ||||
|  | ||||
| 		result = string.format('%.2f', str) | ||||
|         result = string.format('%.2f', str) | ||||
|  | ||||
| 	end | ||||
|     end | ||||
|  | ||||
| 	local output = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to .. '\n\n' | ||||
| 	output = output .. os.date('!%F %T UTC') .. '\nSource: Google Finance`' | ||||
| 	output = '```\n' .. output .. '\n```' | ||||
|     local output = amount .. ' ' .. from .. ' = ' .. result .. ' ' .. to .. '\n\n' | ||||
|     output = output .. os.date('!%F %T UTC') .. '\nSource: Google Finance`' | ||||
|     output = '```\n' .. output .. '\n```' | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -5,49 +5,49 @@ 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> | ||||
|     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 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 | ||||
|     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) | ||||
|     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 | ||||
|     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 .. '`' | ||||
|     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) | ||||
|     utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -8,8 +8,8 @@ local utilities = require('otouto.utilities') | ||||
| dilbert.command = 'dilbert [date]' | ||||
|  | ||||
| function dilbert:init(config) | ||||
| 	dilbert.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('dilbert', true).table | ||||
| 	dilbert.doc = config.cmd_pat .. [[dilbert [YYYY-MM-DD] | ||||
|     dilbert.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('dilbert', true).table | ||||
|     dilbert.doc = config.cmd_pat .. [[dilbert [YYYY-MM-DD] | ||||
| Returns the latest Dilbert strip or that of the provided date. | ||||
| Dates before the first strip will return the first strip. Dates after the last trip will return the last strip. | ||||
| Source: dilbert.com]] | ||||
| @@ -17,32 +17,32 @@ end | ||||
|  | ||||
| function dilbert:action(msg, config) | ||||
|  | ||||
| 	bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' } ) | ||||
|     bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' } ) | ||||
|  | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	if not input then input = os.date('%F') end | ||||
| 	if not input:match('^%d%d%d%d%-%d%d%-%d%d$') then input = os.date('%F') end | ||||
|     local input = utilities.input(msg.text) | ||||
|     if not input then input = os.date('%F') end | ||||
|     if not input:match('^%d%d%d%d%-%d%d%-%d%d$') then input = os.date('%F') end | ||||
|  | ||||
| 	local url = 'http://dilbert.com/strip/' .. URL.escape(input) | ||||
| 	local str, res = HTTP.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local url = 'http://dilbert.com/strip/' .. URL.escape(input) | ||||
|     local str, res = HTTP.request(url) | ||||
|     if res ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local strip_filename = '/tmp/' .. input .. '.gif' | ||||
| 	local strip_file = io.open(strip_filename) | ||||
| 	if strip_file then | ||||
| 		strip_file:close() | ||||
| 		strip_file = strip_filename | ||||
| 	else | ||||
| 		local strip_url = str:match('<meta property="og:image" content="(.-)"/>') | ||||
| 		strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif') | ||||
| 	end | ||||
|     local strip_filename = '/tmp/' .. input .. '.gif' | ||||
|     local strip_file = io.open(strip_filename) | ||||
|     if strip_file then | ||||
|         strip_file:close() | ||||
|         strip_file = strip_filename | ||||
|     else | ||||
|         local strip_url = str:match('<meta property="og:image" content="(.-)"/>') | ||||
|         strip_file = utilities.download_file(strip_url, '/tmp/' .. input .. '.gif') | ||||
|     end | ||||
|  | ||||
| 	local strip_title = str:match('<meta property="article:publish_date" content="(.-)"/>') | ||||
|     local strip_title = str:match('<meta property="article:publish_date" content="(.-)"/>') | ||||
|  | ||||
| 	bindings.sendPhoto(self, { chat_id = msg.chat.id, caption = strip_title }, { photo = strip_file } ) | ||||
|     bindings.sendPhoto(self, { chat_id = msg.chat.id, caption = strip_title }, { photo = strip_file } ) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -5,25 +5,25 @@ local utilities = require('otouto.utilities') | ||||
| echo.command = 'echo <text>' | ||||
|  | ||||
| function echo:init(config) | ||||
| 	echo.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('echo', true).table | ||||
| 	echo.doc = config.cmd_pat .. 'echo <text> \nRepeats a string of text.' | ||||
|     echo.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('echo', true).table | ||||
|     echo.doc = config.cmd_pat .. 'echo <text> \nRepeats a string of text.' | ||||
| end | ||||
|  | ||||
| function echo:action(msg) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|  | ||||
| 	if not input then | ||||
| 		utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true) | ||||
| 	else | ||||
| 		local output | ||||
| 		if msg.chat.type == 'supergroup' then | ||||
| 			output = utilities.style.enquote('Echo', input) | ||||
| 		else | ||||
| 			output = utilities.md_escape(utilities.char.zwnj..input) | ||||
| 		end | ||||
| 		utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
| 	end | ||||
|     if not input then | ||||
|         utilities.send_message(self, msg.chat.id, echo.doc, true, msg.message_id, true) | ||||
|     else | ||||
|         local output | ||||
|         if msg.chat.type == 'supergroup' then | ||||
|             output = utilities.style.enquote('Echo', input) | ||||
|         else | ||||
|             output = utilities.md_escape(utilities.char.zwnj..input) | ||||
|         end | ||||
|         utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|     end | ||||
|  | ||||
|  | ||||
| end | ||||
|   | ||||
| @@ -6,52 +6,52 @@ eightball.command = '8ball' | ||||
| eightball.doc = 'Returns an answer from a magic 8-ball!' | ||||
|  | ||||
| function eightball:init(config) | ||||
| 	eightball.triggers = utilities.triggers(self.info.username, config.cmd_pat, | ||||
| 		{'[Yy]/[Nn]%p*$'}):t('8ball', true).table | ||||
|     eightball.triggers = utilities.triggers(self.info.username, config.cmd_pat, | ||||
|         {'[Yy]/[Nn]%p*$'}):t('8ball', true).table | ||||
| end | ||||
|  | ||||
| local ball_answers = { | ||||
| 	"It is certain.", | ||||
| 	"It is decidedly so.", | ||||
| 	"Without a doubt.", | ||||
| 	"Yes, definitely.", | ||||
| 	"You may rely on it.", | ||||
| 	"As I see it, yes.", | ||||
| 	"Most likely.", | ||||
| 	"Outlook: good.", | ||||
| 	"Yes.", | ||||
| 	"Signs point to yes.", | ||||
| 	"Reply hazy try again.", | ||||
| 	"Ask again later.", | ||||
| 	"Better not tell you now.", | ||||
| 	"Cannot predict now.", | ||||
| 	"Concentrate and ask again.", | ||||
| 	"Don't count on it.", | ||||
| 	"My reply is no.", | ||||
| 	"My sources say no.", | ||||
| 	"Outlook: not so good.", | ||||
| 	"Very doubtful.", | ||||
| 	"There is a time and place for everything, but not now." | ||||
|     "It is certain.", | ||||
|     "It is decidedly so.", | ||||
|     "Without a doubt.", | ||||
|     "Yes, definitely.", | ||||
|     "You may rely on it.", | ||||
|     "As I see it, yes.", | ||||
|     "Most likely.", | ||||
|     "Outlook: good.", | ||||
|     "Yes.", | ||||
|     "Signs point to yes.", | ||||
|     "Reply hazy try again.", | ||||
|     "Ask again later.", | ||||
|     "Better not tell you now.", | ||||
|     "Cannot predict now.", | ||||
|     "Concentrate and ask again.", | ||||
|     "Don't count on it.", | ||||
|     "My reply is no.", | ||||
|     "My sources say no.", | ||||
|     "Outlook: not so good.", | ||||
|     "Very doubtful.", | ||||
|     "There is a time and place for everything, but not now." | ||||
| } | ||||
|  | ||||
| local yesno_answers = { | ||||
| 	'Absolutely.', | ||||
| 	'In your dreams.', | ||||
| 	'Yes.', | ||||
| 	'No.' | ||||
|     'Absolutely.', | ||||
|     'In your dreams.', | ||||
|     'Yes.', | ||||
|     'No.' | ||||
| } | ||||
|  | ||||
| function eightball:action(msg) | ||||
|  | ||||
| 	local output | ||||
|     local output | ||||
|  | ||||
| 	if msg.text_lower:match('y/n%p?$') then | ||||
| 		output = yesno_answers[math.random(#yesno_answers)] | ||||
| 	else | ||||
| 		output = ball_answers[math.random(#ball_answers)] | ||||
| 	end | ||||
|     if msg.text_lower:match('y/n%p?$') then | ||||
|         output = yesno_answers[math.random(#yesno_answers)] | ||||
|     else | ||||
|         output = ball_answers[math.random(#ball_answers)] | ||||
|     end | ||||
|  | ||||
| 	utilities.send_reply(self, msg, output) | ||||
|     utilities.send_reply(self, msg, output) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -5,13 +5,13 @@ local fortune = {} | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function fortune:init(config) | ||||
| 	local s = io.popen('fortune'):read('*all') | ||||
| 	assert( | ||||
| 		not s:match('not found$'), | ||||
| 		'fortune.lua requires the fortune program to be installed.' | ||||
| 	) | ||||
|     local s = io.popen('fortune'):read('*all') | ||||
|     assert( | ||||
|         not s:match('not found$'), | ||||
|         'fortune.lua requires the fortune program to be installed.' | ||||
|     ) | ||||
|  | ||||
| 	fortune.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('fortune').table | ||||
|     fortune.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('fortune').table | ||||
| end | ||||
|  | ||||
| fortune.command = 'fortune' | ||||
| @@ -19,11 +19,11 @@ fortune.doc = 'Returns a UNIX fortune.' | ||||
|  | ||||
| function fortune:action(msg) | ||||
|  | ||||
| 	local fortunef = io.popen('fortune') | ||||
| 	local output = fortunef:read('*all') | ||||
| 	output = '```\n' .. output .. '\n```' | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
| 	fortunef:close() | ||||
|     local fortunef = io.popen('fortune') | ||||
|     local output = fortunef:read('*all') | ||||
|     output = '```\n' .. output .. '\n```' | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|     fortunef:close() | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -9,58 +9,58 @@ local JSON = require('dkjson') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function gImages:init(config) | ||||
| 	assert(config.google_api_key and config.google_cse_key, | ||||
| 		'gImages.lua requires a Google API key from http://console.developers.google.com and a Google Custom Search Engine key from http://cse.google.com/cse.' | ||||
| 	) | ||||
|     assert(config.google_api_key and config.google_cse_key, | ||||
|         'gImages.lua requires a Google API key from http://console.developers.google.com and a Google Custom Search Engine key from http://cse.google.com/cse.' | ||||
|     ) | ||||
|  | ||||
| 	gImages.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('image', true):t('i', true):t('insfw', true).table | ||||
| 	gImages.doc = config.cmd_pat .. [[image <query> | ||||
|     gImages.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('image', true):t('i', true):t('insfw', true).table | ||||
|     gImages.doc = config.cmd_pat .. [[image <query> | ||||
| Returns a randomized top result from Google Images. Safe search is enabled by default; use "]] .. config.cmd_pat .. [[insfw" to disable it. NSFW results will not display an image preview. | ||||
| Alias: ]] .. config.cmd_pat .. 'i' | ||||
| 	gImages.search_url = 'https://www.googleapis.com/customsearch/v1?&searchType=image&imgSize=xlarge&alt=json&num=8&start=1&key=' .. config.google_api_key .. '&cx=' .. config.google_cse_key | ||||
|     gImages.search_url = 'https://www.googleapis.com/customsearch/v1?&searchType=image&imgSize=xlarge&alt=json&num=8&start=1&key=' .. config.google_api_key .. '&cx=' .. config.google_cse_key | ||||
| end | ||||
|  | ||||
| gImages.command = 'image <query>' | ||||
|  | ||||
| function gImages:action(msg, config) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, gImages.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, gImages.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = gImages.search_url | ||||
|     local url = gImages.search_url | ||||
|  | ||||
| 	if not string.match(msg.text, '^'..config.cmd_pat..'i[mage]*nsfw') then | ||||
| 		url = url .. '&safe=high' | ||||
| 	end | ||||
|     if not string.match(msg.text, '^'..config.cmd_pat..'i[mage]*nsfw') then | ||||
|         url = url .. '&safe=high' | ||||
|     end | ||||
|  | ||||
| 	url = url .. '&q=' .. URL.escape(input) | ||||
|     url = url .. '&q=' .. URL.escape(input) | ||||
|  | ||||
| 	local jstr, res = HTTPS.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local jstr, res = HTTPS.request(url) | ||||
|     if res ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local jdat = JSON.decode(jstr) | ||||
| 	if jdat.searchInformation.totalResults == '0' then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     local jdat = JSON.decode(jstr) | ||||
|     if jdat.searchInformation.totalResults == '0' then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local i = math.random(jdat.queries.request[1].count) | ||||
| 	local img_url = jdat.items[i].link | ||||
| 	local img_title = jdat.items[i].title | ||||
| 	local output = '[' .. img_title .. '](' .. img_url .. ')' | ||||
|     local i = math.random(jdat.queries.request[1].count) | ||||
|     local img_url = jdat.items[i].link | ||||
|     local img_title = jdat.items[i].title | ||||
|     local output = '[' .. img_title .. '](' .. img_url .. ')' | ||||
|  | ||||
|  | ||||
| 	if msg.text:match('nsfw') then | ||||
| 		utilities.send_reply(self, '*NSFW*\n'..msg, output) | ||||
| 	else | ||||
| 		utilities.send_message(self, msg.chat.id, output, false, nil, true) | ||||
| 	end | ||||
|     if msg.text:match('nsfw') then | ||||
|         utilities.send_reply(self, '*NSFW*\n'..msg, output) | ||||
|     else | ||||
|         utilities.send_message(self, msg.chat.id, output, false, nil, true) | ||||
|     end | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -6,34 +6,34 @@ local utilities = require('otouto.utilities') | ||||
| gMaps.command = 'location <query>' | ||||
|  | ||||
| function gMaps:init(config) | ||||
| 	gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('location', true):t('loc', true).table | ||||
| 	gMaps.doc = [[ | ||||
|     gMaps.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('location', true):t('loc', true).table | ||||
|     gMaps.doc = [[ | ||||
| /location <query> | ||||
| Returns a location from Google Maps. | ||||
| Alias: /loc | ||||
| 	]] | ||||
| 	gMaps.doc = gMaps.doc:gsub('/', config.cmd_pat) | ||||
|     ]] | ||||
|     gMaps.doc = gMaps.doc:gsub('/', config.cmd_pat) | ||||
| end | ||||
|  | ||||
| function gMaps:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, gMaps.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, gMaps.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local coords = utilities.get_coords(input, config) | ||||
| 	if type(coords) == 'string' then | ||||
| 		utilities.send_reply(self, msg, coords) | ||||
| 	end | ||||
|     local coords = utilities.get_coords(input, config) | ||||
|     if type(coords) == 'string' then | ||||
|         utilities.send_reply(self, msg, coords) | ||||
|     end | ||||
|  | ||||
| 	bindings.sendLocation(self, { | ||||
| 		chat_id = msg.chat.id, | ||||
| 		latitude = coords.lat, | ||||
| 		longitude = coords.lon, | ||||
| 		reply_to_message_id = msg.message_id | ||||
| 	} ) | ||||
|     bindings.sendLocation(self, { | ||||
|         chat_id = msg.chat.id, | ||||
|         latitude = coords.lat, | ||||
|         longitude = coords.lon, | ||||
|         reply_to_message_id = msg.message_id | ||||
|     } ) | ||||
| end | ||||
|  | ||||
| return gMaps | ||||
|   | ||||
| @@ -3,30 +3,30 @@ local utilities = require('otouto.utilities') | ||||
| local greetings = {} | ||||
|  | ||||
| function greetings:init(config) | ||||
| 	greetings.triggers = {} | ||||
| 	for _, triggers in pairs(config.greetings) do | ||||
| 		for i = 1, #triggers do | ||||
| 			triggers[i] = '^' .. triggers[i] .. ',? ' .. self.info.first_name:lower() .. '%p*$' | ||||
| 			table.insert(greetings.triggers, triggers[i]) | ||||
| 		end | ||||
| 	end | ||||
|     greetings.triggers = {} | ||||
|     for _, triggers in pairs(config.greetings) do | ||||
|         for i = 1, #triggers do | ||||
|             triggers[i] = '^' .. triggers[i] .. ',? ' .. self.info.first_name:lower() .. '%p*$' | ||||
|             table.insert(greetings.triggers, triggers[i]) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| function greetings:action(msg, config) | ||||
| 	local nick | ||||
| 	if self.database.userdata[tostring(msg.from.id)] then | ||||
| 		nick = self.database.userdata[tostring(msg.from.id)].nickname | ||||
| 	end | ||||
| 	nick = nick or utilities.build_name(msg.from.first_name, msg.from.last_name) | ||||
|     local nick | ||||
|     if self.database.userdata[tostring(msg.from.id)] then | ||||
|         nick = self.database.userdata[tostring(msg.from.id)].nickname | ||||
|     end | ||||
|     nick = nick or utilities.build_name(msg.from.first_name, msg.from.last_name) | ||||
|  | ||||
| 	for response, triggers in pairs(config.greetings) do | ||||
| 		for _, trigger in pairs(triggers) do | ||||
| 			if string.match(msg.text_lower, trigger) then | ||||
| 				utilities.send_message(self, msg.chat.id, response:gsub('#NAME', nick)) | ||||
| 				return | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|     for response, triggers in pairs(config.greetings) do | ||||
|         for _, trigger in pairs(triggers) do | ||||
|             if string.match(msg.text_lower, trigger) then | ||||
|                 utilities.send_message(self, msg.chat.id, response:gsub('#NAME', nick)) | ||||
|                 return | ||||
|             end | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| return greetings | ||||
|   | ||||
| @@ -8,68 +8,68 @@ local hackernews = {} | ||||
| hackernews.command = 'hackernews' | ||||
|  | ||||
| local function get_hackernews_results() | ||||
| 	local results = {} | ||||
| 	local jstr, code = HTTPS.request(hackernews.topstories_url) | ||||
| 	if code ~= 200 then return end | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	for i = 1, 8 do | ||||
| 		local ijstr, icode = HTTPS.request(hackernews.res_url:format(data[i])) | ||||
| 		if icode ~= 200 then return end | ||||
| 		local idata = JSON.decode(ijstr) | ||||
| 		local result | ||||
| 		if idata.url then | ||||
| 			result = string.format( | ||||
| 				'\n• <code>[</code><a href="%s">%s</a><code>]</code> <a href="%s">%s</a>', | ||||
| 				utilities.html_escape(hackernews.art_url:format(idata.id)), | ||||
| 				idata.id, | ||||
| 				utilities.html_escape(idata.url), | ||||
| 				utilities.html_escape(idata.title) | ||||
| 			) | ||||
| 		else | ||||
| 			result = string.format( | ||||
| 				'\n• <code>[</code><a href="%s">%s</a><code>]</code> %s', | ||||
| 				utilities.html_escape(hackernews.art_url:format(idata.id)), | ||||
| 				idata.id, | ||||
| 				utilities.html_escape(idata.title) | ||||
| 			) | ||||
| 		end | ||||
| 		table.insert(results, result) | ||||
| 	end | ||||
| 	return results | ||||
|     local results = {} | ||||
|     local jstr, code = HTTPS.request(hackernews.topstories_url) | ||||
|     if code ~= 200 then return end | ||||
|     local data = JSON.decode(jstr) | ||||
|     for i = 1, 8 do | ||||
|         local ijstr, icode = HTTPS.request(hackernews.res_url:format(data[i])) | ||||
|         if icode ~= 200 then return end | ||||
|         local idata = JSON.decode(ijstr) | ||||
|         local result | ||||
|         if idata.url then | ||||
|             result = string.format( | ||||
|                 '\n• <code>[</code><a href="%s">%s</a><code>]</code> <a href="%s">%s</a>', | ||||
|                 utilities.html_escape(hackernews.art_url:format(idata.id)), | ||||
|                 idata.id, | ||||
|                 utilities.html_escape(idata.url), | ||||
|                 utilities.html_escape(idata.title) | ||||
|             ) | ||||
|         else | ||||
|             result = string.format( | ||||
|                 '\n• <code>[</code><a href="%s">%s</a><code>]</code> %s', | ||||
|                 utilities.html_escape(hackernews.art_url:format(idata.id)), | ||||
|                 idata.id, | ||||
|                 utilities.html_escape(idata.title) | ||||
|             ) | ||||
|         end | ||||
|         table.insert(results, result) | ||||
|     end | ||||
|     return results | ||||
| end | ||||
|  | ||||
| function hackernews:init(config) | ||||
| 	hackernews.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hackernews', true):t('hn', true).table | ||||
| 	hackernews.doc = [[Returns four (if group) or eight (if private message) top stories from Hacker News. | ||||
|     hackernews.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hackernews', true):t('hn', true).table | ||||
|     hackernews.doc = [[Returns four (if group) or eight (if private message) top stories from Hacker News. | ||||
| Alias: ]] .. config.cmd_pat .. 'hn' | ||||
| 	hackernews.topstories_url = 'https://hacker-news.firebaseio.com/v0/topstories.json' | ||||
| 	hackernews.res_url = 'https://hacker-news.firebaseio.com/v0/item/%s.json' | ||||
| 	hackernews.art_url = 'https://news.ycombinator.com/item?id=%s' | ||||
| 	hackernews.last_update = 0 | ||||
| 	if config.hackernews_onstart == true then | ||||
| 		hackernews.results = get_hackernews_results() | ||||
| 		if hackernews.results then hackernews.last_update = os.time() / 60 end | ||||
| 	end | ||||
|     hackernews.topstories_url = 'https://hacker-news.firebaseio.com/v0/topstories.json' | ||||
|     hackernews.res_url = 'https://hacker-news.firebaseio.com/v0/item/%s.json' | ||||
|     hackernews.art_url = 'https://news.ycombinator.com/item?id=%s' | ||||
|     hackernews.last_update = 0 | ||||
|     if config.hackernews_onstart == true then | ||||
|         hackernews.results = get_hackernews_results() | ||||
|         if hackernews.results then hackernews.last_update = os.time() / 60 end | ||||
|     end | ||||
| end | ||||
|  | ||||
| function hackernews:action(msg, config) | ||||
| 	local now = os.time() / 60 | ||||
| 	if not hackernews.results or hackernews.last_update + config.hackernews_interval < now then | ||||
| 		bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' }) | ||||
| 		hackernews.results = get_hackernews_results() | ||||
| 		if not hackernews.results then | ||||
| 			utilities.send_reply(self, msg, config.errors.connection) | ||||
| 			return | ||||
| 		end | ||||
| 		hackernews.last_update = now | ||||
| 	end | ||||
| 	-- Four results in a group, eight in private. | ||||
| 	local res_count = msg.chat.id == msg.from.id and 8 or 4 | ||||
| 	local output = '<b>Top Stories from Hacker News:</b>' | ||||
| 	for i = 1, res_count do | ||||
| 		output = output .. hackernews.results[i] | ||||
| 	end | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, 'html') | ||||
|     local now = os.time() / 60 | ||||
|     if not hackernews.results or hackernews.last_update + config.hackernews_interval < now then | ||||
|         bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' }) | ||||
|         hackernews.results = get_hackernews_results() | ||||
|         if not hackernews.results then | ||||
|             utilities.send_reply(self, msg, config.errors.connection) | ||||
|             return | ||||
|         end | ||||
|         hackernews.last_update = now | ||||
|     end | ||||
|     -- Four results in a group, eight in private. | ||||
|     local res_count = msg.chat.id == msg.from.id and 8 or 4 | ||||
|     local output = '<b>Top Stories from Hacker News:</b>' | ||||
|     for i = 1, res_count do | ||||
|         output = output .. hackernews.results[i] | ||||
|     end | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, 'html') | ||||
| end | ||||
|  | ||||
| return hackernews | ||||
|   | ||||
| @@ -8,111 +8,111 @@ local utilities = require('otouto.utilities') | ||||
| local HTTPS = require('ssl.https') | ||||
|  | ||||
| function hearthstone:init(config) | ||||
| 	hearthstone.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hearthstone', true):t('hs').table | ||||
| 	hearthstone.command = 'hearthstone <query>' | ||||
|     hearthstone.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('hearthstone', true):t('hs').table | ||||
|     hearthstone.command = 'hearthstone <query>' | ||||
|  | ||||
| 	if not self.database.hearthstone or os.time() > self.database.hearthstone.expiration then | ||||
|     if not self.database.hearthstone or os.time() > self.database.hearthstone.expiration then | ||||
|  | ||||
| 		print('Downloading Hearthstone database...') | ||||
|         print('Downloading Hearthstone database...') | ||||
|  | ||||
| 		local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json') | ||||
| 		if not jstr or res ~= 200 then | ||||
| 			print('Error connecting to hearthstonejson.com.') | ||||
| 			print('hearthstone.lua will not be enabled.') | ||||
| 			hearthstone.command = nil | ||||
| 			hearthstone.triggers = nil | ||||
| 			return | ||||
| 		end | ||||
| 		self.database.hearthstone = JSON.decode(jstr) | ||||
| 		self.database.hearthstone.expiration = os.time() + 600000 | ||||
|         local jstr, res = HTTPS.request('https://api.hearthstonejson.com/v1/latest/enUS/cards.json') | ||||
|         if not jstr or res ~= 200 then | ||||
|             print('Error connecting to hearthstonejson.com.') | ||||
|             print('hearthstone.lua will not be enabled.') | ||||
|             hearthstone.command = nil | ||||
|             hearthstone.triggers = nil | ||||
|             return | ||||
|         end | ||||
|         self.database.hearthstone = JSON.decode(jstr) | ||||
|         self.database.hearthstone.expiration = os.time() + 600000 | ||||
|  | ||||
| 		print('Download complete! It will be stored for a week.') | ||||
|         print('Download complete! It will be stored for a week.') | ||||
|  | ||||
| 	end | ||||
|     end | ||||
|  | ||||
| 	hearthstone.doc = config.cmd_pat .. [[hearthstone <query> | ||||
|     hearthstone.doc = config.cmd_pat .. [[hearthstone <query> | ||||
| Returns Hearthstone card info. | ||||
| Alias: ]] .. config.cmd_pat .. 'hs' | ||||
| end | ||||
|  | ||||
| local function format_card(card) | ||||
|  | ||||
| 	local ctype = card.type | ||||
| 	if card.race then | ||||
| 		ctype = card.race | ||||
| 	end | ||||
| 	if card.rarity then | ||||
| 		ctype = card.rarity .. ' ' .. ctype | ||||
| 	end | ||||
| 	if card.playerClass then | ||||
| 		ctype = ctype .. ' (' .. card.playerClass .. ')' | ||||
| 	elseif card.faction then | ||||
| 		ctype = ctype .. ' (' .. card.faction .. ')' | ||||
| 	end | ||||
|     local ctype = card.type | ||||
|     if card.race then | ||||
|         ctype = card.race | ||||
|     end | ||||
|     if card.rarity then | ||||
|         ctype = card.rarity .. ' ' .. ctype | ||||
|     end | ||||
|     if card.playerClass then | ||||
|         ctype = ctype .. ' (' .. card.playerClass .. ')' | ||||
|     elseif card.faction then | ||||
|         ctype = ctype .. ' (' .. card.faction .. ')' | ||||
|     end | ||||
|  | ||||
| 	local stats | ||||
| 	if card.cost then | ||||
| 		stats = card.cost .. 'c' | ||||
| 		if card.attack then | ||||
| 			stats = stats .. ' | ' .. card.attack .. 'a' | ||||
| 		end | ||||
| 		if card.health then | ||||
| 			stats = stats .. ' | ' .. card.health .. 'h' | ||||
| 		end | ||||
| 		if card.durability then | ||||
| 			stats = stats .. ' | ' .. card.durability .. 'd' | ||||
| 		end | ||||
| 	elseif card.health then | ||||
| 		stats = card.health .. 'h' | ||||
| 	end | ||||
|     local stats | ||||
|     if card.cost then | ||||
|         stats = card.cost .. 'c' | ||||
|         if card.attack then | ||||
|             stats = stats .. ' | ' .. card.attack .. 'a' | ||||
|         end | ||||
|         if card.health then | ||||
|             stats = stats .. ' | ' .. card.health .. 'h' | ||||
|         end | ||||
|         if card.durability then | ||||
|             stats = stats .. ' | ' .. card.durability .. 'd' | ||||
|         end | ||||
|     elseif card.health then | ||||
|         stats = card.health .. 'h' | ||||
|     end | ||||
|  | ||||
| 	-- unused? | ||||
| 	local info | ||||
| 	if card.text then | ||||
| 		info = card.text:gsub('</?.->',''):gsub('%$','') | ||||
| 		if card.flavor then | ||||
| 			info = info .. '\n_' .. card.flavor .. '_' | ||||
| 		end | ||||
| 	elseif card.flavor then | ||||
| 		info = card.flavor | ||||
| 	else | ||||
| 		info = nil | ||||
| 	end | ||||
|     -- unused? | ||||
|     local info | ||||
|     if card.text then | ||||
|         info = card.text:gsub('</?.->',''):gsub('%$','') | ||||
|         if card.flavor then | ||||
|             info = info .. '\n_' .. card.flavor .. '_' | ||||
|         end | ||||
|     elseif card.flavor then | ||||
|         info = card.flavor | ||||
|     else | ||||
|         info = nil | ||||
|     end | ||||
|  | ||||
| 	local s = '*' .. card.name .. '*\n' .. ctype | ||||
| 	if stats then | ||||
| 		s = s .. '\n' .. stats | ||||
| 	end | ||||
| 	if info then | ||||
| 		s = s .. '\n' .. info | ||||
| 	end | ||||
|     local s = '*' .. card.name .. '*\n' .. ctype | ||||
|     if stats then | ||||
|         s = s .. '\n' .. stats | ||||
|     end | ||||
|     if info then | ||||
|         s = s .. '\n' .. info | ||||
|     end | ||||
|  | ||||
| 	return s | ||||
|     return s | ||||
|  | ||||
| end | ||||
|  | ||||
| function hearthstone:action(msg, config) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, hearthstone.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, hearthstone.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local output = '' | ||||
| 	for _,v in pairs(self.database.hearthstone) do | ||||
| 		if type(v) == 'table' and string.lower(v.name):match(input) then | ||||
| 			output = output .. format_card(v) .. '\n\n' | ||||
| 		end | ||||
| 	end | ||||
|     local output = '' | ||||
|     for _,v in pairs(self.database.hearthstone) do | ||||
|         if type(v) == 'table' and string.lower(v.name):match(input) then | ||||
|             output = output .. format_card(v) .. '\n\n' | ||||
|         end | ||||
|     end | ||||
|  | ||||
| 	output = utilities.trim(output) | ||||
| 	if output:len() == 0 then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     output = utilities.trim(output) | ||||
|     if output:len() == 0 then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) | ||||
|     utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -3,51 +3,51 @@ local utilities = require('otouto.utilities') | ||||
| local help = {} | ||||
|  | ||||
| function help:init(config) | ||||
| 	help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('help', true):t('h', true).table | ||||
| 	help.command = 'help [command]' | ||||
| 	help.doc = config.cmd_pat .. 'help [command] \nReturns usage information for a given command.' | ||||
|     help.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('help', true):t('h', true).table | ||||
|     help.command = 'help [command]' | ||||
|     help.doc = config.cmd_pat .. 'help [command] \nReturns usage information for a given command.' | ||||
| end | ||||
|  | ||||
| function help:action(msg, config) | ||||
| 	local input = utilities.input(msg.text_lower) | ||||
| 	if input then | ||||
| 		if not help.help_word then | ||||
| 			for _, plugin in ipairs(self.plugins) do | ||||
| 				if plugin.command and plugin.doc and not plugin.help_word then | ||||
| 					plugin.help_word = utilities.get_word(plugin.command, 1) | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 		for _,plugin in ipairs(self.plugins) do | ||||
| 			if plugin.help_word == input:gsub('^/', '') then | ||||
| 				local output = '*Help for* _' .. plugin.help_word .. '_*:*\n' .. plugin.doc | ||||
| 				utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
| 				return | ||||
| 			end | ||||
| 		end | ||||
| 		utilities.send_reply(self, msg, 'Sorry, there is no help for that command.') | ||||
| 	else | ||||
| 		-- Generate the help message on first run. | ||||
| 		if not help.text then | ||||
| 			local commandlist = {} | ||||
| 			for _, plugin in ipairs(self.plugins) do | ||||
| 				if plugin.command then | ||||
| 					table.insert(commandlist, plugin.command) | ||||
| 				end | ||||
| 			end | ||||
| 			table.sort(commandlist) | ||||
| 			help.text = '*Available commands:*\n• ' .. config.cmd_pat .. table.concat(commandlist, '\n• '..config.cmd_pat) .. '\nArguments: <required> [optional]' | ||||
| 			help.text = help.text:gsub('%[', '\\[') | ||||
| 		end | ||||
| 		-- Attempt to send the help message via PM. | ||||
| 		-- If msg is from a group, tell the group whether the PM was successful. | ||||
| 		local res = utilities.send_message(self, msg.from.id, help.text, true, nil, true) | ||||
| 		if not res then | ||||
| 			utilities.send_reply(self, msg, 'Please [message me privately](http://telegram.me/' .. self.info.username .. '?start=help) for a list of commands.', true) | ||||
| 		elseif msg.chat.type ~= 'private' then | ||||
| 			utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') | ||||
| 		end | ||||
| 	end | ||||
|     local input = utilities.input(msg.text_lower) | ||||
|     if input then | ||||
|         if not help.help_word then | ||||
|             for _, plugin in ipairs(self.plugins) do | ||||
|                 if plugin.command and plugin.doc and not plugin.help_word then | ||||
|                     plugin.help_word = utilities.get_word(plugin.command, 1) | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|         for _,plugin in ipairs(self.plugins) do | ||||
|             if plugin.help_word == input:gsub('^/', '') then | ||||
|                 local output = '*Help for* _' .. plugin.help_word .. '_*:*\n' .. plugin.doc | ||||
|                 utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|                 return | ||||
|             end | ||||
|         end | ||||
|         utilities.send_reply(self, msg, 'Sorry, there is no help for that command.') | ||||
|     else | ||||
|         -- Generate the help message on first run. | ||||
|         if not help.text then | ||||
|             local commandlist = {} | ||||
|             for _, plugin in ipairs(self.plugins) do | ||||
|                 if plugin.command then | ||||
|                     table.insert(commandlist, plugin.command) | ||||
|                 end | ||||
|             end | ||||
|             table.sort(commandlist) | ||||
|             help.text = '*Available commands:*\n• ' .. config.cmd_pat .. table.concat(commandlist, '\n• '..config.cmd_pat) .. '\nArguments: <required> [optional]' | ||||
|             help.text = help.text:gsub('%[', '\\[') | ||||
|         end | ||||
|         -- Attempt to send the help message via PM. | ||||
|         -- If msg is from a group, tell the group whether the PM was successful. | ||||
|         local res = utilities.send_message(self, msg.from.id, help.text, true, nil, true) | ||||
|         if not res then | ||||
|             utilities.send_reply(self, msg, 'Please [message me privately](http://telegram.me/' .. self.info.username .. '?start=help) for a list of commands.', true) | ||||
|         elseif msg.chat.type ~= 'private' then | ||||
|             utilities.send_reply(self, msg, 'I have sent you the requested information in a private message.') | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| return help | ||||
|   | ||||
| @@ -3,60 +3,60 @@ local utilities = require('otouto.utilities') | ||||
| local id = {} | ||||
|  | ||||
| function id:init(config) | ||||
| 	id.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('id', true).table | ||||
| 	id.command = 'id <user>' | ||||
| 	id.doc = config.cmd_pat .. [[id <user> ... | ||||
|     id.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('id', true).table | ||||
|     id.command = 'id <user>' | ||||
|     id.doc = config.cmd_pat .. [[id <user> ... | ||||
| Returns the name, ID, and username (if applicable) for the given users. | ||||
| Arguments must be usernames and/or IDs. Input is also accepted via reply. If no input is given, returns info for the user. | ||||
| 	]] | ||||
|     ]] | ||||
| end | ||||
|  | ||||
| function id.format(t) | ||||
| 	if t.username then | ||||
| 		return string.format( | ||||
| 			'@%s, AKA <b>%s</b> <code>[%s]</code>.\n', | ||||
| 			t.username, | ||||
| 			utilities.build_name(t.first_name, t.last_name), | ||||
| 			t.id | ||||
| 		) | ||||
| 	else | ||||
| 		return string.format( | ||||
| 			'<b>%s</b> <code>[%s]</code>.\n', | ||||
| 			utilities.build_name(t.first_name, t.last_name), | ||||
| 			t.id | ||||
| 		) | ||||
| 	end | ||||
|     if t.username then | ||||
|         return string.format( | ||||
|             '@%s, AKA <b>%s</b> <code>[%s]</code>.\n', | ||||
|             t.username, | ||||
|             utilities.build_name(t.first_name, t.last_name), | ||||
|             t.id | ||||
|         ) | ||||
|     else | ||||
|         return string.format( | ||||
|             '<b>%s</b> <code>[%s]</code>.\n', | ||||
|             utilities.build_name(t.first_name, t.last_name), | ||||
|             t.id | ||||
|         ) | ||||
|     end | ||||
| end | ||||
|  | ||||
| function id:action(msg) | ||||
| 	local output | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	if msg.reply_to_message then | ||||
| 		output = id.format(msg.reply_to_message.from) | ||||
| 	elseif input then | ||||
| 		output = '' | ||||
| 		for user in input:gmatch('%g+') do | ||||
| 			if tonumber(user) then | ||||
| 				if self.database.users[user] then | ||||
| 					output = output .. id.format(self.database.users[user]) | ||||
| 				else | ||||
| 					output = output .. 'I don\'t recognize that ID (' .. user .. ').\n' | ||||
| 				end | ||||
| 			elseif user:match('^@') then | ||||
| 				local t = utilities.resolve_username(self, user) | ||||
| 				if t then | ||||
| 					output = output .. id.format(t) | ||||
| 				else | ||||
| 					output = output .. 'I don\'t recognize that username (' .. user .. ').\n' | ||||
| 				end | ||||
| 			else | ||||
| 				output = output .. 'Invalid username or ID (' .. user .. ').\n' | ||||
| 			end | ||||
| 		end | ||||
| 	else | ||||
| 		output = id.format(msg.from) | ||||
| 	end | ||||
| 	utilities.send_reply(self, msg, output, 'html') | ||||
|     local output | ||||
|     local input = utilities.input(msg.text) | ||||
|     if msg.reply_to_message then | ||||
|         output = id.format(msg.reply_to_message.from) | ||||
|     elseif input then | ||||
|         output = '' | ||||
|         for user in input:gmatch('%g+') do | ||||
|             if tonumber(user) then | ||||
|                 if self.database.users[user] then | ||||
|                     output = output .. id.format(self.database.users[user]) | ||||
|                 else | ||||
|                     output = output .. 'I don\'t recognize that ID (' .. user .. ').\n' | ||||
|                 end | ||||
|             elseif user:match('^@') then | ||||
|                 local t = utilities.resolve_username(self, user) | ||||
|                 if t then | ||||
|                     output = output .. id.format(t) | ||||
|                 else | ||||
|                     output = output .. 'I don\'t recognize that username (' .. user .. ').\n' | ||||
|                 end | ||||
|             else | ||||
|                 output = output .. 'Invalid username or ID (' .. user .. ').\n' | ||||
|             end | ||||
|         end | ||||
|     else | ||||
|         output = id.format(msg.from) | ||||
|     end | ||||
|     utilities.send_reply(self, msg, output, 'html') | ||||
| end | ||||
|  | ||||
| return id | ||||
|   | ||||
| @@ -8,39 +8,39 @@ local utilities = require('otouto.utilities') | ||||
| imdb.command = 'imdb <query>' | ||||
|  | ||||
| function imdb:init(config) | ||||
| 	imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table | ||||
| 	imdb.doc = config.cmd_pat .. 'imdb <query> \nReturns an IMDb entry.' | ||||
|     imdb.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('imdb', true).table | ||||
|     imdb.doc = config.cmd_pat .. 'imdb <query> \nReturns an IMDb entry.' | ||||
| end | ||||
|  | ||||
| function imdb:action(msg, config) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, imdb.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, imdb.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input) | ||||
|     local url = 'http://www.omdbapi.com/?t=' .. URL.escape(input) | ||||
|  | ||||
| 	local jstr, res = HTTP.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     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) | ||||
|     local jdat = JSON.decode(jstr) | ||||
|  | ||||
| 	if jdat.Response ~= 'True' then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     if jdat.Response ~= 'True' then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')*\n' | ||||
| 	output = output .. jdat.imdbRating ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n' | ||||
| 	output = output .. '_' .. jdat.Plot .. '_\n' | ||||
| 	output = output .. '[Read more.](http://imdb.com/title/' .. jdat.imdbID .. ')' | ||||
|     local output = '*' .. jdat.Title .. ' ('.. jdat.Year ..')*\n' | ||||
|     output = output .. jdat.imdbRating ..'/10 | '.. jdat.Runtime ..' | '.. jdat.Genre ..'\n' | ||||
|     output = output .. '_' .. jdat.Plot .. '_\n' | ||||
|     output = output .. '[Read more.](http://imdb.com/title/' .. jdat.imdbID .. ')' | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -7,37 +7,37 @@ local utilities = require('otouto.utilities') | ||||
| local isup = {} | ||||
|  | ||||
| function isup:init(config) | ||||
| 	isup.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('websitedown', true):t('isitup', true):t('isup', true).table | ||||
|     isup.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('websitedown', true):t('isitup', true):t('isup', true).table | ||||
|  | ||||
| 	isup.doc = config.cmd_pat .. [[isup <url> | ||||
|     isup.doc = config.cmd_pat .. [[isup <url> | ||||
| Returns the up or down status of a website.]] | ||||
| 	isup.command = 'isup <url>' | ||||
|     isup.command = 'isup <url>' | ||||
| end | ||||
|  | ||||
| function isup:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, isup.doc) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, isup.doc) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local protocol = HTTP | ||||
| 	local url_lower = input:lower() | ||||
| 	if url_lower:match('^https') then | ||||
| 		protocol = HTTPS | ||||
| 	elseif not url_lower:match('^http') then | ||||
| 		input = 'http://' .. input | ||||
| 	end | ||||
| 	local _, code = protocol.request(input) | ||||
| 	code = tonumber(code) | ||||
| 	local output | ||||
| 	if not code or code > 399 then | ||||
| 		output = 'This website is down or nonexistent.' | ||||
| 	else | ||||
| 		output = 'This website is up.' | ||||
| 	end | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|     local protocol = HTTP | ||||
|     local url_lower = input:lower() | ||||
|     if url_lower:match('^https') then | ||||
|         protocol = HTTPS | ||||
|     elseif not url_lower:match('^http') then | ||||
|         input = 'http://' .. input | ||||
|     end | ||||
|     local _, code = protocol.request(input) | ||||
|     code = tonumber(code) | ||||
|     local output | ||||
|     if not code or code > 399 then | ||||
|         output = 'This website is down or nonexistent.' | ||||
|     else | ||||
|         output = 'This website is up.' | ||||
|     end | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
| end | ||||
|  | ||||
| return isup | ||||
|   | ||||
| @@ -9,12 +9,12 @@ local JSON = require('dkjson') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function lastfm:init(config) | ||||
| 	assert(config.lastfm_api_key, | ||||
| 		'lastfm.lua requires a last.fm API key from http://last.fm/api.' | ||||
| 	) | ||||
|     assert(config.lastfm_api_key, | ||||
|         'lastfm.lua requires a last.fm API key from http://last.fm/api.' | ||||
|     ) | ||||
|  | ||||
| 	lastfm.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lastfm', true):t('np', true):t('fmset', true).table | ||||
| 	lastfm.doc = config.cmd_pat .. [[np [username] | ||||
|     lastfm.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lastfm', true):t('np', true):t('fmset', true).table | ||||
|     lastfm.doc = config.cmd_pat .. [[np [username] | ||||
| Returns what you are or were last listening to. If you specify a username, info will be returned for that username. | ||||
|  | ||||
| ]] .. config.cmd_pat .. [[fmset <username> | ||||
| @@ -25,84 +25,84 @@ lastfm.command = 'lastfm' | ||||
|  | ||||
| function lastfm:action(msg, config) | ||||
|  | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	local from_id_str = tostring(msg.from.id) | ||||
| 	self.database.userdata[from_id_str] = self.database.userdata[from_id_str] or {} | ||||
|     local input = utilities.input(msg.text) | ||||
|     local from_id_str = tostring(msg.from.id) | ||||
|     self.database.userdata[from_id_str] = self.database.userdata[from_id_str] or {} | ||||
|  | ||||
| 	if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then | ||||
| 		utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) | ||||
| 		return | ||||
| 	elseif string.match(msg.text, '^'..config.cmd_pat..'fmset') then | ||||
| 		if not input then | ||||
| 			utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) | ||||
| 		elseif input == '--' or input == utilities.char.em_dash then | ||||
| 			self.database.userdata[from_id_str].lastfm = nil | ||||
| 			utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.') | ||||
| 		else | ||||
| 			self.database.userdata[from_id_str].lastfm = input | ||||
| 			utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".') | ||||
| 		end | ||||
| 		return | ||||
| 	end | ||||
|     if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then | ||||
|         utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) | ||||
|         return | ||||
|     elseif string.match(msg.text, '^'..config.cmd_pat..'fmset') then | ||||
|         if not input then | ||||
|             utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true) | ||||
|         elseif input == '--' or input == utilities.char.em_dash then | ||||
|             self.database.userdata[from_id_str].lastfm = nil | ||||
|             utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.') | ||||
|         else | ||||
|             self.database.userdata[from_id_str].lastfm = input | ||||
|             utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".') | ||||
|         end | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user=' | ||||
|     local url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&api_key=' .. config.lastfm_api_key .. '&user=' | ||||
|  | ||||
| 	local username | ||||
| 	local alert = '' | ||||
| 	if input then | ||||
| 		username = input | ||||
| 	elseif self.database.userdata[from_id_str].lastfm then | ||||
| 		username = self.database.userdata[from_id_str].lastfm | ||||
| 	elseif msg.from.username then | ||||
| 		username = msg.from.username | ||||
| 		alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use '..config.cmd_pat..'fmset <username>.' | ||||
| 		self.database.userdata[from_id_str].lastfm = username | ||||
| 	else | ||||
| 		utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') | ||||
| 		return | ||||
| 	end | ||||
|     local username | ||||
|     local alert = '' | ||||
|     if input then | ||||
|         username = input | ||||
|     elseif self.database.userdata[from_id_str].lastfm then | ||||
|         username = self.database.userdata[from_id_str].lastfm | ||||
|     elseif msg.from.username then | ||||
|         username = msg.from.username | ||||
|         alert = '\n\nYour username has been set to ' .. username .. '.\nTo change it, use '..config.cmd_pat..'fmset <username>.' | ||||
|         self.database.userdata[from_id_str].lastfm = username | ||||
|     else | ||||
|         utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	url = url .. URL.escape(username) | ||||
|     url = url .. URL.escape(username) | ||||
|  | ||||
| 	local jstr, res | ||||
| 	utilities.with_http_timeout( | ||||
| 		1, function () | ||||
| 			jstr, res = HTTP.request(url) | ||||
| 	end) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local jstr, res | ||||
|     utilities.with_http_timeout( | ||||
|         1, function () | ||||
|             jstr, res = HTTP.request(url) | ||||
|     end) | ||||
|     if res ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local jdat = JSON.decode(jstr) | ||||
| 	if jdat.error then | ||||
| 		utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') | ||||
| 		return | ||||
| 	end | ||||
|     local jdat = JSON.decode(jstr) | ||||
|     if jdat.error then | ||||
|         utilities.send_reply(self, msg, 'Please specify your last.fm username or set it with '..config.cmd_pat..'fmset.') | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track | ||||
| 	if not jdat then | ||||
| 		utilities.send_reply(self, msg, 'No history for this user.' .. alert) | ||||
| 		return | ||||
| 	end | ||||
|     jdat = jdat.recenttracks.track[1] or jdat.recenttracks.track | ||||
|     if not jdat then | ||||
|         utilities.send_reply(self, msg, 'No history for this user.' .. alert) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local output = input or msg.from.first_name | ||||
| 	output = '🎵  ' .. output | ||||
|     local output = input or msg.from.first_name | ||||
|     output = '🎵  ' .. output | ||||
|  | ||||
| 	if jdat['@attr'] and jdat['@attr'].nowplaying then | ||||
| 		output = output .. ' is currently listening to:\n' | ||||
| 	else | ||||
| 		output = output .. ' last listened to:\n' | ||||
| 	end | ||||
|     if jdat['@attr'] and jdat['@attr'].nowplaying then | ||||
|         output = output .. ' is currently listening to:\n' | ||||
|     else | ||||
|         output = output .. ' last listened to:\n' | ||||
|     end | ||||
|  | ||||
| 	local title = jdat.name or 'Unknown' | ||||
| 	local artist = 'Unknown' | ||||
| 	if jdat.artist then | ||||
| 		artist = jdat.artist['#text'] | ||||
| 	end | ||||
|     local title = jdat.name or 'Unknown' | ||||
|     local artist = 'Unknown' | ||||
|     if jdat.artist then | ||||
|         artist = jdat.artist['#text'] | ||||
|     end | ||||
|  | ||||
| 	output = output .. title .. ' - ' .. artist .. alert | ||||
| 	utilities.send_message(self, msg.chat.id, output) | ||||
|     output = output .. title .. ' - ' .. artist .. alert | ||||
|     utilities.send_message(self, msg.chat.id, output) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -5,59 +5,59 @@ local URL = require('socket.url') | ||||
| local JSON, serpent | ||||
|  | ||||
| function luarun:init(config) | ||||
| 	luarun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lua', true):t('return', true).table | ||||
| 	if config.luarun_serpent then | ||||
| 		serpent = require('serpent') | ||||
| 		luarun.serialize = function(t) | ||||
| 			return serpent.block(t, {comment=false}) | ||||
| 		end | ||||
| 	else | ||||
| 		JSON = require('dkjson') | ||||
| 		luarun.serialize = function(t) | ||||
| 			return JSON.encode(t, {indent=true}) | ||||
| 		end | ||||
| 	end | ||||
|     luarun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('lua', true):t('return', true).table | ||||
|     if config.luarun_serpent then | ||||
|         serpent = require('serpent') | ||||
|         luarun.serialize = function(t) | ||||
|             return serpent.block(t, {comment=false}) | ||||
|         end | ||||
|     else | ||||
|         JSON = require('dkjson') | ||||
|         luarun.serialize = function(t) | ||||
|             return JSON.encode(t, {indent=true}) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| function luarun:action(msg, config) | ||||
|  | ||||
| 	if msg.from.id ~= config.admin then | ||||
| 		return true | ||||
| 	end | ||||
|     if msg.from.id ~= config.admin then | ||||
|         return true | ||||
|     end | ||||
|  | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, 'Please enter a string to load.') | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input(msg.text) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, 'Please enter a string to load.') | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	if msg.text_lower:match('^'..config.cmd_pat..'return') then | ||||
| 		input = 'return ' .. input | ||||
| 	end | ||||
|     if msg.text_lower:match('^'..config.cmd_pat..'return') then | ||||
|         input = 'return ' .. input | ||||
|     end | ||||
|  | ||||
| 	local output = loadstring( [[ | ||||
| 		local bot = require('otouto.bot') | ||||
| 		local bindings = require('otouto.bindings') | ||||
| 		local utilities = require('otouto.utilities') | ||||
| 		local drua = require('otouto.drua-tg') | ||||
| 		local JSON = require('dkjson') | ||||
| 		local URL = require('socket.url') | ||||
| 		local HTTP = require('socket.http') | ||||
| 		local HTTPS = require('ssl.https') | ||||
| 		return function (self, msg, config) ]] .. input .. [[ end | ||||
| 	]] )()(self, msg, config) | ||||
| 	if output == nil then | ||||
| 		output = 'Done!' | ||||
| 	else | ||||
| 		if type(output) == 'table' then | ||||
| 			local s = luarun.serialize(output) | ||||
| 			if URL.escape(s):len() < 4000 then | ||||
| 				output = s | ||||
| 			end | ||||
| 		end | ||||
| 		output = '```\n' .. tostring(output) .. '\n```' | ||||
| 	end | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) | ||||
|     local output = loadstring( [[ | ||||
|         local bot = require('otouto.bot') | ||||
|         local bindings = require('otouto.bindings') | ||||
|         local utilities = require('otouto.utilities') | ||||
|         local drua = require('otouto.drua-tg') | ||||
|         local JSON = require('dkjson') | ||||
|         local URL = require('socket.url') | ||||
|         local HTTP = require('socket.http') | ||||
|         local HTTPS = require('ssl.https') | ||||
|         return function (self, msg, config) ]] .. input .. [[ end | ||||
|     ]] )()(self, msg, config) | ||||
|     if output == nil then | ||||
|         output = 'Done!' | ||||
|     else | ||||
|         if type(output) == 'table' then | ||||
|             local s = luarun.serialize(output) | ||||
|             if URL.escape(s):len() < 4000 then | ||||
|                 output = s | ||||
|             end | ||||
|         end | ||||
|         output = '```\n' .. tostring(output) .. '\n```' | ||||
|     end | ||||
|     utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -3,65 +3,65 @@ local me = {} | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function me:init(config) | ||||
| 	me.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('me', true).table | ||||
| 	me.command = 'me' | ||||
| 	me.doc = 'Returns userdata stored by the bot.' | ||||
|     me.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('me', true).table | ||||
|     me.command = 'me' | ||||
|     me.doc = 'Returns userdata stored by the bot.' | ||||
| end | ||||
|  | ||||
| function me:action(msg, config) | ||||
| 	local user | ||||
| 	if msg.from.id == config.admin then | ||||
| 		if msg.reply_to_message then | ||||
| 			user = msg.reply_to_message.from | ||||
| 		else | ||||
| 			local input = utilities.input(msg.text) | ||||
| 			if input then | ||||
| 				if tonumber(input) then | ||||
| 					user = self.database.users[input] | ||||
| 					if not user then | ||||
| 						utilities.send_reply(self, msg, 'Unrecognized ID.') | ||||
| 						return | ||||
| 					end | ||||
| 				elseif input:match('^@') then | ||||
| 					user = utilities.resolve_username(self, input) | ||||
| 					if not user then | ||||
| 						utilities.send_reply(self, msg, 'Unrecognized username.') | ||||
| 						return | ||||
| 					end | ||||
| 				else | ||||
| 					utilities.send_reply(self, msg, 'Invalid username or ID.') | ||||
| 					return | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	user = user or msg.from | ||||
| 	local userdata = self.database.userdata[tostring(user.id)] or {} | ||||
|     local user | ||||
|     if msg.from.id == config.admin then | ||||
|         if msg.reply_to_message then | ||||
|             user = msg.reply_to_message.from | ||||
|         else | ||||
|             local input = utilities.input(msg.text) | ||||
|             if input then | ||||
|                 if tonumber(input) then | ||||
|                     user = self.database.users[input] | ||||
|                     if not user then | ||||
|                         utilities.send_reply(self, msg, 'Unrecognized ID.') | ||||
|                         return | ||||
|                     end | ||||
|                 elseif input:match('^@') then | ||||
|                     user = utilities.resolve_username(self, input) | ||||
|                     if not user then | ||||
|                         utilities.send_reply(self, msg, 'Unrecognized username.') | ||||
|                         return | ||||
|                     end | ||||
|                 else | ||||
|                     utilities.send_reply(self, msg, 'Invalid username or ID.') | ||||
|                     return | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|     end | ||||
|     user = user or msg.from | ||||
|     local userdata = self.database.userdata[tostring(user.id)] or {} | ||||
|  | ||||
| 	local data = {} | ||||
| 	for k,v in pairs(userdata) do | ||||
| 		table.insert(data, string.format( | ||||
| 			'<b>%s</b> <code>%s</code>\n', | ||||
| 			utilities.html_escape(k), | ||||
| 			utilities.html_escape(v) | ||||
| 		)) | ||||
| 	end | ||||
|     local data = {} | ||||
|     for k,v in pairs(userdata) do | ||||
|         table.insert(data, string.format( | ||||
|             '<b>%s</b> <code>%s</code>\n', | ||||
|             utilities.html_escape(k), | ||||
|             utilities.html_escape(v) | ||||
|         )) | ||||
|     end | ||||
|  | ||||
| 	local output | ||||
| 	if #data == 0 then | ||||
| 		output = 'There is no data stored for this user.' | ||||
| 	else | ||||
| 		output = string.format( | ||||
| 			'<b>%s</b> <code>[%s]</code><b>:</b>\n', | ||||
| 			utilities.html_escape(utilities.build_name( | ||||
| 				user.first_name, | ||||
| 				user.last_name | ||||
| 			)), | ||||
| 			user.id | ||||
| 		) .. table.concat(data) | ||||
| 	end | ||||
|     local output | ||||
|     if #data == 0 then | ||||
|         output = 'There is no data stored for this user.' | ||||
|     else | ||||
|         output = string.format( | ||||
|             '<b>%s</b> <code>[%s]</code><b>:</b>\n', | ||||
|             utilities.html_escape(utilities.build_name( | ||||
|                 user.first_name, | ||||
|                 user.last_name | ||||
|             )), | ||||
|             user.id | ||||
|         ) .. table.concat(data) | ||||
|     end | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, 'html') | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, 'html') | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -5,45 +5,45 @@ local utilities = require('otouto.utilities') | ||||
| nick.command = 'nick <nickname>' | ||||
|  | ||||
| function nick:init(config) | ||||
| 	nick.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('nick', true).table | ||||
| 	nick.doc = config.cmd_pat .. [[nick <nickname> | ||||
|     nick.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('nick', true).table | ||||
|     nick.doc = config.cmd_pat .. [[nick <nickname> | ||||
| Set your nickname. Use "]] .. config.cmd_pat .. 'nick --" to delete it.' | ||||
| end | ||||
|  | ||||
| function nick:action(msg, config) | ||||
|  | ||||
| 	local id_str, name | ||||
|     local id_str, name | ||||
|  | ||||
| 	if msg.from.id == config.admin and msg.reply_to_message then | ||||
| 		id_str = tostring(msg.reply_to_message.from.id) | ||||
| 		name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) | ||||
| 	else | ||||
| 		id_str = tostring(msg.from.id) | ||||
| 		name = utilities.build_name(msg.from.first_name, msg.from.last_name) | ||||
| 	end | ||||
|     if msg.from.id == config.admin and msg.reply_to_message then | ||||
|         id_str = tostring(msg.reply_to_message.from.id) | ||||
|         name = utilities.build_name(msg.reply_to_message.from.first_name, msg.reply_to_message.from.last_name) | ||||
|     else | ||||
|         id_str = tostring(msg.from.id) | ||||
|         name = utilities.build_name(msg.from.first_name, msg.from.last_name) | ||||
|     end | ||||
|  | ||||
| 	self.database.userdata[id_str] = self.database.userdata[id_str] or {} | ||||
|     self.database.userdata[id_str] = self.database.userdata[id_str] or {} | ||||
|  | ||||
| 	local output | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	if not input then | ||||
| 		if self.database.userdata[id_str].nickname then | ||||
| 			output = name .. '\'s nickname is "' .. self.database.userdata[id_str].nickname .. '".' | ||||
| 		else | ||||
| 			output = name .. ' currently has no nickname.' | ||||
| 		end | ||||
| 	elseif utilities.utf8_len(input) > 32 then | ||||
| 		output = 'The character limit for nicknames is 32.' | ||||
| 	elseif input == '--' or input == utilities.char.em_dash then | ||||
| 		self.database.userdata[id_str].nickname = nil | ||||
| 		output = name .. '\'s nickname has been deleted.' | ||||
| 	else | ||||
| 		input = input:gsub('\n', ' ') | ||||
| 		self.database.userdata[id_str].nickname = input | ||||
| 		output = name .. '\'s nickname has been set to "' .. input .. '".' | ||||
| 	end | ||||
|     local output | ||||
|     local input = utilities.input(msg.text) | ||||
|     if not input then | ||||
|         if self.database.userdata[id_str].nickname then | ||||
|             output = name .. '\'s nickname is "' .. self.database.userdata[id_str].nickname .. '".' | ||||
|         else | ||||
|             output = name .. ' currently has no nickname.' | ||||
|         end | ||||
|     elseif utilities.utf8_len(input) > 32 then | ||||
|         output = 'The character limit for nicknames is 32.' | ||||
|     elseif input == '--' or input == utilities.char.em_dash then | ||||
|         self.database.userdata[id_str].nickname = nil | ||||
|         output = name .. '\'s nickname has been deleted.' | ||||
|     else | ||||
|         input = input:gsub('\n', ' ') | ||||
|         self.database.userdata[id_str].nickname = input | ||||
|         output = name .. '\'s nickname has been set to "' .. input .. '".' | ||||
|     end | ||||
|  | ||||
| 	utilities.send_reply(self, msg, output) | ||||
|     utilities.send_reply(self, msg, output) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -8,34 +8,34 @@ patterns.doc = [[ | ||||
| s/<pattern>/<substitution> | ||||
| Replace all matches for the given pattern. | ||||
| Uses Lua patterns. | ||||
| 	]] | ||||
|     ]] | ||||
|  | ||||
| function patterns:init(config) | ||||
| 	patterns.triggers = { config.cmd_pat .. '?s/.-/.-$' } | ||||
|     patterns.triggers = { config.cmd_pat .. '?s/.-/.-$' } | ||||
| end | ||||
|  | ||||
| function patterns:action(msg) | ||||
| 	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"', '') | ||||
| 		output = output:gsub('"$', '') | ||||
| 	end | ||||
| 	local m1, m2 = msg.text:match('^/?s/(.-)/(.-)/?$') | ||||
| 	if not m2 then return true end | ||||
| 	local res | ||||
| 	res, output = pcall( | ||||
| 		function() | ||||
| 			return output:gsub(m1, m2) | ||||
| 		end | ||||
| 	) | ||||
| 	if res == false then | ||||
| 		utilities.send_reply(self, msg, 'Malformed pattern!') | ||||
| 	else | ||||
| 		output = utilities.trim(output:sub(1, 4000)) | ||||
| 		output = utilities.style.enquote('Did you mean', output) | ||||
| 		utilities.send_reply(self, msg.reply_to_message, output, true) | ||||
| 	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"', '') | ||||
|         output = output:gsub('"$', '') | ||||
|     end | ||||
|     local m1, m2 = msg.text:match('^/?s/(.-)/(.-)/?$') | ||||
|     if not m2 then return true end | ||||
|     local res | ||||
|     res, output = pcall( | ||||
|         function() | ||||
|             return output:gsub(m1, m2) | ||||
|         end | ||||
|     ) | ||||
|     if res == false then | ||||
|         utilities.send_reply(self, msg, 'Malformed pattern!') | ||||
|     else | ||||
|         output = utilities.trim(output:sub(1, 4000)) | ||||
|         output = utilities.style.enquote('Did you mean', output) | ||||
|         utilities.send_reply(self, msg.reply_to_message, output, true) | ||||
|     end | ||||
| end | ||||
|  | ||||
| return patterns | ||||
|   | ||||
| @@ -5,12 +5,12 @@ local ping = {} | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function ping:init(config) | ||||
| 	ping.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ping'):t('annyong').table | ||||
|     ping.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('ping'):t('annyong').table | ||||
| end | ||||
|  | ||||
| function ping:action(msg, config) | ||||
| 	local output = msg.text_lower:match('^'..config.cmd_pat..'ping') and 'Pong!' or 'Annyong.' | ||||
| 	utilities.send_message(self, msg.chat.id, output) | ||||
|     local output = msg.text_lower:match('^'..config.cmd_pat..'ping') and 'Pong!' or 'Annyong.' | ||||
|     utilities.send_message(self, msg.chat.id, output) | ||||
| end | ||||
|  | ||||
| return ping | ||||
|   | ||||
| @@ -8,8 +8,8 @@ local utilities = require('otouto.utilities') | ||||
| pokedex.command = 'pokedex <query>' | ||||
|  | ||||
| function pokedex:init(config) | ||||
| 	pokedex.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pokedex', true):t('dex', true).table | ||||
| 	pokedex.doc = config.cmd_pat .. [[pokedex <query> | ||||
|     pokedex.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pokedex', true):t('dex', true).table | ||||
|     pokedex.doc = config.cmd_pat .. [[pokedex <query> | ||||
| Returns a Pokedex entry from pokeapi.co. | ||||
| Queries must be a number of the name of a Pokémon. | ||||
| Alias: ]] .. config.cmd_pat .. 'dex' | ||||
| @@ -17,54 +17,54 @@ end | ||||
|  | ||||
| function pokedex:action(msg, config) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, pokedex.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, pokedex.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } ) | ||||
|     bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } ) | ||||
|  | ||||
| 	local url = 'http://pokeapi.co' | ||||
|     local url = 'http://pokeapi.co' | ||||
|  | ||||
| 	local dex_url = url .. '/api/v1/pokemon/' .. input | ||||
| 	local dex_jstr, res = HTTP.request(dex_url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local dex_url = url .. '/api/v1/pokemon/' .. input | ||||
|     local dex_jstr, res = HTTP.request(dex_url) | ||||
|     if res ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local dex_jdat = JSON.decode(dex_jstr) | ||||
|     local dex_jdat = JSON.decode(dex_jstr) | ||||
|  | ||||
| 	if not dex_jdat.descriptions or not dex_jdat.descriptions[1] then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     if not dex_jdat.descriptions or not dex_jdat.descriptions[1] then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri | ||||
| 	local desc_jstr, _ = HTTP.request(desc_url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local desc_url = url .. dex_jdat.descriptions[math.random(#dex_jdat.descriptions)].resource_uri | ||||
|     local desc_jstr, _ = HTTP.request(desc_url) | ||||
|     if res ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local desc_jdat = JSON.decode(desc_jstr) | ||||
|     local desc_jdat = JSON.decode(desc_jstr) | ||||
|  | ||||
| 	local poke_type | ||||
| 	for _,v in ipairs(dex_jdat.types) do | ||||
| 		local type_name = v.name:gsub("^%l", string.upper) | ||||
| 		if not poke_type then | ||||
| 			poke_type = type_name | ||||
| 		else | ||||
| 			poke_type = poke_type .. ' / ' .. type_name | ||||
| 		end | ||||
| 	end | ||||
| 	poke_type = poke_type .. ' type' | ||||
|     local poke_type | ||||
|     for _,v in ipairs(dex_jdat.types) do | ||||
|         local type_name = v.name:gsub("^%l", string.upper) | ||||
|         if not poke_type then | ||||
|             poke_type = type_name | ||||
|         else | ||||
|             poke_type = poke_type .. ' / ' .. type_name | ||||
|         end | ||||
|     end | ||||
|     poke_type = poke_type .. ' type' | ||||
|  | ||||
| 	local output = '*' .. dex_jdat.name .. '*\n#' .. dex_jdat.national_id .. ' | ' .. poke_type .. '\n_' .. desc_jdat.description:gsub('POKMON', 'Pokémon'):gsub('Pokmon', 'Pokémon') .. '_' | ||||
|     local output = '*' .. dex_jdat.name .. '*\n#' .. dex_jdat.national_id .. ' | ' .. poke_type .. '\n_' .. desc_jdat.description:gsub('POKMON', 'Pokémon'):gsub('Pokmon', 'Pokémon') .. '_' | ||||
|  | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -3,110 +3,110 @@ local utilities = require('otouto.utilities') | ||||
| local pgc = {} | ||||
|  | ||||
| function pgc:init(config) | ||||
| 	pgc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('gocalc', true).table | ||||
| 	pgc.doc = config.cmd_pat .. [[gocalc <required candy> <number of Pokémon> <number of candy> | ||||
|     pgc.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('gocalc', true).table | ||||
|     pgc.doc = config.cmd_pat .. [[gocalc <required candy> <number of Pokémon> <number of candy> | ||||
| Calculates the number of Pokémon that must be transferred before evolving, how many evolutions the user is able to perform, and how many Pokémon and candy will be left over. | ||||
| All arguments must be positive numbers. Batch jobs may be performed by separating valid sets of arguments by lines. | ||||
| Example (forty pidgeys and three hundred pidgey candies): | ||||
| ]] .. config.cmd_pat .. 'gocalc 12 40 300' | ||||
| 	pgc.command = 'gocalc <required candy> <#pokemon> <#candy>' | ||||
|     pgc.command = 'gocalc <required candy> <#pokemon> <#candy>' | ||||
| end | ||||
|  | ||||
|  -- This function written by Juan Potato. MIT-licensed. | ||||
| local pidgey_calc = function(candies_to_evolve, mons, candies) | ||||
| 	local transferred = 0; | ||||
| 	local evolved = 0; | ||||
|     local transferred = 0; | ||||
|     local evolved = 0; | ||||
|  | ||||
| 	while true do | ||||
| 		if math.floor(candies / candies_to_evolve) == 0 or mons == 0 then | ||||
| 			break | ||||
| 		else | ||||
| 			mons = mons - 1 | ||||
| 			candies = candies - candies_to_evolve + 1 | ||||
| 			evolved = evolved + 1 | ||||
| 			if mons == 0 then | ||||
| 				break | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|     while true do | ||||
|         if math.floor(candies / candies_to_evolve) == 0 or mons == 0 then | ||||
|             break | ||||
|         else | ||||
|             mons = mons - 1 | ||||
|             candies = candies - candies_to_evolve + 1 | ||||
|             evolved = evolved + 1 | ||||
|             if mons == 0 then | ||||
|                 break | ||||
|             end | ||||
|         end | ||||
|     end | ||||
|  | ||||
| 	while true do | ||||
| 		if (candies + mons) < (candies_to_evolve + 1) or mons == 0 then | ||||
| 			break | ||||
| 		end | ||||
| 		while candies < candies_to_evolve do | ||||
| 			transferred = transferred + 1 | ||||
| 			mons = mons - 1 | ||||
| 			candies = candies + 1 | ||||
| 		end | ||||
| 		mons = mons - 1 | ||||
| 		candies = candies - candies_to_evolve + 1 | ||||
| 		evolved = evolved + 1 | ||||
| 	end | ||||
|     while true do | ||||
|         if (candies + mons) < (candies_to_evolve + 1) or mons == 0 then | ||||
|             break | ||||
|         end | ||||
|         while candies < candies_to_evolve do | ||||
|             transferred = transferred + 1 | ||||
|             mons = mons - 1 | ||||
|             candies = candies + 1 | ||||
|         end | ||||
|         mons = mons - 1 | ||||
|         candies = candies - candies_to_evolve + 1 | ||||
|         evolved = evolved + 1 | ||||
|     end | ||||
|  | ||||
| 	return { | ||||
| 		transfer = transferred, | ||||
| 		evolve = evolved, | ||||
| 		leftover_mons = mons, | ||||
| 		leftover_candy = candies | ||||
| 	} | ||||
|     return { | ||||
|         transfer = transferred, | ||||
|         evolve = evolved, | ||||
|         leftover_mons = mons, | ||||
|         leftover_candy = candies | ||||
|     } | ||||
| end | ||||
|  | ||||
| local single_job = function(input) | ||||
| 	local req_candy, mons, candies = input:match('^(%d+) (%d+) (%d+)$') | ||||
| 	req_candy = tonumber(req_candy) | ||||
| 	mons = tonumber(mons) | ||||
| 	candies = tonumber(candies) | ||||
| 	if not (req_candy and mons and candies) then | ||||
| 		return { err = 'Invalid input: Three numbers expected.' } | ||||
| 	elseif req_candy > 400 then | ||||
| 		return { err = 'Invalid required candy: Maximum is 400.' } | ||||
| 	elseif mons > 1000 then | ||||
| 		return { err = 'Invalid number of Pokémon: Maximum is 1000.' } | ||||
| 	elseif candies > 10000 then | ||||
| 		return { err = 'Invalid number of candies: Maximum is 10000.' } | ||||
| 	else | ||||
| 		return pidgey_calc(req_candy, mons, candies) | ||||
| 	end | ||||
|     local req_candy, mons, candies = input:match('^(%d+) (%d+) (%d+)$') | ||||
|     req_candy = tonumber(req_candy) | ||||
|     mons = tonumber(mons) | ||||
|     candies = tonumber(candies) | ||||
|     if not (req_candy and mons and candies) then | ||||
|         return { err = 'Invalid input: Three numbers expected.' } | ||||
|     elseif req_candy > 400 then | ||||
|         return { err = 'Invalid required candy: Maximum is 400.' } | ||||
|     elseif mons > 1000 then | ||||
|         return { err = 'Invalid number of Pokémon: Maximum is 1000.' } | ||||
|     elseif candies > 10000 then | ||||
|         return { err = 'Invalid number of candies: Maximum is 10000.' } | ||||
|     else | ||||
|         return pidgey_calc(req_candy, mons, candies) | ||||
|     end | ||||
| end | ||||
|  | ||||
| function pgc:action(msg) | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, pgc.doc, true) | ||||
| 		return | ||||
| 	end | ||||
| 	input = input .. '\n' | ||||
| 	local output = '' | ||||
| 	local total_evolutions = 0 | ||||
| 	for line in input:gmatch('(.-)\n') do | ||||
| 		local info = single_job(line) | ||||
| 		output = output .. '`' .. line .. '`\n' | ||||
| 		if info.err then | ||||
| 			output = output .. info.err .. '\n\n' | ||||
| 		else | ||||
| 			total_evolutions = total_evolutions + info.evolve | ||||
| 			local s = '*Transfer:* %s. \n*Evolve:* %s (%s XP, %s minutes). \n*Leftover:* %s mons, %s candy.\n\n' | ||||
| 			s = s:format(info.transfer, info.evolve, info.evolve..'k', info.evolve*0.5, info.leftover_mons, info.leftover_candy) | ||||
| 			output = output .. s | ||||
| 		end | ||||
| 	end | ||||
| 	local s = '*Total evolutions:* %s. \n*Recommendation:* %s' | ||||
| 	local recommendation | ||||
| 	local egg_count = math.floor(total_evolutions/60) | ||||
| 	if egg_count < 1 then | ||||
| 		recommendation = 'Wait until you have atleast sixty Pokémon to evolve before using a lucky egg.' | ||||
| 	else | ||||
| 		recommendation = string.format( | ||||
| 			'Use %s lucky egg%s for %s evolutions.', | ||||
| 			egg_count, | ||||
| 			egg_count == 1 and '' or 's', | ||||
| 			egg_count * 60 | ||||
| 		) | ||||
| 	end | ||||
| 	s = s:format(total_evolutions, recommendation) | ||||
| 	output = output .. s | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|     local input = utilities.input(msg.text) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, pgc.doc, true) | ||||
|         return | ||||
|     end | ||||
|     input = input .. '\n' | ||||
|     local output = '' | ||||
|     local total_evolutions = 0 | ||||
|     for line in input:gmatch('(.-)\n') do | ||||
|         local info = single_job(line) | ||||
|         output = output .. '`' .. line .. '`\n' | ||||
|         if info.err then | ||||
|             output = output .. info.err .. '\n\n' | ||||
|         else | ||||
|             total_evolutions = total_evolutions + info.evolve | ||||
|             local s = '*Transfer:* %s. \n*Evolve:* %s (%s XP, %s minutes). \n*Leftover:* %s mons, %s candy.\n\n' | ||||
|             s = s:format(info.transfer, info.evolve, info.evolve..'k', info.evolve*0.5, info.leftover_mons, info.leftover_candy) | ||||
|             output = output .. s | ||||
|         end | ||||
|     end | ||||
|     local s = '*Total evolutions:* %s. \n*Recommendation:* %s' | ||||
|     local recommendation | ||||
|     local egg_count = math.floor(total_evolutions/60) | ||||
|     if egg_count < 1 then | ||||
|         recommendation = 'Wait until you have atleast sixty Pokémon to evolve before using a lucky egg.' | ||||
|     else | ||||
|         recommendation = string.format( | ||||
|             'Use %s lucky egg%s for %s evolutions.', | ||||
|             egg_count, | ||||
|             egg_count == 1 and '' or 's', | ||||
|             egg_count * 60 | ||||
|         ) | ||||
|     end | ||||
|     s = s:format(total_evolutions, recommendation) | ||||
|     output = output .. s | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
| end | ||||
|  | ||||
| return pgc | ||||
|   | ||||
| @@ -21,7 +21,7 @@ Set your Pokémon Go team for statistical purposes. The team must be valid, and | ||||
|     db.membership = {} | ||||
|   end | ||||
|   for _, set in pairs(db.membership) do | ||||
|   	setmetatable(set, utilities.set_meta) | ||||
|       setmetatable(set, utilities.set_meta) | ||||
|   end | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -6,37 +6,37 @@ local utilities = require('otouto.utilities') | ||||
| preview.command = 'preview <link>' | ||||
|  | ||||
| function preview:init(config) | ||||
| 	preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table | ||||
| 	preview.doc = config.cmd_pat .. 'preview <link> \nReturns a full-message, "unlinked" preview.' | ||||
|     preview.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('preview', true).table | ||||
|     preview.doc = config.cmd_pat .. 'preview <link> \nReturns a full-message, "unlinked" preview.' | ||||
| end | ||||
|  | ||||
| function preview:action(msg) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, preview.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, preview.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	input = utilities.get_word(input, 1) | ||||
| 	if not input:match('^https?://.+') then | ||||
| 		input = 'http://' .. input | ||||
| 	end | ||||
|     input = utilities.get_word(input, 1) | ||||
|     if not input:match('^https?://.+') then | ||||
|         input = 'http://' .. input | ||||
|     end | ||||
|  | ||||
| 	local res = HTTP.request(input) | ||||
| 	if not res then | ||||
| 		utilities.send_reply(self, msg, 'Please provide a valid link.') | ||||
| 		return | ||||
| 	end | ||||
|     local res = HTTP.request(input) | ||||
|     if not res then | ||||
|         utilities.send_reply(self, msg, 'Please provide a valid link.') | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	if res:len() == 0 then | ||||
| 		utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.') | ||||
| 		return | ||||
| 	end | ||||
|     if res:len() == 0 then | ||||
|         utilities.send_reply(self, msg, 'Sorry, the link you provided is not letting us make a preview.') | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	-- Invisible zero-width, non-joiner. | ||||
| 	local output = '<a href="' .. input .. '">' .. utilities.char.zwnj .. '</a>' | ||||
| 	utilities.send_message(self, msg.chat.id, output, false, nil, 'html') | ||||
|     -- Invisible zero-width, non-joiner. | ||||
|     local output = '<a href="' .. input .. '">' .. utilities.char.zwnj .. '</a>' | ||||
|     utilities.send_message(self, msg.chat.id, output, false, nil, 'html') | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -6,138 +6,138 @@ pun.command = 'pun' | ||||
| pun.doc = 'Returns a pun.' | ||||
|  | ||||
| function pun:init(config) | ||||
| 	pun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pun').table | ||||
|     pun.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('pun').table | ||||
| end | ||||
|  | ||||
| local puns = { | ||||
| 	"The person who invented the door-knock won the No-bell prize.", | ||||
| 	"I couldn't work out how to fasten my seatbelt. Then it clicked.", | ||||
| 	"Never trust atoms; they make up everything.", | ||||
| 	"Singing in the shower is all fun and games until you get shampoo in your mouth - Then it becomes a soap opera.", | ||||
| 	"I can't believe I got fired from the calendar factory. All I did was take a day off.", | ||||
| 	"To the guy who invented zero: Thanks for nothing!", | ||||
| 	"Enough with the cripple jokes! I just can't stand them.", | ||||
| 	"I've accidentally swallowed some Scrabble tiles. My next crap could spell disaster.", | ||||
| 	"How does Moses make his tea? Hebrews it.", | ||||
| 	"Did you hear about the guy who got hit in the head with a can of soda? He was lucky it was a soft drink.", | ||||
| 	"When William joined the army he disliked the phrase 'fire at will'.", | ||||
| 	"There was a sign on the lawn at a rehab center that said 'Keep off the Grass'.", | ||||
| 	"I wondered why the baseball was getting bigger. Then it hit me.", | ||||
| 	"I can hear music coming out of my printer. I think the paper's jamming again.", | ||||
| 	"I have a few jokes about unemployed people, but none of them work", | ||||
| 	"Want to hear a construction joke? I'm working on it", | ||||
| 	"I always take a second pair of pants when I go golfing, in case I get a hole in one.", | ||||
| 	"I couldn't remember how to throw a boomerang, but then it came back to me.", | ||||
| 	"I've decided that my wifi will be my valentine. IDK, we just have this connection.", | ||||
| 	"A prisoner's favorite punctuation mark is the period. It marks the end of his sentence.", | ||||
| 	"I used to go fishing with Skrillex, but he kept dropping the bass.", | ||||
| 	"Two antennae met on a roof and got married. The wedding was okay, but the reception was incredible.", | ||||
| 	"A book just fell on my head. I've only got my shelf to blame.", | ||||
| 	"I dropped my steak on the floor. Now it's ground beef.", | ||||
| 	"I used to have a fear of hurdles, but I got over it.", | ||||
| 	"The outcome of war does not prove who is right, but only who is left.", | ||||
| 	"Darth Vader tries not to burn his food, but it always comes out a little on the dark side.", | ||||
| 	"The store keeps calling me to buy more furniture, but all I wanted was a one night stand.", | ||||
| 	"This girl said she recognized me from the vegetarian club, but I'd never met herbivore.", | ||||
| 	"Police arrested two kids yesterday, one was drinking battery acid, the other was eating fireworks. They charged one and let the other one off...", | ||||
| 	"No more Harry Potter jokes guys. I'm Sirius.", | ||||
| 	"It was hard getting over my addiction to hokey pokey, but I've turned myself around.", | ||||
| 	"It takes a lot of balls to golf the way I do.", | ||||
| 	"Why did everyone want to hang out with the mushroom? Because he was a fungi.", | ||||
| 	"How much does a hipster weigh? An instagram.", | ||||
| 	"I used to be addicted to soap, but I'm clean now.", | ||||
| 	"When life gives you melons, you’re probably dyslexic.", | ||||
| 	"What's with all the blind jokes? I just don't see the point.", | ||||
| 	"If Apple made a car, would it have Windows?", | ||||
| 	"Need an ark? I Noah guy.", | ||||
| 	"The scarecrow won an award because he was outstanding in his field.", | ||||
| 	"What's the difference between a man in a tux on a bicycle, and a man in a sweatsuit on a trycicle? A tire.", | ||||
| 	"What do you do with a sick chemist? If you can't helium, and you can't curium, you'll just have to barium.", | ||||
| 	"I'm reading a book about anti-gravity. It's impossible to put down.", | ||||
| 	"Trying to write with a broken pencil is pointless.", | ||||
| 	"When TVs go on vacation, they travel to remote islands.", | ||||
| 	"I was going to tell a midget joke, but it's too short.", | ||||
| 	"Jokes about German sausage are the wurst.", | ||||
| 	"How do you organize a space party? You planet.", | ||||
| 	"Sleeping comes so naturally to me, I could do it with my eyes closed.", | ||||
| 	"I'm glad I know sign language; it's pretty handy.", | ||||
| 	"Atheism is a non-prophet organization.", | ||||
| 	"Velcro: What a rip-off!", | ||||
| 	"If they made a Minecraft movie, it would be a blockbuster.", | ||||
| 	"I don't trust people with graph paper. They're always plotting something", | ||||
| 	"I had a friend who was addicted to brake fluid. He says he can stop anytime.", | ||||
| 	"The form said I had Type A blood, but it was a Type O.", | ||||
| 	"I went to to the shop to buy eight Sprites -  I came home and realised I'd picked 7Up.", | ||||
| 	"There was an explosion at a pie factory. 3.14 people died.", | ||||
| 	"A man drove his car into a tree and found out how a Mercedes bends.", | ||||
| 	"The experienced carpenter really nailed it, but the new guy screwed everything up.", | ||||
| 	"I didn't like my beard at first, but then it grew on me.", | ||||
| 	"Smaller babies may be delivered by stork, but the heavier ones need a crane.", | ||||
| 	"What's the definition of a will? It's a dead giveaway.", | ||||
| 	"I was going to look for my missing watch, but I could never find the time.", | ||||
| 	"I hate elevators, and I often take steps to avoid them.", | ||||
| 	"Did you hear about the guy whose whole left side was cut off? He's all right now.", | ||||
| 	"It's not that the man did not know how to juggle, he just didn't have the balls to do it.", | ||||
| 	"I used to be a loan shark, but I lost interest", | ||||
| 	"I don't trust these stairs; they're always up to something.", | ||||
| 	"My friend's bakery burned down last night. Now his business is toast.", | ||||
| 	"Don't trust people that do acupuncture; they're back stabbers.", | ||||
| 	"The man who survived mustard gas and pepper spray is now a seasoned veteran.", | ||||
| 	"Police were called to a daycare where a three-year-old was resisting a rest.", | ||||
| 	"When Peter Pan punches, they Neverland", | ||||
| 	"The shoemaker did not deny his apprentice anything he needed. He gave him his awl.", | ||||
| 	"I did a theatrical performance about puns. It was a play on words.", | ||||
| 	"Show me a piano falling down a mineshaft and I'll show you A-flat minor.", | ||||
| 	"Have you ever tried to eat a clock? It's very time consuming.", | ||||
| 	"There was once a cross-eyed teacher who couldn't control his pupils.", | ||||
| 	"A new type of broom came out and it is sweeping the nation.", | ||||
| 	"I relish the fact that you've mustard the strength to ketchup to me.", | ||||
| 	"I knew a woman who owned a taser. Man, was she stunning!", | ||||
| 	"What did the grape say when it got stepped on? Nothing - but it let out a little whine.", | ||||
| 	"It was an emotional wedding. Even the cake was in tiers.", | ||||
| 	"When a clock is hungry it goes back four seconds.", | ||||
| 	"The dead batteries were given out free of charge.", | ||||
| 	"Why are there no knock-knock jokes about America? Because freedom rings.", | ||||
| 	"When the cannibal showed up late to dinner, they gave him the cold shoulder.", | ||||
| 	"I should have been sad when my flashlight died, but I was delighted.", | ||||
| 	"Why don't tennis players ever get married? Love means nothing to them.", | ||||
| 	"Pterodactyls can't be heard going to the bathroom because the P is silent.", | ||||
| 	"Mermaids make calls on their shell phones.", | ||||
| 	"What do you call an aardvark with three feet? A yaardvark.", | ||||
| 	"Captain Kirk has three ears: A right ear, a left ear, and a final front ear.", | ||||
| 	"How do celebrities stay cool? They have a lot of fans.", | ||||
| 	"Without geometry, life is pointless.", | ||||
| 	"Did you hear about the cow who tried to jump over a barbed-wire fence? It ended in udder destruction.", | ||||
| 	"The truth may ring like a bell, but it is seldom ever tolled.", | ||||
| 	"I used to work for the IRS, but my job was too taxing.", | ||||
| 	"I used to be a programmer, but then I lost my drive.", | ||||
| 	"Pediatricians are doctors with little patients.", | ||||
| 	"I finally fired my masseuse today. She always rubbed me the wrong way.", | ||||
| 	"I stayed up all night wondering where the sun went. Then it dawned on me.", | ||||
| 	"What's the difference between a man and his dog? The man wears a suit; the dog just pants.", | ||||
| 	"A psychic midget who escapes from prison is a small medium at large.", | ||||
| 	"I've been to the dentist several times, so I know the drill.", | ||||
| 	"The roundest knight at King Arthur's round table was Sir Cumference. He acquired his size from too much pi.", | ||||
| 	"She was only a whiskey maker, but he loved her still.", | ||||
| 	"Male deer have buck teeth.", | ||||
| 	"Whiteboards are remarkable.", | ||||
| 	"Visitors in Cuba are always Havana good time.", | ||||
| 	"Why does electricity shock people? It doesn't know how to conduct itself.", | ||||
| 	"Lancelot had a scary dream about his horse. It was a knight mare.", | ||||
| 	"A tribe of cannibals captured a missionary and ate him. Afterward, they all had violent food poisoning. This just goes to show that you can't keep a good man down.", | ||||
| 	"Heaven for gamblers is a paradise.", | ||||
| 	"Old wheels aren't thrown away, they're just retired.", | ||||
| 	"Horses are very stable animals.", | ||||
| 	"Banks don't crash, they just lose their balance.", | ||||
| 	"The career of a skier can go downhill very fast.", | ||||
| 	"In democracy, it's your vote that counts. In feudalism, it's your count that votes.", | ||||
| 	"A sea lion is nothing but an ionized seal.", | ||||
| 	"The vegetables from my garden aren't that great. I guess you could say they're mediokra." | ||||
|     "The person who invented the door-knock won the No-bell prize.", | ||||
|     "I couldn't work out how to fasten my seatbelt. Then it clicked.", | ||||
|     "Never trust atoms; they make up everything.", | ||||
|     "Singing in the shower is all fun and games until you get shampoo in your mouth - Then it becomes a soap opera.", | ||||
|     "I can't believe I got fired from the calendar factory. All I did was take a day off.", | ||||
|     "To the guy who invented zero: Thanks for nothing!", | ||||
|     "Enough with the cripple jokes! I just can't stand them.", | ||||
|     "I've accidentally swallowed some Scrabble tiles. My next crap could spell disaster.", | ||||
|     "How does Moses make his tea? Hebrews it.", | ||||
|     "Did you hear about the guy who got hit in the head with a can of soda? He was lucky it was a soft drink.", | ||||
|     "When William joined the army he disliked the phrase 'fire at will'.", | ||||
|     "There was a sign on the lawn at a rehab center that said 'Keep off the Grass'.", | ||||
|     "I wondered why the baseball was getting bigger. Then it hit me.", | ||||
|     "I can hear music coming out of my printer. I think the paper's jamming again.", | ||||
|     "I have a few jokes about unemployed people, but none of them work", | ||||
|     "Want to hear a construction joke? I'm working on it", | ||||
|     "I always take a second pair of pants when I go golfing, in case I get a hole in one.", | ||||
|     "I couldn't remember how to throw a boomerang, but then it came back to me.", | ||||
|     "I've decided that my wifi will be my valentine. IDK, we just have this connection.", | ||||
|     "A prisoner's favorite punctuation mark is the period. It marks the end of his sentence.", | ||||
|     "I used to go fishing with Skrillex, but he kept dropping the bass.", | ||||
|     "Two antennae met on a roof and got married. The wedding was okay, but the reception was incredible.", | ||||
|     "A book just fell on my head. I've only got my shelf to blame.", | ||||
|     "I dropped my steak on the floor. Now it's ground beef.", | ||||
|     "I used to have a fear of hurdles, but I got over it.", | ||||
|     "The outcome of war does not prove who is right, but only who is left.", | ||||
|     "Darth Vader tries not to burn his food, but it always comes out a little on the dark side.", | ||||
|     "The store keeps calling me to buy more furniture, but all I wanted was a one night stand.", | ||||
|     "This girl said she recognized me from the vegetarian club, but I'd never met herbivore.", | ||||
|     "Police arrested two kids yesterday, one was drinking battery acid, the other was eating fireworks. They charged one and let the other one off...", | ||||
|     "No more Harry Potter jokes guys. I'm Sirius.", | ||||
|     "It was hard getting over my addiction to hokey pokey, but I've turned myself around.", | ||||
|     "It takes a lot of balls to golf the way I do.", | ||||
|     "Why did everyone want to hang out with the mushroom? Because he was a fungi.", | ||||
|     "How much does a hipster weigh? An instagram.", | ||||
|     "I used to be addicted to soap, but I'm clean now.", | ||||
|     "When life gives you melons, you’re probably dyslexic.", | ||||
|     "What's with all the blind jokes? I just don't see the point.", | ||||
|     "If Apple made a car, would it have Windows?", | ||||
|     "Need an ark? I Noah guy.", | ||||
|     "The scarecrow won an award because he was outstanding in his field.", | ||||
|     "What's the difference between a man in a tux on a bicycle, and a man in a sweatsuit on a trycicle? A tire.", | ||||
|     "What do you do with a sick chemist? If you can't helium, and you can't curium, you'll just have to barium.", | ||||
|     "I'm reading a book about anti-gravity. It's impossible to put down.", | ||||
|     "Trying to write with a broken pencil is pointless.", | ||||
|     "When TVs go on vacation, they travel to remote islands.", | ||||
|     "I was going to tell a midget joke, but it's too short.", | ||||
|     "Jokes about German sausage are the wurst.", | ||||
|     "How do you organize a space party? You planet.", | ||||
|     "Sleeping comes so naturally to me, I could do it with my eyes closed.", | ||||
|     "I'm glad I know sign language; it's pretty handy.", | ||||
|     "Atheism is a non-prophet organization.", | ||||
|     "Velcro: What a rip-off!", | ||||
|     "If they made a Minecraft movie, it would be a blockbuster.", | ||||
|     "I don't trust people with graph paper. They're always plotting something", | ||||
|     "I had a friend who was addicted to brake fluid. He says he can stop anytime.", | ||||
|     "The form said I had Type A blood, but it was a Type O.", | ||||
|     "I went to to the shop to buy eight Sprites -  I came home and realised I'd picked 7Up.", | ||||
|     "There was an explosion at a pie factory. 3.14 people died.", | ||||
|     "A man drove his car into a tree and found out how a Mercedes bends.", | ||||
|     "The experienced carpenter really nailed it, but the new guy screwed everything up.", | ||||
|     "I didn't like my beard at first, but then it grew on me.", | ||||
|     "Smaller babies may be delivered by stork, but the heavier ones need a crane.", | ||||
|     "What's the definition of a will? It's a dead giveaway.", | ||||
|     "I was going to look for my missing watch, but I could never find the time.", | ||||
|     "I hate elevators, and I often take steps to avoid them.", | ||||
|     "Did you hear about the guy whose whole left side was cut off? He's all right now.", | ||||
|     "It's not that the man did not know how to juggle, he just didn't have the balls to do it.", | ||||
|     "I used to be a loan shark, but I lost interest", | ||||
|     "I don't trust these stairs; they're always up to something.", | ||||
|     "My friend's bakery burned down last night. Now his business is toast.", | ||||
|     "Don't trust people that do acupuncture; they're back stabbers.", | ||||
|     "The man who survived mustard gas and pepper spray is now a seasoned veteran.", | ||||
|     "Police were called to a daycare where a three-year-old was resisting a rest.", | ||||
|     "When Peter Pan punches, they Neverland", | ||||
|     "The shoemaker did not deny his apprentice anything he needed. He gave him his awl.", | ||||
|     "I did a theatrical performance about puns. It was a play on words.", | ||||
|     "Show me a piano falling down a mineshaft and I'll show you A-flat minor.", | ||||
|     "Have you ever tried to eat a clock? It's very time consuming.", | ||||
|     "There was once a cross-eyed teacher who couldn't control his pupils.", | ||||
|     "A new type of broom came out and it is sweeping the nation.", | ||||
|     "I relish the fact that you've mustard the strength to ketchup to me.", | ||||
|     "I knew a woman who owned a taser. Man, was she stunning!", | ||||
|     "What did the grape say when it got stepped on? Nothing - but it let out a little whine.", | ||||
|     "It was an emotional wedding. Even the cake was in tiers.", | ||||
|     "When a clock is hungry it goes back four seconds.", | ||||
|     "The dead batteries were given out free of charge.", | ||||
|     "Why are there no knock-knock jokes about America? Because freedom rings.", | ||||
|     "When the cannibal showed up late to dinner, they gave him the cold shoulder.", | ||||
|     "I should have been sad when my flashlight died, but I was delighted.", | ||||
|     "Why don't tennis players ever get married? Love means nothing to them.", | ||||
|     "Pterodactyls can't be heard going to the bathroom because the P is silent.", | ||||
|     "Mermaids make calls on their shell phones.", | ||||
|     "What do you call an aardvark with three feet? A yaardvark.", | ||||
|     "Captain Kirk has three ears: A right ear, a left ear, and a final front ear.", | ||||
|     "How do celebrities stay cool? They have a lot of fans.", | ||||
|     "Without geometry, life is pointless.", | ||||
|     "Did you hear about the cow who tried to jump over a barbed-wire fence? It ended in udder destruction.", | ||||
|     "The truth may ring like a bell, but it is seldom ever tolled.", | ||||
|     "I used to work for the IRS, but my job was too taxing.", | ||||
|     "I used to be a programmer, but then I lost my drive.", | ||||
|     "Pediatricians are doctors with little patients.", | ||||
|     "I finally fired my masseuse today. She always rubbed me the wrong way.", | ||||
|     "I stayed up all night wondering where the sun went. Then it dawned on me.", | ||||
|     "What's the difference between a man and his dog? The man wears a suit; the dog just pants.", | ||||
|     "A psychic midget who escapes from prison is a small medium at large.", | ||||
|     "I've been to the dentist several times, so I know the drill.", | ||||
|     "The roundest knight at King Arthur's round table was Sir Cumference. He acquired his size from too much pi.", | ||||
|     "She was only a whiskey maker, but he loved her still.", | ||||
|     "Male deer have buck teeth.", | ||||
|     "Whiteboards are remarkable.", | ||||
|     "Visitors in Cuba are always Havana good time.", | ||||
|     "Why does electricity shock people? It doesn't know how to conduct itself.", | ||||
|     "Lancelot had a scary dream about his horse. It was a knight mare.", | ||||
|     "A tribe of cannibals captured a missionary and ate him. Afterward, they all had violent food poisoning. This just goes to show that you can't keep a good man down.", | ||||
|     "Heaven for gamblers is a paradise.", | ||||
|     "Old wheels aren't thrown away, they're just retired.", | ||||
|     "Horses are very stable animals.", | ||||
|     "Banks don't crash, they just lose their balance.", | ||||
|     "The career of a skier can go downhill very fast.", | ||||
|     "In democracy, it's your vote that counts. In feudalism, it's your count that votes.", | ||||
|     "A sea lion is nothing but an ionized seal.", | ||||
|     "The vegetables from my garden aren't that great. I guess you could say they're mediokra." | ||||
| } | ||||
|  | ||||
| function pun:action(msg) | ||||
|  | ||||
| 	utilities.send_reply(self, msg, puns[math.random(#puns)]) | ||||
|     utilities.send_reply(self, msg, puns[math.random(#puns)]) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -16,34 +16,34 @@ reactions.doc = 'Returns a list of "reaction" emoticon commands.' | ||||
| local help | ||||
|  | ||||
| function reactions:init(config) | ||||
| 	-- Generate a "help" message triggered by "/reactions". | ||||
| 	help = 'Reactions:\n' | ||||
| 	reactions.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('reactions').table | ||||
| 	local username = self.info.username:lower() | ||||
| 	for trigger,reaction in pairs(config.reactions) do | ||||
| 		help = help .. '• ' .. config.cmd_pat .. trigger .. ': ' .. reaction .. '\n' | ||||
| 		table.insert(reactions.triggers, '^'..config.cmd_pat..trigger) | ||||
| 		table.insert(reactions.triggers, '^'..config.cmd_pat..trigger..'@'..username) | ||||
| 		table.insert(reactions.triggers, config.cmd_pat..trigger..'$') | ||||
| 		table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'$') | ||||
| 		table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger) | ||||
| 		table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger..'@'..username) | ||||
| 		table.insert(reactions.triggers, config.cmd_pat..trigger..'\n') | ||||
| 		table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'\n') | ||||
| 	end | ||||
|     -- Generate a "help" message triggered by "/reactions". | ||||
|     help = 'Reactions:\n' | ||||
|     reactions.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('reactions').table | ||||
|     local username = self.info.username:lower() | ||||
|     for trigger,reaction in pairs(config.reactions) do | ||||
|         help = help .. '• ' .. config.cmd_pat .. trigger .. ': ' .. reaction .. '\n' | ||||
|         table.insert(reactions.triggers, '^'..config.cmd_pat..trigger) | ||||
|         table.insert(reactions.triggers, '^'..config.cmd_pat..trigger..'@'..username) | ||||
|         table.insert(reactions.triggers, config.cmd_pat..trigger..'$') | ||||
|         table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'$') | ||||
|         table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger) | ||||
|         table.insert(reactions.triggers, '\n'..config.cmd_pat..trigger..'@'..username) | ||||
|         table.insert(reactions.triggers, config.cmd_pat..trigger..'\n') | ||||
|         table.insert(reactions.triggers, config.cmd_pat..trigger..'@'..username..'\n') | ||||
|     end | ||||
| end | ||||
|  | ||||
| function reactions:action(msg, config) | ||||
| 	if string.match(msg.text_lower, config.cmd_pat..'reactions') then | ||||
| 		utilities.send_message(self, msg.chat.id, help) | ||||
| 		return | ||||
| 	end | ||||
| 	for trigger,reaction in pairs(config.reactions) do | ||||
| 		if string.match(msg.text_lower, config.cmd_pat..trigger) then | ||||
| 			utilities.send_message(self, msg.chat.id, reaction) | ||||
| 			return | ||||
| 		end | ||||
| 	end | ||||
|     if string.match(msg.text_lower, config.cmd_pat..'reactions') then | ||||
|         utilities.send_message(self, msg.chat.id, help) | ||||
|         return | ||||
|     end | ||||
|     for trigger,reaction in pairs(config.reactions) do | ||||
|         if string.match(msg.text_lower, config.cmd_pat..trigger) then | ||||
|             utilities.send_message(self, msg.chat.id, reaction) | ||||
|             return | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| return reactions | ||||
|   | ||||
| @@ -8,29 +8,29 @@ local utilities = require('otouto.utilities') | ||||
| reddit.command = 'reddit [r/subreddit | query]' | ||||
|  | ||||
| function reddit:init(config) | ||||
| 	reddit.triggers = utilities.triggers(self.info.username, config.cmd_pat, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table | ||||
| 	reddit.doc = config.cmd_pat .. [[reddit [r/subreddit | query] | ||||
|     reddit.triggers = utilities.triggers(self.info.username, config.cmd_pat, {'^/r/'}):t('reddit', true):t('r', true):t('r/', true).table | ||||
|     reddit.doc = config.cmd_pat .. [[reddit [r/subreddit | query] | ||||
| Returns the top posts or results for a given subreddit or query. If no argument is given, returns the top posts from r/all. Querying specific subreddits is not supported. | ||||
| Aliases: ]] .. config.cmd_pat .. 'r, /r/subreddit' | ||||
| end | ||||
|  | ||||
| local format_results = function(posts) | ||||
| 	local output = '' | ||||
| 	for _,v in ipairs(posts) do | ||||
| 		local post = v.data | ||||
| 		local title = post.title:gsub('%[', '('):gsub('%]', ')'):gsub('&', '&') | ||||
| 		if title:len() > 256 then | ||||
| 			title = title:sub(1, 253) | ||||
| 			title = utilities.trim(title) .. '...' | ||||
| 		end | ||||
| 		local short_url = 'redd.it/' .. post.id | ||||
| 		local s = '[' .. title .. '](' .. short_url .. ')' | ||||
| 		if post.domain and not post.is_self and not post.over_18 then | ||||
| 			s = '`[`[' .. post.domain .. '](' .. post.url:gsub('%)', '\\)') .. ')`]` ' .. s | ||||
| 		end | ||||
| 		output = output .. '• ' .. s .. '\n' | ||||
| 	end | ||||
| 	return output | ||||
|     local output = '' | ||||
|     for _,v in ipairs(posts) do | ||||
|         local post = v.data | ||||
|         local title = post.title:gsub('%[', '('):gsub('%]', ')'):gsub('&', '&') | ||||
|         if title:len() > 256 then | ||||
|             title = title:sub(1, 253) | ||||
|             title = utilities.trim(title) .. '...' | ||||
|         end | ||||
|         local short_url = 'redd.it/' .. post.id | ||||
|         local s = '[' .. title .. '](' .. short_url .. ')' | ||||
|         if post.domain and not post.is_self and not post.over_18 then | ||||
|             s = '`[`[' .. post.domain .. '](' .. post.url:gsub('%)', '\\)') .. ')`]` ' .. s | ||||
|         end | ||||
|         output = output .. '• ' .. s .. '\n' | ||||
|     end | ||||
|     return output | ||||
| end | ||||
|  | ||||
| reddit.subreddit_url = 'http://www.reddit.com/%s/.json?limit=' | ||||
| @@ -38,46 +38,46 @@ reddit.search_url = 'http://www.reddit.com/search.json?q=%s&limit=' | ||||
| reddit.rall_url = 'http://www.reddit.com/.json?limit=' | ||||
|  | ||||
| function reddit:action(msg, config) | ||||
| 	-- Eight results in PM, four results elsewhere. | ||||
| 	local limit = 4 | ||||
| 	if msg.chat.type == 'private' then | ||||
| 		limit = 8 | ||||
| 	end | ||||
| 	local text = msg.text_lower | ||||
| 	if text:match('^/r/.') then | ||||
| 		-- Normalize input so this hack works easily. | ||||
| 		text = msg.text_lower:gsub('^/r/', config.cmd_pat..'r r/') | ||||
| 	end | ||||
| 	local input = utilities.input(text) | ||||
| 	local source, url | ||||
| 	if input then | ||||
| 		if input:match('^r/.') then | ||||
| 			input = utilities.get_word(input, 1) | ||||
| 			url = reddit.subreddit_url:format(input) .. limit | ||||
| 			source = '*/' .. utilities.md_escape(input) .. '*\n' | ||||
| 		else | ||||
| 			input = utilities.input(msg.text) | ||||
| 			source = '*Results for* _' .. utilities.md_escape(input) .. '_ *:*\n' | ||||
| 			input = URL.escape(input) | ||||
| 			url = reddit.search_url:format(input) .. limit | ||||
| 		end | ||||
| 	else | ||||
| 		url = reddit.rall_url .. limit | ||||
| 		source = '*/r/all*\n' | ||||
| 	end | ||||
| 	local jstr, res = HTTP.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 	else | ||||
| 		local jdat = JSON.decode(jstr) | ||||
| 		if #jdat.data.children == 0 then | ||||
| 			utilities.send_reply(self, msg, config.errors.results) | ||||
| 		else | ||||
| 			local output = format_results(jdat.data.children) | ||||
| 			output = source .. output | ||||
| 			utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
| 		end | ||||
| 	end | ||||
|     -- Eight results in PM, four results elsewhere. | ||||
|     local limit = 4 | ||||
|     if msg.chat.type == 'private' then | ||||
|         limit = 8 | ||||
|     end | ||||
|     local text = msg.text_lower | ||||
|     if text:match('^/r/.') then | ||||
|         -- Normalize input so this hack works easily. | ||||
|         text = msg.text_lower:gsub('^/r/', config.cmd_pat..'r r/') | ||||
|     end | ||||
|     local input = utilities.input(text) | ||||
|     local source, url | ||||
|     if input then | ||||
|         if input:match('^r/.') then | ||||
|             input = utilities.get_word(input, 1) | ||||
|             url = reddit.subreddit_url:format(input) .. limit | ||||
|             source = '*/' .. utilities.md_escape(input) .. '*\n' | ||||
|         else | ||||
|             input = utilities.input(msg.text) | ||||
|             source = '*Results for* _' .. utilities.md_escape(input) .. '_ *:*\n' | ||||
|             input = URL.escape(input) | ||||
|             url = reddit.search_url:format(input) .. limit | ||||
|         end | ||||
|     else | ||||
|         url = reddit.rall_url .. limit | ||||
|         source = '*/r/all*\n' | ||||
|     end | ||||
|     local jstr, res = HTTP.request(url) | ||||
|     if res ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|     else | ||||
|         local jdat = JSON.decode(jstr) | ||||
|         if #jdat.data.children == 0 then | ||||
|             utilities.send_reply(self, msg, config.errors.results) | ||||
|         else | ||||
|             local output = format_results(jdat.data.children) | ||||
|             output = source .. output | ||||
|             utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| return reddit | ||||
|   | ||||
| @@ -5,87 +5,87 @@ local utilities = require('otouto.utilities') | ||||
| remind.command = 'remind <duration> <message>' | ||||
|  | ||||
| function remind:init(config) | ||||
| 	self.database.reminders = self.database.reminders or {} | ||||
|     self.database.reminders = self.database.reminders or {} | ||||
|  | ||||
| 	remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table | ||||
|     remind.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('remind', true).table | ||||
|  | ||||
| 	config.remind = config.remind or {} | ||||
| 	setmetatable(config.remind, { __index = function() return 1000 end }) | ||||
|     config.remind = config.remind or {} | ||||
|     setmetatable(config.remind, { __index = function() return 1000 end }) | ||||
|  | ||||
| 	remind.doc = config.cmd_pat .. [[remind <duration> <message> | ||||
|     remind.doc = config.cmd_pat .. [[remind <duration> <message> | ||||
| Repeats a message after a duration of time, in minutes. | ||||
| The maximum length of a reminder is %s characters. The maximum duration of a timer is %s minutes. The maximum number of reminders for a group is %s. The maximum number of reminders in private is %s.]] | ||||
| 	remind.doc = remind.doc:format(config.remind.max_length, config.remind.max_duration, config.remind.max_reminders_group, config.remind.max_reminders_private) | ||||
|     remind.doc = remind.doc:format(config.remind.max_length, config.remind.max_duration, config.remind.max_reminders_group, config.remind.max_reminders_private) | ||||
| end | ||||
|  | ||||
| function remind:action(msg, config) | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, remind.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input(msg.text) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, remind.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local duration = tonumber(utilities.get_word(input, 1)) | ||||
| 	if not duration then | ||||
| 		utilities.send_reply(self, msg, remind.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local duration = tonumber(utilities.get_word(input, 1)) | ||||
|     if not duration then | ||||
|         utilities.send_reply(self, msg, remind.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	if duration < 1 then | ||||
| 		duration = 1 | ||||
| 	elseif duration > config.remind.max_duration then | ||||
| 		duration = config.remind.max_duration | ||||
| 	end | ||||
| 	local message = utilities.input(input) | ||||
| 	if not message then | ||||
| 		utilities.send_reply(self, msg, remind.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     if duration < 1 then | ||||
|         duration = 1 | ||||
|     elseif duration > config.remind.max_duration then | ||||
|         duration = config.remind.max_duration | ||||
|     end | ||||
|     local message = utilities.input(input) | ||||
|     if not message then | ||||
|         utilities.send_reply(self, msg, remind.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	if #message > config.remind.max_length then | ||||
| 		utilities.send_reply(self, msg, 'The maximum length of reminders is ' .. config.remind.max_length .. '.') | ||||
| 		return | ||||
| 	end | ||||
|     if #message > config.remind.max_length then | ||||
|         utilities.send_reply(self, msg, 'The maximum length of reminders is ' .. config.remind.max_length .. '.') | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local chat_id_str = tostring(msg.chat.id) | ||||
| 	local output | ||||
| 	self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {} | ||||
| 	if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then | ||||
| 		output = 'Sorry, you already have the maximum number of reminders.' | ||||
| 	elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then | ||||
| 		output = 'Sorry, this group already has the maximum number of reminders.' | ||||
| 	else | ||||
| 		table.insert(self.database.reminders[chat_id_str], { | ||||
| 			time = os.time() + (duration * 60), | ||||
| 			message = message | ||||
| 		}) | ||||
| 		output = string.format( | ||||
| 			'I will remind you in %s minute%s!', | ||||
| 			duration, | ||||
| 			duration == 1 and '' or 's' | ||||
| 		) | ||||
| 	end | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|     local chat_id_str = tostring(msg.chat.id) | ||||
|     local output | ||||
|     self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {} | ||||
|     if msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_private then | ||||
|         output = 'Sorry, you already have the maximum number of reminders.' | ||||
|     elseif msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) >= config.remind.max_reminders_group then | ||||
|         output = 'Sorry, this group already has the maximum number of reminders.' | ||||
|     else | ||||
|         table.insert(self.database.reminders[chat_id_str], { | ||||
|             time = os.time() + (duration * 60), | ||||
|             message = message | ||||
|         }) | ||||
|         output = string.format( | ||||
|             'I will remind you in %s minute%s!', | ||||
|             duration, | ||||
|             duration == 1 and '' or 's' | ||||
|         ) | ||||
|     end | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
| end | ||||
|  | ||||
| function remind:cron(config) | ||||
| 	local time = os.time() | ||||
| 	-- Iterate over the group entries in the reminders database. | ||||
| 	for chat_id, group in pairs(self.database.reminders) do | ||||
| 		-- Iterate over each reminder. | ||||
| 		for k, reminder in pairs(group) do | ||||
| 			-- If the reminder is past-due, send it and nullify it. | ||||
| 			-- Otherwise, add it to the replacement table. | ||||
| 			if time > reminder.time then | ||||
| 				local output = utilities.style.enquote('Reminder', reminder.message) | ||||
| 				local res = utilities.send_message(self, chat_id, output, true, nil, true) | ||||
| 				-- If the message fails to send, save it for later (if enabled in config). | ||||
| 				if res or not config.remind.persist then | ||||
| 					group[k] = nil | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|     local time = os.time() | ||||
|     -- Iterate over the group entries in the reminders database. | ||||
|     for chat_id, group in pairs(self.database.reminders) do | ||||
|         -- Iterate over each reminder. | ||||
|         for k, reminder in pairs(group) do | ||||
|             -- If the reminder is past-due, send it and nullify it. | ||||
|             -- Otherwise, add it to the replacement table. | ||||
|             if time > reminder.time then | ||||
|                 local output = utilities.style.enquote('Reminder', reminder.message) | ||||
|                 local res = utilities.send_message(self, chat_id, output, true, nil, true) | ||||
|                 -- If the message fails to send, save it for later (if enabled in config). | ||||
|                 if res or not config.remind.persist then | ||||
|                     group[k] = nil | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| return remind | ||||
|   | ||||
| @@ -5,30 +5,30 @@ local bindings = require('otouto.bindings') | ||||
| local rms = {} | ||||
|  | ||||
| function rms:init(config) | ||||
| 	rms.BASE_URL = 'https://rms.sexy/img/' | ||||
| 	rms.LIST = {} | ||||
| 	local s, r = https.request(rms.BASE_URL) | ||||
| 	if r ~= 200 then | ||||
| 		print('Error connecting to rms.sexy.\nrmspic.lua will not be enabled.') | ||||
| 		return | ||||
| 	end | ||||
| 	for link in s:gmatch('<a href=".-%.%a%a%a">(.-)</a>') do | ||||
| 		table.insert(rms.LIST, link) | ||||
| 	end | ||||
| 	rms.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rms').table | ||||
|     rms.BASE_URL = 'https://rms.sexy/img/' | ||||
|     rms.LIST = {} | ||||
|     local s, r = https.request(rms.BASE_URL) | ||||
|     if r ~= 200 then | ||||
|         print('Error connecting to rms.sexy.\nrmspic.lua will not be enabled.') | ||||
|         return | ||||
|     end | ||||
|     for link in s:gmatch('<a href=".-%.%a%a%a">(.-)</a>') do | ||||
|         table.insert(rms.LIST, link) | ||||
|     end | ||||
|     rms.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('rms').table | ||||
| end | ||||
|  | ||||
| function rms:action(msg, config) | ||||
| 	bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' }) | ||||
| 	local choice = rms.LIST[math.random(#rms.LIST)] | ||||
| 	local filename = '/tmp/' .. choice | ||||
| 	local image_file = io.open(filename) | ||||
| 	if image_file then | ||||
| 		image_file:close() | ||||
| 	else | ||||
| 		utilities.download_file(rms.BASE_URL .. choice, filename) | ||||
| 	end | ||||
| 	bindings.sendPhoto(self, { chat_id = msg.chat.id }, { photo = filename }) | ||||
|     bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'upload_photo' }) | ||||
|     local choice = rms.LIST[math.random(#rms.LIST)] | ||||
|     local filename = '/tmp/' .. choice | ||||
|     local image_file = io.open(filename) | ||||
|     if image_file then | ||||
|         image_file:close() | ||||
|     else | ||||
|         utilities.download_file(rms.BASE_URL .. choice, filename) | ||||
|     end | ||||
|     bindings.sendPhoto(self, { chat_id = msg.chat.id }, { photo = filename }) | ||||
| end | ||||
|  | ||||
| return rms | ||||
|   | ||||
| @@ -3,9 +3,9 @@ local setandget = {} | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function setandget:init(config) | ||||
| 	self.database.setandget = self.database.setandget or {} | ||||
| 	setandget.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('set', true):t('get', true).table | ||||
| 	setandget.doc = config.cmd_pat .. [[set <name> <value> | ||||
|     self.database.setandget = self.database.setandget or {} | ||||
|     setandget.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('set', true):t('get', true).table | ||||
|     setandget.doc = config.cmd_pat .. [[set <name> <value> | ||||
| Stores a value with the given name. Use "]] .. config.cmd_pat .. [[set <name> --" to delete the stored value. | ||||
| ]] .. config.cmd_pat .. [[get [name] | ||||
| Returns the stored value or a list of stored values.]] | ||||
| @@ -15,56 +15,56 @@ setandget.command = 'set <name> <value>' | ||||
|  | ||||
| function setandget:action(msg, config) | ||||
|  | ||||
| 	local chat_id_str = tostring(msg.chat.id) | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	self.database.setandget[chat_id_str] = self.database.setandget[chat_id_str] or {} | ||||
|     local chat_id_str = tostring(msg.chat.id) | ||||
|     local input = utilities.input(msg.text) | ||||
|     self.database.setandget[chat_id_str] = self.database.setandget[chat_id_str] or {} | ||||
|  | ||||
| 	if msg.text_lower:match('^'..config.cmd_pat..'set') then | ||||
|     if msg.text_lower:match('^'..config.cmd_pat..'set') then | ||||
|  | ||||
| 		if not input then | ||||
| 			utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) | ||||
| 			return | ||||
| 		end | ||||
|         if not input then | ||||
|             utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) | ||||
|             return | ||||
|         end | ||||
|  | ||||
| 		local name = utilities.get_word(input:lower(), 1) | ||||
| 		local value = utilities.input(input) | ||||
|         local name = utilities.get_word(input:lower(), 1) | ||||
|         local value = utilities.input(input) | ||||
|  | ||||
| 		if not name or not value then | ||||
| 			utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) | ||||
| 		elseif value == '--' or value == '—' then | ||||
| 			self.database.setandget[chat_id_str][name] = nil | ||||
| 			utilities.send_message(self, msg.chat.id, 'That value has been deleted.') | ||||
| 		else | ||||
| 			self.database.setandget[chat_id_str][name] = value | ||||
| 			utilities.send_message(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true) | ||||
| 		end | ||||
|         if not name or not value then | ||||
|             utilities.send_message(self, msg.chat.id, setandget.doc, true, nil, true) | ||||
|         elseif value == '--' or value == '—' then | ||||
|             self.database.setandget[chat_id_str][name] = nil | ||||
|             utilities.send_message(self, msg.chat.id, 'That value has been deleted.') | ||||
|         else | ||||
|             self.database.setandget[chat_id_str][name] = value | ||||
|             utilities.send_message(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true) | ||||
|         end | ||||
|  | ||||
| 	elseif msg.text_lower:match('^'..config.cmd_pat..'get') then | ||||
|     elseif msg.text_lower:match('^'..config.cmd_pat..'get') then | ||||
|  | ||||
| 		if not input then | ||||
| 			local output | ||||
| 			if utilities.table_size(self.database.setandget[chat_id_str]) == 0 then | ||||
| 				output = 'No values have been stored here.' | ||||
| 			else | ||||
| 				output = '*List of stored values:*\n' | ||||
| 				for k,v in pairs(self.database.setandget[chat_id_str]) do | ||||
| 					output = output .. '• ' .. k .. ': `' .. v .. '`\n' | ||||
| 				end | ||||
| 			end | ||||
| 			utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
| 			return | ||||
| 		end | ||||
|         if not input then | ||||
|             local output | ||||
|             if utilities.table_size(self.database.setandget[chat_id_str]) == 0 then | ||||
|                 output = 'No values have been stored here.' | ||||
|             else | ||||
|                 output = '*List of stored values:*\n' | ||||
|                 for k,v in pairs(self.database.setandget[chat_id_str]) do | ||||
|                     output = output .. '• ' .. k .. ': `' .. v .. '`\n' | ||||
|                 end | ||||
|             end | ||||
|             utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|             return | ||||
|         end | ||||
|  | ||||
| 		local output | ||||
| 		if self.database.setandget[chat_id_str][input:lower()] then | ||||
| 			output = '`' .. self.database.setandget[chat_id_str][input:lower()] .. '`' | ||||
| 		else | ||||
| 			output = 'There is no value stored by that name.' | ||||
| 		end | ||||
|         local output | ||||
|         if self.database.setandget[chat_id_str][input:lower()] then | ||||
|             output = '`' .. self.database.setandget[chat_id_str][input:lower()] .. '`' | ||||
|         else | ||||
|             output = 'There is no value stored by that name.' | ||||
|         end | ||||
|  | ||||
| 		utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|         utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|  | ||||
| 	end | ||||
|     end | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -3,32 +3,32 @@ local shell = {} | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function shell:init(config) | ||||
| 	shell.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('run', true).table | ||||
|     shell.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('run', true).table | ||||
| end | ||||
|  | ||||
| function shell:action(msg, config) | ||||
|  | ||||
| 	if msg.from.id ~= config.admin then | ||||
| 		return | ||||
| 	end | ||||
|     if msg.from.id ~= config.admin then | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	input = input:gsub('—', '--') | ||||
|     local input = utilities.input(msg.text) | ||||
|     input = input:gsub('—', '--') | ||||
|  | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, 'Please specify a command to run.') | ||||
| 		return | ||||
| 	end | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, 'Please specify a command to run.') | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local f = io.popen(input) | ||||
| 	local output = f:read('*all') | ||||
| 	f:close() | ||||
| 	if output:len() == 0 then | ||||
| 		output = 'Done!' | ||||
| 	else | ||||
| 		output = '```\n' .. output .. '\n```' | ||||
| 	end | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) | ||||
|     local f = io.popen(input) | ||||
|     local output = f:read('*all') | ||||
|     f:close() | ||||
|     if output:len() == 0 then | ||||
|         output = 'Done!' | ||||
|     else | ||||
|         output = '```\n' .. output .. '\n```' | ||||
|     end | ||||
|     utilities.send_message(self, msg.chat.id, output, true, msg.message_id, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -6,45 +6,45 @@ shout.command = 'shout <text>' | ||||
| local utf8 = '('..utilities.char.utf_8..'*)' | ||||
|  | ||||
| function shout:init(config) | ||||
| 	shout.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('shout', true).table | ||||
| 	shout.doc = config.cmd_pat .. 'shout <text> \nShouts something. Input may be the replied-to message.' | ||||
|     shout.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('shout', true).table | ||||
|     shout.doc = config.cmd_pat .. 'shout <text> \nShouts something. Input may be the replied-to message.' | ||||
| end | ||||
|  | ||||
| function shout:action(msg) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, shout.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, shout.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	input = utilities.trim(input) | ||||
| 	input = input:upper() | ||||
|     input = utilities.trim(input) | ||||
|     input = input:upper() | ||||
|  | ||||
| 	local output = '' | ||||
| 	local inc = 0 | ||||
| 	local ilen = 0 | ||||
| 	for match in input:gmatch(utf8) do | ||||
| 		if ilen < 20 then | ||||
| 			ilen = ilen + 1 | ||||
| 			output = output .. match .. ' ' | ||||
| 		end | ||||
| 	end | ||||
| 	ilen = 0 | ||||
| 	output = output .. '\n' | ||||
| 	for match in input:sub(2):gmatch(utf8) do | ||||
| 		if ilen < 19 then | ||||
| 			local spacing = '' | ||||
| 			for _ = 1, inc do | ||||
| 				spacing = spacing .. '  ' | ||||
| 			end | ||||
| 			inc = inc + 1 | ||||
| 			ilen = ilen + 1 | ||||
| 			output = output .. match .. ' ' .. spacing .. match .. '\n' | ||||
| 		end | ||||
| 	end | ||||
| 	output = '```\n' .. utilities.trim(output) .. '\n```' | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, false, true) | ||||
|     local output = '' | ||||
|     local inc = 0 | ||||
|     local ilen = 0 | ||||
|     for match in input:gmatch(utf8) do | ||||
|         if ilen < 20 then | ||||
|             ilen = ilen + 1 | ||||
|             output = output .. match .. ' ' | ||||
|         end | ||||
|     end | ||||
|     ilen = 0 | ||||
|     output = output .. '\n' | ||||
|     for match in input:sub(2):gmatch(utf8) do | ||||
|         if ilen < 19 then | ||||
|             local spacing = '' | ||||
|             for _ = 1, inc do | ||||
|                 spacing = spacing .. '  ' | ||||
|             end | ||||
|             inc = inc + 1 | ||||
|             ilen = ilen + 1 | ||||
|             output = output .. match .. ' ' .. spacing .. match .. '\n' | ||||
|         end | ||||
|     end | ||||
|     output = '```\n' .. utilities.trim(output) .. '\n```' | ||||
|     utilities.send_message(self, msg.chat.id, output, true, false, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -5,160 +5,160 @@ local utilities = require('otouto.utilities') | ||||
| slap.command = 'slap [target]' | ||||
|  | ||||
| function slap:init(config) | ||||
| 	slap.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('slap', true).table | ||||
| 	slap.doc = config.cmd_pat .. 'slap [target] \nSlap somebody.' | ||||
|     slap.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('slap', true).table | ||||
|     slap.doc = config.cmd_pat .. 'slap [target] \nSlap somebody.' | ||||
| end | ||||
|  | ||||
| local slaps = { | ||||
| 	'VICTIM was shot by VICTOR.', | ||||
| 	'VICTIM was pricked to death.', | ||||
| 	'VICTIM walked into a cactus while trying to escape VICTOR.', | ||||
| 	'VICTIM drowned.', | ||||
| 	'VICTIM drowned whilst trying to escape VICTOR.', | ||||
| 	'VICTIM blew up.', | ||||
| 	'VICTIM was blown up by VICTOR.', | ||||
| 	'VICTIM hit the ground too hard.', | ||||
| 	'VICTIM fell from a high place.', | ||||
| 	'VICTIM fell off a ladder.', | ||||
| 	'VICTIM fell into a patch of cacti.', | ||||
| 	'VICTIM was doomed to fall by VICTOR.', | ||||
| 	'VICTIM was blown from a high place by VICTOR.', | ||||
| 	'VICTIM was squashed by a falling anvil.', | ||||
| 	'VICTIM went up in flames.', | ||||
| 	'VICTIM burned to death.', | ||||
| 	'VICTIM was burnt to a crisp whilst fighting VICTOR.', | ||||
| 	'VICTIM walked into a fire whilst fighting VICTOR.', | ||||
| 	'VICTIM tried to swim in lava.', | ||||
| 	'VICTIM tried to swim in lava while trying to escape VICTOR.', | ||||
| 	'VICTIM was struck by lightning.', | ||||
| 	'VICTIM was slain by VICTOR.', | ||||
| 	'VICTIM got finished off by VICTOR.', | ||||
| 	'VICTIM was killed by magic.', | ||||
| 	'VICTIM was killed by VICTOR using magic.', | ||||
| 	'VICTIM starved to death.', | ||||
| 	'VICTIM suffocated in a wall.', | ||||
| 	'VICTIM fell out of the world.', | ||||
| 	'VICTIM was knocked into the void by VICTOR.', | ||||
| 	'VICTIM withered away.', | ||||
| 	'VICTIM was pummeled by VICTOR.', | ||||
| 	'VICTIM was fragged by VICTOR.', | ||||
| 	'VICTIM was desynchronized.', | ||||
| 	'VICTIM was wasted.', | ||||
| 	'VICTIM was busted.', | ||||
| 	'VICTIM\'s bones are scraped clean by the desolate wind.', | ||||
| 	'VICTIM has died of dysentery.', | ||||
| 	'VICTIM fainted.', | ||||
| 	'VICTIM is out of usable Pokemon! VICTIM whited out!', | ||||
| 	'VICTIM is out of usable Pokemon! VICTIM blacked out!', | ||||
| 	'VICTIM whited out!', | ||||
| 	'VICTIM blacked out!', | ||||
| 	'VICTIM says goodbye to this cruel world.', | ||||
| 	'VICTIM got rekt.', | ||||
| 	'VICTIM was sawn in half by VICTOR.', | ||||
| 	'VICTIM died. I blame VICTOR.', | ||||
| 	'VICTIM was axe-murdered by VICTOR.', | ||||
| 	'VICTIM\'s melon was split by VICTOR.', | ||||
| 	'VICTIM was sliced and diced by VICTOR.', | ||||
| 	'VICTIM was split from crotch to sternum by VICTOR.', | ||||
| 	'VICTIM\'s death put another notch in VICTOR\'s axe.', | ||||
| 	'VICTIM died impossibly!', | ||||
| 	'VICTIM died from VICTOR\'s mysterious tropical disease.', | ||||
| 	'VICTIM escaped infection by dying.', | ||||
| 	'VICTIM played hot-potato with a grenade.', | ||||
| 	'VICTIM was knifed by VICTOR.', | ||||
| 	'VICTIM fell on his sword.', | ||||
| 	'VICTIM ate a grenade.', | ||||
| 	'VICTIM practiced being VICTOR\'s clay pigeon.', | ||||
| 	'VICTIM is what\'s for dinner!', | ||||
| 	'VICTIM was terminated by VICTOR.', | ||||
| 	'VICTIM was shot before being thrown out of a plane.', | ||||
| 	'VICTIM was not invincible.', | ||||
| 	'VICTIM has encountered an error.', | ||||
| 	'VICTIM died and reincarnated as a goat.', | ||||
| 	'VICTOR threw VICTIM off a building.', | ||||
| 	'VICTIM is sleeping with the fishes.', | ||||
| 	'VICTIM got a premature burial.', | ||||
| 	'VICTOR replaced all of VICTIM\'s music with Nickelback.', | ||||
| 	'VICTOR spammed VICTIM\'s email.', | ||||
| 	'VICTOR made VICTIM a knuckle sandwich.', | ||||
| 	'VICTOR slapped VICTIM with pure nothing.', | ||||
| 	'VICTOR hit VICTIM with a small, interstellar spaceship.', | ||||
| 	'VICTIM was quickscoped by VICTOR.', | ||||
| 	'VICTOR put VICTIM in check-mate.', | ||||
| 	'VICTOR RSA-encrypted VICTIM and deleted the private key.', | ||||
| 	'VICTOR put VICTIM in the friendzone.', | ||||
| 	'VICTOR slaps VICTIM with a DMCA takedown request!', | ||||
| 	'VICTIM became a corpse blanket for VICTOR.', | ||||
| 	'Death is when the monsters get you. Death comes for VICTIM.', | ||||
| 	'Cowards die many times before their death. VICTIM never tasted death but once.', | ||||
| 	'VICTIM died of hospital gangrene.', | ||||
| 	'VICTIM got a house call from Doctor VICTOR.', | ||||
| 	'VICTOR beheaded VICTIM.', | ||||
| 	'VICTIM got stoned...by an angry mob.', | ||||
| 	'VICTOR sued the pants off VICTIM.', | ||||
| 	'VICTIM was impeached.', | ||||
| 	'VICTIM was one-hit KO\'d by VICTOR.', | ||||
| 	'VICTOR sent VICTIM to /dev/null.', | ||||
| 	'VICTOR sent VICTIM down the memory hole.', | ||||
| 	'VICTIM was a mistake.', | ||||
| 	'"VICTIM was a mistake." - VICTOR', | ||||
| 	'VICTOR checkmated VICTIM in two moves.' | ||||
|     'VICTIM was shot by VICTOR.', | ||||
|     'VICTIM was pricked to death.', | ||||
|     'VICTIM walked into a cactus while trying to escape VICTOR.', | ||||
|     'VICTIM drowned.', | ||||
|     'VICTIM drowned whilst trying to escape VICTOR.', | ||||
|     'VICTIM blew up.', | ||||
|     'VICTIM was blown up by VICTOR.', | ||||
|     'VICTIM hit the ground too hard.', | ||||
|     'VICTIM fell from a high place.', | ||||
|     'VICTIM fell off a ladder.', | ||||
|     'VICTIM fell into a patch of cacti.', | ||||
|     'VICTIM was doomed to fall by VICTOR.', | ||||
|     'VICTIM was blown from a high place by VICTOR.', | ||||
|     'VICTIM was squashed by a falling anvil.', | ||||
|     'VICTIM went up in flames.', | ||||
|     'VICTIM burned to death.', | ||||
|     'VICTIM was burnt to a crisp whilst fighting VICTOR.', | ||||
|     'VICTIM walked into a fire whilst fighting VICTOR.', | ||||
|     'VICTIM tried to swim in lava.', | ||||
|     'VICTIM tried to swim in lava while trying to escape VICTOR.', | ||||
|     'VICTIM was struck by lightning.', | ||||
|     'VICTIM was slain by VICTOR.', | ||||
|     'VICTIM got finished off by VICTOR.', | ||||
|     'VICTIM was killed by magic.', | ||||
|     'VICTIM was killed by VICTOR using magic.', | ||||
|     'VICTIM starved to death.', | ||||
|     'VICTIM suffocated in a wall.', | ||||
|     'VICTIM fell out of the world.', | ||||
|     'VICTIM was knocked into the void by VICTOR.', | ||||
|     'VICTIM withered away.', | ||||
|     'VICTIM was pummeled by VICTOR.', | ||||
|     'VICTIM was fragged by VICTOR.', | ||||
|     'VICTIM was desynchronized.', | ||||
|     'VICTIM was wasted.', | ||||
|     'VICTIM was busted.', | ||||
|     'VICTIM\'s bones are scraped clean by the desolate wind.', | ||||
|     'VICTIM has died of dysentery.', | ||||
|     'VICTIM fainted.', | ||||
|     'VICTIM is out of usable Pokemon! VICTIM whited out!', | ||||
|     'VICTIM is out of usable Pokemon! VICTIM blacked out!', | ||||
|     'VICTIM whited out!', | ||||
|     'VICTIM blacked out!', | ||||
|     'VICTIM says goodbye to this cruel world.', | ||||
|     'VICTIM got rekt.', | ||||
|     'VICTIM was sawn in half by VICTOR.', | ||||
|     'VICTIM died. I blame VICTOR.', | ||||
|     'VICTIM was axe-murdered by VICTOR.', | ||||
|     'VICTIM\'s melon was split by VICTOR.', | ||||
|     'VICTIM was sliced and diced by VICTOR.', | ||||
|     'VICTIM was split from crotch to sternum by VICTOR.', | ||||
|     'VICTIM\'s death put another notch in VICTOR\'s axe.', | ||||
|     'VICTIM died impossibly!', | ||||
|     'VICTIM died from VICTOR\'s mysterious tropical disease.', | ||||
|     'VICTIM escaped infection by dying.', | ||||
|     'VICTIM played hot-potato with a grenade.', | ||||
|     'VICTIM was knifed by VICTOR.', | ||||
|     'VICTIM fell on his sword.', | ||||
|     'VICTIM ate a grenade.', | ||||
|     'VICTIM practiced being VICTOR\'s clay pigeon.', | ||||
|     'VICTIM is what\'s for dinner!', | ||||
|     'VICTIM was terminated by VICTOR.', | ||||
|     'VICTIM was shot before being thrown out of a plane.', | ||||
|     'VICTIM was not invincible.', | ||||
|     'VICTIM has encountered an error.', | ||||
|     'VICTIM died and reincarnated as a goat.', | ||||
|     'VICTOR threw VICTIM off a building.', | ||||
|     'VICTIM is sleeping with the fishes.', | ||||
|     'VICTIM got a premature burial.', | ||||
|     'VICTOR replaced all of VICTIM\'s music with Nickelback.', | ||||
|     'VICTOR spammed VICTIM\'s email.', | ||||
|     'VICTOR made VICTIM a knuckle sandwich.', | ||||
|     'VICTOR slapped VICTIM with pure nothing.', | ||||
|     'VICTOR hit VICTIM with a small, interstellar spaceship.', | ||||
|     'VICTIM was quickscoped by VICTOR.', | ||||
|     'VICTOR put VICTIM in check-mate.', | ||||
|     'VICTOR RSA-encrypted VICTIM and deleted the private key.', | ||||
|     'VICTOR put VICTIM in the friendzone.', | ||||
|     'VICTOR slaps VICTIM with a DMCA takedown request!', | ||||
|     'VICTIM became a corpse blanket for VICTOR.', | ||||
|     'Death is when the monsters get you. Death comes for VICTIM.', | ||||
|     'Cowards die many times before their death. VICTIM never tasted death but once.', | ||||
|     'VICTIM died of hospital gangrene.', | ||||
|     'VICTIM got a house call from Doctor VICTOR.', | ||||
|     'VICTOR beheaded VICTIM.', | ||||
|     'VICTIM got stoned...by an angry mob.', | ||||
|     'VICTOR sued the pants off VICTIM.', | ||||
|     'VICTIM was impeached.', | ||||
|     'VICTIM was one-hit KO\'d by VICTOR.', | ||||
|     'VICTOR sent VICTIM to /dev/null.', | ||||
|     'VICTOR sent VICTIM down the memory hole.', | ||||
|     'VICTIM was a mistake.', | ||||
|     '"VICTIM was a mistake." - VICTOR', | ||||
|     'VICTOR checkmated VICTIM in two moves.' | ||||
| } | ||||
|  | ||||
|  -- optimize later | ||||
| function slap:action(msg) | ||||
| 	local input = utilities.input(msg.text) | ||||
| 	local victor_id = msg.from.id | ||||
| 	local victim_id | ||||
| 	if msg.reply_to_message then | ||||
| 		victim_id = msg.reply_to_message.from.id | ||||
| 	else | ||||
| 		if input then | ||||
| 			if tonumber(input) then | ||||
| 				victim_id = tonumber(input) | ||||
| 			elseif input:match('^@') then | ||||
| 				local t = utilities.resolve_username(self, input) | ||||
| 				if t then | ||||
| 					victim_id = t.id | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	-- IDs | ||||
| 	if victim_id then | ||||
| 		if victim_id == victor_id then | ||||
| 			victor_id = self.info.id | ||||
| 		end | ||||
| 	else | ||||
| 		if not input then | ||||
| 			victor_id = self.info.id | ||||
| 			victim_id = msg.from.id | ||||
| 		end | ||||
| 	end | ||||
| 	-- Names | ||||
| 	local victor_name, victim_name | ||||
| 	if input and not victim_id then | ||||
| 		victim_name = input | ||||
| 	else | ||||
| 		local victim_id_str = tostring(victim_id) | ||||
| 		if self.database.userdata[victim_id_str] and self.database.userdata[victim_id_str].nickname then | ||||
| 			victim_name = self.database.userdata[victim_id_str].nickname | ||||
| 		elseif self.database.users[victim_id_str] then | ||||
| 			victim_name = utilities.build_name(self.database.users[victim_id_str].first_name, self.database.users[victim_id_str].last_name) | ||||
| 		else | ||||
| 			victim_name = victim_id_str | ||||
| 		end | ||||
| 	end | ||||
| 	local victor_id_str = tostring(victor_id) | ||||
| 	if self.database.userdata[victor_id_str] and self.database.userdata[victor_id_str].nickname then | ||||
| 		victor_name = self.database.userdata[victor_id_str].nickname | ||||
| 	elseif self.database.users[victor_id_str] then | ||||
| 		victor_name = utilities.build_name(self.database.users[victor_id_str].first_name, self.database.users[victor_id_str].last_name) | ||||
| 	else | ||||
| 		victor_name = self.info.first_name | ||||
| 	end | ||||
| 	local output = utilities.char.zwnj .. slaps[math.random(#slaps)]:gsub('VICTIM', victim_name):gsub('VICTOR', victor_name) | ||||
| 	utilities.send_message(self, msg.chat.id, output) | ||||
|     local input = utilities.input(msg.text) | ||||
|     local victor_id = msg.from.id | ||||
|     local victim_id | ||||
|     if msg.reply_to_message then | ||||
|         victim_id = msg.reply_to_message.from.id | ||||
|     else | ||||
|         if input then | ||||
|             if tonumber(input) then | ||||
|                 victim_id = tonumber(input) | ||||
|             elseif input:match('^@') then | ||||
|                 local t = utilities.resolve_username(self, input) | ||||
|                 if t then | ||||
|                     victim_id = t.id | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|     end | ||||
|     -- IDs | ||||
|     if victim_id then | ||||
|         if victim_id == victor_id then | ||||
|             victor_id = self.info.id | ||||
|         end | ||||
|     else | ||||
|         if not input then | ||||
|             victor_id = self.info.id | ||||
|             victim_id = msg.from.id | ||||
|         end | ||||
|     end | ||||
|     -- Names | ||||
|     local victor_name, victim_name | ||||
|     if input and not victim_id then | ||||
|         victim_name = input | ||||
|     else | ||||
|         local victim_id_str = tostring(victim_id) | ||||
|         if self.database.userdata[victim_id_str] and self.database.userdata[victim_id_str].nickname then | ||||
|             victim_name = self.database.userdata[victim_id_str].nickname | ||||
|         elseif self.database.users[victim_id_str] then | ||||
|             victim_name = utilities.build_name(self.database.users[victim_id_str].first_name, self.database.users[victim_id_str].last_name) | ||||
|         else | ||||
|             victim_name = victim_id_str | ||||
|         end | ||||
|     end | ||||
|     local victor_id_str = tostring(victor_id) | ||||
|     if self.database.userdata[victor_id_str] and self.database.userdata[victor_id_str].nickname then | ||||
|         victor_name = self.database.userdata[victor_id_str].nickname | ||||
|     elseif self.database.users[victor_id_str] then | ||||
|         victor_name = utilities.build_name(self.database.users[victor_id_str].first_name, self.database.users[victor_id_str].last_name) | ||||
|     else | ||||
|         victor_name = self.info.first_name | ||||
|     end | ||||
|     local output = utilities.char.zwnj .. slaps[math.random(#slaps)]:gsub('VICTIM', victim_name):gsub('VICTOR', victor_name) | ||||
|     utilities.send_message(self, msg.chat.id, output) | ||||
| end | ||||
|  | ||||
| return slap | ||||
|   | ||||
| @@ -8,71 +8,71 @@ local utilities = require('otouto.utilities') | ||||
| local starwars = {} | ||||
|  | ||||
| function starwars:init(config) | ||||
| 	starwars.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('starwars', true):t('sw', true).table | ||||
| 	starwars.doc = config.cmd_pat .. [[starwars <query> | ||||
|     starwars.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('starwars', true):t('sw', true).table | ||||
|     starwars.doc = config.cmd_pat .. [[starwars <query> | ||||
| Returns the opening crawl from the specified Star Wars film. | ||||
| Alias: ]] .. config.cmd_pat .. 'sw' | ||||
| 	starwars.command = 'starwars <query>' | ||||
| 	starwars.base_url = 'http://swapi.co/api/films/' | ||||
|     starwars.command = 'starwars <query>' | ||||
|     starwars.base_url = 'http://swapi.co/api/films/' | ||||
| end | ||||
|  | ||||
| local films_by_number = { | ||||
| 	['phantom menace'] = 4, | ||||
| 	['attack of the clones'] = 5, | ||||
| 	['revenge of the sith'] = 6, | ||||
| 	['new hope'] = 1, | ||||
| 	['empire strikes back'] = 2, | ||||
| 	['return of the jedi'] = 3, | ||||
| 	['force awakens'] = 7 | ||||
|     ['phantom menace'] = 4, | ||||
|     ['attack of the clones'] = 5, | ||||
|     ['revenge of the sith'] = 6, | ||||
|     ['new hope'] = 1, | ||||
|     ['empire strikes back'] = 2, | ||||
|     ['return of the jedi'] = 3, | ||||
|     ['force awakens'] = 7 | ||||
| } | ||||
|  | ||||
| local corrected_numbers = { | ||||
| 	4, | ||||
| 	5, | ||||
| 	6, | ||||
| 	1, | ||||
| 	2, | ||||
| 	3, | ||||
| 	7 | ||||
|     4, | ||||
|     5, | ||||
|     6, | ||||
|     1, | ||||
|     2, | ||||
|     3, | ||||
|     7 | ||||
| } | ||||
|  | ||||
| function starwars:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, starwars.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, starwars.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } ) | ||||
|     bindings.sendChatAction(self, { chat_id = msg.chat.id, action = 'typing' } ) | ||||
|  | ||||
| 	local film | ||||
| 	if tonumber(input) then | ||||
| 		input = tonumber(input) | ||||
| 		film = corrected_numbers[input] or input | ||||
| 	else | ||||
| 		for title, number in pairs(films_by_number) do | ||||
| 			if string.match(input, title) then | ||||
| 				film = number | ||||
| 				break | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|     local film | ||||
|     if tonumber(input) then | ||||
|         input = tonumber(input) | ||||
|         film = corrected_numbers[input] or input | ||||
|     else | ||||
|         for title, number in pairs(films_by_number) do | ||||
|             if string.match(input, title) then | ||||
|                 film = number | ||||
|                 break | ||||
|             end | ||||
|         end | ||||
|     end | ||||
|  | ||||
| 	if not film then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     if not film then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = starwars.base_url .. film | ||||
| 	local jstr, code = HTTP.request(url) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local url = starwars.base_url .. film | ||||
|     local jstr, code = HTTP.request(url) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local output = '*' .. JSON.decode(jstr).opening_crawl .. '*' | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
|     local output = '*' .. JSON.decode(jstr).opening_crawl .. '*' | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, true) | ||||
| end | ||||
|  | ||||
| return starwars | ||||
|   | ||||
| @@ -8,52 +8,52 @@ time.command = 'time <location>' | ||||
| time.base_url = 'https://maps.googleapis.com/maps/api/timezone/json?location=%s,%s×tamp=%s' | ||||
|  | ||||
| function time:init(config) | ||||
| 	time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table | ||||
| 	time.doc = config.cmd_pat .. [[time <location> | ||||
|     time.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('time', true).table | ||||
|     time.doc = config.cmd_pat .. [[time <location> | ||||
| Returns the time, date, and timezone for the given location.]] | ||||
| end | ||||
|  | ||||
| function time:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, time.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, time.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local coords = utilities.get_coords(input, config) | ||||
| 	if type(coords) == 'string' then | ||||
| 		utilities.send_reply(self, msg, coords) | ||||
| 		return | ||||
| 	end | ||||
|     local coords = utilities.get_coords(input, config) | ||||
|     if type(coords) == 'string' then | ||||
|         utilities.send_reply(self, msg, coords) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local now = os.time() | ||||
| 	local utc = os.time(os.date('!*t', now)) | ||||
| 	local url = time.base_url:format(coords.lat, coords.lon, utc) | ||||
| 	local jstr, code = HTTPS.request(url) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local now = os.time() | ||||
|     local utc = os.time(os.date('!*t', now)) | ||||
|     local url = time.base_url:format(coords.lat, coords.lon, utc) | ||||
|     local jstr, code = HTTPS.request(url) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	if data.status == 'ZERO_RESULTS' then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     local data = JSON.decode(jstr) | ||||
|     if data.status == 'ZERO_RESULTS' then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local timestamp = now + data.rawOffset + data.dstOffset | ||||
| 	local utcoff = (data.rawOffset + data.dstOffset) / 3600 | ||||
| 	if utcoff == math.abs(utcoff) then | ||||
| 		utcoff = '+' .. utilities.pretty_float(utcoff) | ||||
| 	else | ||||
| 		utcoff = utilities.pretty_float(utcoff) | ||||
| 	end | ||||
| 	local output = string.format('```\n%s\n%s (UTC%s)\n```', | ||||
| 		os.date('!%I:%M %p\n%A, %B %d, %Y', timestamp), | ||||
| 		data.timeZoneName, | ||||
| 		utcoff | ||||
| 	) | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|     local timestamp = now + data.rawOffset + data.dstOffset | ||||
|     local utcoff = (data.rawOffset + data.dstOffset) / 3600 | ||||
|     if utcoff == math.abs(utcoff) then | ||||
|         utcoff = '+' .. utilities.pretty_float(utcoff) | ||||
|     else | ||||
|         utcoff = utilities.pretty_float(utcoff) | ||||
|     end | ||||
|     local output = string.format('```\n%s\n%s (UTC%s)\n```', | ||||
|         os.date('!%I:%M %p\n%A, %B %d, %Y', timestamp), | ||||
|         data.timeZoneName, | ||||
|         utcoff | ||||
|     ) | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
| end | ||||
|  | ||||
| return time | ||||
|   | ||||
| @@ -8,38 +8,38 @@ local utilities = require('otouto.utilities') | ||||
| translate.command = 'translate [text]' | ||||
|  | ||||
| function translate:init(config) | ||||
| 	assert(config.yandex_key, | ||||
| 		'translate.lua requires a Yandex translate API key from http://tech.yandex.com/keys/get.' | ||||
| 	) | ||||
|     assert(config.yandex_key, | ||||
|         'translate.lua requires a Yandex translate API key from http://tech.yandex.com/keys/get.' | ||||
|     ) | ||||
|  | ||||
| 	translate.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('translate', true):t('tl', true).table | ||||
| 	translate.doc = config.cmd_pat .. [[translate [text] | ||||
|     translate.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('translate', true):t('tl', true).table | ||||
|     translate.doc = config.cmd_pat .. [[translate [text] | ||||
| Translates input or the replied-to message into the bot's language.]] | ||||
| 	translate.base_url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. config.yandex_key .. '&lang=' .. config.lang .. '&text=%s' | ||||
|     translate.base_url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' .. config.yandex_key .. '&lang=' .. config.lang .. '&text=%s' | ||||
| end | ||||
|  | ||||
| function translate:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, translate.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, translate.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = translate.base_url:format(URL.escape(input)) | ||||
| 	local jstr, code = HTTPS.request(url) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local url = translate.base_url:format(URL.escape(input)) | ||||
|     local jstr, code = HTTPS.request(url) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	if data.code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local data = JSON.decode(jstr) | ||||
|     if data.code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	utilities.send_reply(self, msg.reply_to_message or msg, utilities.style.enquote('Translation', data.text[1]), true) | ||||
|     utilities.send_reply(self, msg.reply_to_message or msg, utilities.style.enquote('Translation', data.text[1]), true) | ||||
| end | ||||
|  | ||||
| return translate | ||||
|   | ||||
| @@ -9,42 +9,42 @@ urbandictionary.command = 'urbandictionary <query>' | ||||
| urbandictionary.base_url = 'http://api.urbandictionary.com/v0/define?term=' | ||||
|  | ||||
| function urbandictionary:init(config) | ||||
| 	urbandictionary.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
| 		:t('urbandictionary', true):t('ud', true):t('urban', true).table | ||||
| 	urbandictionary.doc = [[ | ||||
|     urbandictionary.triggers = utilities.triggers(self.info.username, config.cmd_pat) | ||||
|         :t('urbandictionary', true):t('ud', true):t('urban', true).table | ||||
|     urbandictionary.doc = [[ | ||||
| /urbandictionary <query> | ||||
| Returns a definition from Urban Dictionary. | ||||
| Aliases: /ud, /urban | ||||
| 	]] | ||||
| 	urbandictionary.doc = urbandictionary.doc:gsub('/', config.cmd_pat) | ||||
|     ]] | ||||
|     urbandictionary.doc = urbandictionary.doc:gsub('/', config.cmd_pat) | ||||
| end | ||||
|  | ||||
| function urbandictionary:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, urbandictionary.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, urbandictionary.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = urbandictionary.base_url .. URL.escape(input) | ||||
| 	local jstr, code = HTTP.request(url) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local url = urbandictionary.base_url .. URL.escape(input) | ||||
|     local jstr, code = HTTP.request(url) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	local output | ||||
| 	if data.result_type == 'no_results' then | ||||
| 		output = config.errors.results | ||||
| 	else | ||||
| 		output = string.format('*%s*\n\n%s\n\n_%s_', | ||||
| 			data.list[1].word:gsub('*', '*\\**'), | ||||
| 			utilities.trim(utilities.md_escape(data.list[1].definition)), | ||||
| 			utilities.trim((data.list[1].example or '')):gsub('_', '_\\__') | ||||
| 		) | ||||
| 	end | ||||
| 	utilities.send_reply(self, msg, output, true) | ||||
|     local data = JSON.decode(jstr) | ||||
|     local output | ||||
|     if data.result_type == 'no_results' then | ||||
|         output = config.errors.results | ||||
|     else | ||||
|         output = string.format('*%s*\n\n%s\n\n_%s_', | ||||
|             data.list[1].word:gsub('*', '*\\**'), | ||||
|             utilities.trim(utilities.md_escape(data.list[1].definition)), | ||||
|             utilities.trim((data.list[1].example or '')):gsub('_', '_\\__') | ||||
|         ) | ||||
|     end | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
| end | ||||
|  | ||||
| return urbandictionary | ||||
|   | ||||
| @@ -6,12 +6,12 @@ local JSON = require('dkjson') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function weather:init(config) | ||||
| 	assert(config.owm_api_key, | ||||
| 		'weather.lua requires an OpenWeatherMap API key from http://openweathermap.org/API.' | ||||
| 	) | ||||
|     assert(config.owm_api_key, | ||||
|         'weather.lua requires an OpenWeatherMap API key from http://openweathermap.org/API.' | ||||
|     ) | ||||
|  | ||||
| 	weather.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('weather', true).table | ||||
| 	weather.doc = config.cmd_pat .. [[weather <location> | ||||
|     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.]] | ||||
| end | ||||
|  | ||||
| @@ -19,37 +19,37 @@ weather.command = 'weather <location>' | ||||
|  | ||||
| function weather:action(msg, config) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, weather.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, weather.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local coords = utilities.get_coords(input, config) | ||||
| 	if type(coords) == 'string' then | ||||
| 		utilities.send_reply(self, msg, coords) | ||||
| 		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 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 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 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 .. '.`' | ||||
|     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) | ||||
|     utilities.send_reply(self, msg, output, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -6,54 +6,54 @@ local bindings = require('otouto.bindings') | ||||
| whoami.command = 'whoami' | ||||
|  | ||||
| function whoami:init(config) | ||||
| 	whoami.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('who'):t('whoami').table | ||||
| 	whoami.doc = [[ | ||||
|     whoami.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('who'):t('whoami').table | ||||
|     whoami.doc = [[ | ||||
| Returns user and chat info for you or the replied-to message. | ||||
| Alias: ]] .. config.cmd_pat .. 'who' | ||||
| end | ||||
|  | ||||
| function whoami:action(msg) | ||||
| 	-- Operate on the replied-to message, if it exists. | ||||
| 	msg = msg.reply_to_message or msg | ||||
| 	-- If it's a private conversation, bot is chat, unless bot is from. | ||||
| 	local chat = msg.from.id == msg.chat.id and self.info or msg.chat | ||||
| 	-- Names for the user and group, respectively. HTML-escaped. | ||||
| 	local from_name = utilities.html_escape( | ||||
| 		utilities.build_name( | ||||
| 			msg.from.first_name, | ||||
| 			msg.from.last_name | ||||
| 		) | ||||
| 	) | ||||
| 	local chat_name = utilities.html_escape( | ||||
| 		chat.title | ||||
| 		or utilities.build_name(chat.first_name, chat.last_name) | ||||
| 	) | ||||
| 	-- "Normalize" a group ID so it's not arbitrarily modified by the bot API. | ||||
| 	local chat_id = math.abs(chat.id) | ||||
| 	if chat_id > 1000000000000 then chat_id = chat_id - 1000000000000 end | ||||
| 	-- Do the thing. | ||||
| 	local output = string.format( | ||||
| 		'You are %s <code>[%s]</code>, and you are messaging %s <code>[%s]</code>.', | ||||
| 		msg.from.username and string.format( | ||||
| 			'@%s, also known as <b>%s</b>', | ||||
| 			msg.from.username, | ||||
| 			from_name | ||||
| 		) or '<b>' .. from_name .. '</b>', | ||||
| 		msg.from.id, | ||||
| 		msg.chat.username and string.format( | ||||
| 			'@%s, also known as <b>%s</b>', | ||||
| 			chat.username, | ||||
| 			chat_name | ||||
| 		) or '<b>' .. chat_name .. '</b>', | ||||
| 		chat_id | ||||
| 	) | ||||
| 	bindings.sendMessage(self, { | ||||
| 		chat_id = msg.chat.id, | ||||
| 		reply_to_message_id = msg.message_id, | ||||
| 		disable_web_page_preview = true, | ||||
| 		parse_mode = 'HTML', | ||||
| 		text = output | ||||
| 	}) | ||||
|     -- Operate on the replied-to message, if it exists. | ||||
|     msg = msg.reply_to_message or msg | ||||
|     -- If it's a private conversation, bot is chat, unless bot is from. | ||||
|     local chat = msg.from.id == msg.chat.id and self.info or msg.chat | ||||
|     -- Names for the user and group, respectively. HTML-escaped. | ||||
|     local from_name = utilities.html_escape( | ||||
|         utilities.build_name( | ||||
|             msg.from.first_name, | ||||
|             msg.from.last_name | ||||
|         ) | ||||
|     ) | ||||
|     local chat_name = utilities.html_escape( | ||||
|         chat.title | ||||
|         or utilities.build_name(chat.first_name, chat.last_name) | ||||
|     ) | ||||
|     -- "Normalize" a group ID so it's not arbitrarily modified by the bot API. | ||||
|     local chat_id = math.abs(chat.id) | ||||
|     if chat_id > 1000000000000 then chat_id = chat_id - 1000000000000 end | ||||
|     -- Do the thing. | ||||
|     local output = string.format( | ||||
|         'You are %s <code>[%s]</code>, and you are messaging %s <code>[%s]</code>.', | ||||
|         msg.from.username and string.format( | ||||
|             '@%s, also known as <b>%s</b>', | ||||
|             msg.from.username, | ||||
|             from_name | ||||
|         ) or '<b>' .. from_name .. '</b>', | ||||
|         msg.from.id, | ||||
|         msg.chat.username and string.format( | ||||
|             '@%s, also known as <b>%s</b>', | ||||
|             chat.username, | ||||
|             chat_name | ||||
|         ) or '<b>' .. chat_name .. '</b>', | ||||
|         chat_id | ||||
|     ) | ||||
|     bindings.sendMessage(self, { | ||||
|         chat_id = msg.chat.id, | ||||
|         reply_to_message_id = msg.message_id, | ||||
|         disable_web_page_preview = true, | ||||
|         parse_mode = 'HTML', | ||||
|         text = output | ||||
|     }) | ||||
| end | ||||
|  | ||||
| return whoami | ||||
|   | ||||
| @@ -8,83 +8,83 @@ local utilities = require('otouto.utilities') | ||||
| wikipedia.command = 'wikipedia <query>' | ||||
|  | ||||
| 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.doc = config.cmd_pat .. [[wikipedia <query> | ||||
|     wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true):t('w', true).table | ||||
|     wikipedia.doc = config.cmd_pat .. [[wikipedia <query> | ||||
| Returns an article from Wikipedia. | ||||
| Aliases: ]] .. config.cmd_pat .. 'w, ' .. config.cmd_pat .. 'wiki' | ||||
| 	wikipedia.search_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch=' | ||||
| 	wikipedia.res_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles=' | ||||
| 	wikipedia.art_url = 'https://' .. config.lang .. '.wikipedia.org/wiki/' | ||||
|     wikipedia.search_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch=' | ||||
|     wikipedia.res_url = 'https://' .. config.lang .. '.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exchars=4000&exsectionformat=plain&titles=' | ||||
|     wikipedia.art_url = 'https://' .. config.lang .. '.wikipedia.org/wiki/' | ||||
| end | ||||
|  | ||||
| function wikipedia:action(msg, config) | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, wikipedia.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, wikipedia.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local jstr, code = HTTPS.request(wikipedia.search_url .. URL.escape(input)) | ||||
| 	if code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local jstr, code = HTTPS.request(wikipedia.search_url .. URL.escape(input)) | ||||
|     if code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local data = JSON.decode(jstr) | ||||
| 	if data.query.searchinfo.totalhits == 0 then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     local data = JSON.decode(jstr) | ||||
|     if data.query.searchinfo.totalhits == 0 then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local title | ||||
| 	for _, v in ipairs(data.query.search) do | ||||
| 		if not v.snippet:match('may refer to:') then | ||||
| 			title = v.title | ||||
| 			break | ||||
| 		end | ||||
| 	end | ||||
| 	if not title then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     local title | ||||
|     for _, v in ipairs(data.query.search) do | ||||
|         if not v.snippet:match('may refer to:') then | ||||
|             title = v.title | ||||
|             break | ||||
|         end | ||||
|     end | ||||
|     if not title then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local res_jstr, res_code = HTTPS.request(wikipedia.res_url .. URL.escape(title)) | ||||
| 	if res_code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local res_jstr, res_code = HTTPS.request(wikipedia.res_url .. URL.escape(title)) | ||||
|     if res_code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local _, text = next(JSON.decode(res_jstr).query.pages) | ||||
| 	if not text then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     local _, text = next(JSON.decode(res_jstr).query.pages) | ||||
|     if not text then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	text = text.extract | ||||
| 	-- Remove crap and take only the first paragraph. | ||||
| 	text = text:gsub('</?.->', ''):gsub('%[.+%]', '') | ||||
| 	local l = text:find('\n') | ||||
| 	if l then | ||||
| 		text = text:sub(1, l-1) | ||||
| 	end | ||||
| 	local url = wikipedia.art_url .. URL.escape(title) | ||||
| 	title = utilities.html_escape(title) | ||||
| 	-- If the beginning of the article is the title, embolden that. | ||||
| 	-- Otherwise, we'll add a title in bold. | ||||
| 	local short_title = title:gsub('%(.+%)', '') | ||||
| 	local combined_text, count = text:gsub('^'..short_title, '<b>'..short_title..'</b>') | ||||
| 	local body | ||||
| 	if count == 1 then | ||||
| 		body = combined_text | ||||
| 	else | ||||
| 		body = '<b>' .. title .. '</b>\n' .. text | ||||
| 	end | ||||
| 	local output = string.format( | ||||
| 		'%s\n<a href="%s">Read more.</a>', | ||||
| 		body, | ||||
| 		utilities.html_escape(url) | ||||
| 	) | ||||
| 	utilities.send_message(self, msg.chat.id, output, true, nil, 'html') | ||||
|     text = text.extract | ||||
|     -- Remove crap and take only the first paragraph. | ||||
|     text = text:gsub('</?.->', ''):gsub('%[.+%]', '') | ||||
|     local l = text:find('\n') | ||||
|     if l then | ||||
|         text = text:sub(1, l-1) | ||||
|     end | ||||
|     local url = wikipedia.art_url .. URL.escape(title) | ||||
|     title = utilities.html_escape(title) | ||||
|     -- If the beginning of the article is the title, embolden that. | ||||
|     -- Otherwise, we'll add a title in bold. | ||||
|     local short_title = title:gsub('%(.+%)', '') | ||||
|     local combined_text, count = text:gsub('^'..short_title, '<b>'..short_title..'</b>') | ||||
|     local body | ||||
|     if count == 1 then | ||||
|         body = combined_text | ||||
|     else | ||||
|         body = '<b>' .. title .. '</b>\n' .. text | ||||
|     end | ||||
|     local output = string.format( | ||||
|         '%s\n<a href="%s">Read more.</a>', | ||||
|         body, | ||||
|         utilities.html_escape(url) | ||||
|     ) | ||||
|     utilities.send_message(self, msg.chat.id, output, true, nil, 'html') | ||||
| end | ||||
|  | ||||
| return wikipedia | ||||
|   | ||||
| @@ -9,44 +9,44 @@ xkcd.base_url = 'https://xkcd.com/info.0.json' | ||||
| xkcd.strip_url = 'http://xkcd.com/%s/info.0.json' | ||||
|  | ||||
| function xkcd:init(config) | ||||
| 	xkcd.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('xkcd', true).table | ||||
| 	xkcd.doc = config.cmd_pat .. [[xkcd [i] | ||||
|     xkcd.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('xkcd', true).table | ||||
|     xkcd.doc = config.cmd_pat .. [[xkcd [i] | ||||
| Returns the latest xkcd strip and its alt text. If a number is given, returns that number strip. If "r" is passed in place of a number, returns a random strip.]] | ||||
| 	local jstr = HTTP.request(xkcd.base_url) | ||||
| 	if jstr then | ||||
| 		local data = JSON.decode(jstr) | ||||
| 		if data then | ||||
| 			xkcd.latest = data.num | ||||
| 		end | ||||
| 	end | ||||
| 	xkcd.latest = xkcd.latest or 1700 | ||||
|     local jstr = HTTP.request(xkcd.base_url) | ||||
|     if jstr then | ||||
|         local data = JSON.decode(jstr) | ||||
|         if data then | ||||
|             xkcd.latest = data.num | ||||
|         end | ||||
|     end | ||||
|     xkcd.latest = xkcd.latest or 1700 | ||||
| end | ||||
|  | ||||
| function xkcd:action(msg, config) | ||||
| 	local input = utilities.get_word(msg.text, 2) | ||||
| 	if input == 'r' then | ||||
| 		input = math.random(xkcd.latest) | ||||
| 	elseif tonumber(input) then | ||||
| 		input = tonumber(input) | ||||
| 	else | ||||
| 		input = xkcd.latest | ||||
| 	end | ||||
| 	local url = xkcd.strip_url:format(input) | ||||
| 	local jstr, code = HTTP.request(url) | ||||
| 	if code == 404 then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 	elseif code ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 	else | ||||
| 		local data = JSON.decode(jstr) | ||||
| 		local output = string.format('*%s (*[%s](%s)*)*\n_%s_', | ||||
| 			data.safe_title:gsub('*', '*\\**'), | ||||
| 			data.num, | ||||
| 			data.img, | ||||
| 			data.alt:gsub('_', '_\\__') | ||||
| 		) | ||||
| 		utilities.send_message(self, msg.chat.id, output, false, nil, true) | ||||
| 	end | ||||
|     local input = utilities.get_word(msg.text, 2) | ||||
|     if input == 'r' then | ||||
|         input = math.random(xkcd.latest) | ||||
|     elseif tonumber(input) then | ||||
|         input = tonumber(input) | ||||
|     else | ||||
|         input = xkcd.latest | ||||
|     end | ||||
|     local url = xkcd.strip_url:format(input) | ||||
|     local jstr, code = HTTP.request(url) | ||||
|     if code == 404 then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|     elseif code ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|     else | ||||
|         local data = JSON.decode(jstr) | ||||
|         local output = string.format('*%s (*[%s](%s)*)*\n_%s_', | ||||
|             data.safe_title:gsub('*', '*\\**'), | ||||
|             data.num, | ||||
|             data.img, | ||||
|             data.alt:gsub('_', '_\\__') | ||||
|         ) | ||||
|         utilities.send_message(self, msg.chat.id, output, false, nil, true) | ||||
|     end | ||||
| end | ||||
|  | ||||
| return xkcd | ||||
|   | ||||
| @@ -8,12 +8,12 @@ local JSON = require('dkjson') | ||||
| local utilities = require('otouto.utilities') | ||||
|  | ||||
| function youtube:init(config) | ||||
| 	assert(config.google_api_key, | ||||
| 		'youtube.lua requires a Google API key from http://console.developers.google.com.' | ||||
| 	) | ||||
|     assert(config.google_api_key, | ||||
|         'youtube.lua requires a Google API key from http://console.developers.google.com.' | ||||
|     ) | ||||
|  | ||||
| 	youtube.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('youtube', true):t('yt', true).table | ||||
| 	youtube.doc = config.cmd_pat .. [[youtube <query> | ||||
|     youtube.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('youtube', true):t('yt', true).table | ||||
|     youtube.doc = config.cmd_pat .. [[youtube <query> | ||||
| Returns the top result from YouTube. | ||||
| Alias: ]] .. config.cmd_pat .. 'yt' | ||||
| end | ||||
| @@ -22,32 +22,32 @@ youtube.command = 'youtube <query>' | ||||
|  | ||||
| function youtube:action(msg, config) | ||||
|  | ||||
| 	local input = utilities.input_from_msg(msg) | ||||
| 	if not input then | ||||
| 		utilities.send_reply(self, msg, youtube.doc, true) | ||||
| 		return | ||||
| 	end | ||||
|     local input = utilities.input_from_msg(msg) | ||||
|     if not input then | ||||
|         utilities.send_reply(self, msg, youtube.doc, true) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local url = 'https://www.googleapis.com/youtube/v3/search?key=' .. config.google_api_key .. '&type=video&part=snippet&maxResults=4&q=' .. URL.escape(input) | ||||
|     local url = 'https://www.googleapis.com/youtube/v3/search?key=' .. config.google_api_key .. '&type=video&part=snippet&maxResults=4&q=' .. URL.escape(input) | ||||
|  | ||||
| 	local jstr, res = HTTPS.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		utilities.send_reply(self, msg, config.errors.connection) | ||||
| 		return | ||||
| 	end | ||||
|     local jstr, res = HTTPS.request(url) | ||||
|     if res ~= 200 then | ||||
|         utilities.send_reply(self, msg, config.errors.connection) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local jdat = JSON.decode(jstr) | ||||
| 	if jdat.pageInfo.totalResults == 0 then | ||||
| 		utilities.send_reply(self, msg, config.errors.results) | ||||
| 		return | ||||
| 	end | ||||
|     local jdat = JSON.decode(jstr) | ||||
|     if jdat.pageInfo.totalResults == 0 then | ||||
|         utilities.send_reply(self, msg, config.errors.results) | ||||
|         return | ||||
|     end | ||||
|  | ||||
| 	local vid_url = 'https://www.youtube.com/watch?v=' .. jdat.items[1].id.videoId | ||||
| 	local vid_title = jdat.items[1].snippet.title | ||||
| 	vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','') | ||||
| 	local output = '[' .. vid_title .. '](' .. vid_url .. ')' | ||||
|     local vid_url = 'https://www.youtube.com/watch?v=' .. jdat.items[1].id.videoId | ||||
|     local vid_title = jdat.items[1].snippet.title | ||||
|     vid_title = vid_title:gsub('%(.+%)',''):gsub('%[.+%]','') | ||||
|     local output = '[' .. vid_title .. '](' .. vid_url .. ')' | ||||
|  | ||||
| 	utilities.send_message(self, msg.chat.id, output, false, nil, true) | ||||
|     utilities.send_message(self, msg.chat.id, output, false, nil, true) | ||||
|  | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -15,47 +15,47 @@ local bindings = require('otouto.bindings') | ||||
|  -- Edit: To keep things working and allow for HTML messages, you can now pass a | ||||
|  -- string for use_markdown and that will be sent as the parse mode. | ||||
| function utilities:send_message(chat_id, text, disable_web_page_preview, reply_to_message_id, use_markdown) | ||||
| 	local parse_mode | ||||
| 	if type(use_markdown) == 'string' then | ||||
| 		parse_mode = use_markdown | ||||
| 	elseif use_markdown == true then | ||||
| 		parse_mode = 'markdown' | ||||
| 	end | ||||
| 	return bindings.request(self, 'sendMessage', { | ||||
| 		chat_id = chat_id, | ||||
| 		text = text, | ||||
| 		disable_web_page_preview = disable_web_page_preview, | ||||
| 		reply_to_message_id = reply_to_message_id, | ||||
| 		parse_mode = parse_mode | ||||
| 	} ) | ||||
|     local parse_mode | ||||
|     if type(use_markdown) == 'string' then | ||||
|         parse_mode = use_markdown | ||||
|     elseif use_markdown == true then | ||||
|         parse_mode = 'markdown' | ||||
|     end | ||||
|     return bindings.request(self, 'sendMessage', { | ||||
|         chat_id = chat_id, | ||||
|         text = text, | ||||
|         disable_web_page_preview = disable_web_page_preview, | ||||
|         reply_to_message_id = reply_to_message_id, | ||||
|         parse_mode = parse_mode | ||||
|     } ) | ||||
| end | ||||
|  | ||||
| function utilities:send_reply(old_msg, text, use_markdown) | ||||
| 	return utilities.send_message(self, old_msg.chat.id, text, true, old_msg.message_id, use_markdown) | ||||
|     return utilities.send_message(self, old_msg.chat.id, text, true, old_msg.message_id, use_markdown) | ||||
| end | ||||
|  | ||||
|  -- get the indexed word in a string | ||||
| function utilities.get_word(s, i) | ||||
| 	s = s or '' | ||||
| 	i = i or 1 | ||||
| 	local n = 0 | ||||
| 	for w in s:gmatch('%g+') do | ||||
| 		n = n + 1 | ||||
| 		if n == i then return w end | ||||
| 	end | ||||
| 	return false | ||||
|     s = s or '' | ||||
|     i = i or 1 | ||||
|     local n = 0 | ||||
|     for w in s:gmatch('%g+') do | ||||
|         n = n + 1 | ||||
|         if n == i then return w end | ||||
|     end | ||||
|     return false | ||||
| end | ||||
|  | ||||
|  -- Returns the string after the first space. | ||||
| function utilities.input(s) | ||||
| 	if not s:find(' ') then | ||||
| 		return false | ||||
| 	end | ||||
| 	return s:sub(s:find(' ')+1) | ||||
|     if not s:find(' ') then | ||||
|         return false | ||||
|     end | ||||
|     return s:sub(s:find(' ')+1) | ||||
| end | ||||
|  | ||||
| function utilities.input_from_msg(msg) | ||||
| 	return utilities.input(msg.text) or (msg.reply_to_message and #msg.reply_to_message.text > 0 and msg.reply_to_message.text) or false | ||||
|     return utilities.input(msg.text) or (msg.reply_to_message and #msg.reply_to_message.text > 0 and msg.reply_to_message.text) or false | ||||
| end | ||||
|  | ||||
| -- Calculates the length of the given string as UTF-8 characters | ||||
| @@ -72,180 +72,180 @@ end | ||||
|  | ||||
|  -- Trims whitespace from a string. | ||||
| function utilities.trim(str) | ||||
| 	local s = str:gsub('^%s*(.-)%s*$', '%1') | ||||
| 	return s | ||||
|     local s = str:gsub('^%s*(.-)%s*$', '%1') | ||||
|     return s | ||||
| end | ||||
|  | ||||
|  -- Loads a JSON file as a table. | ||||
| function utilities.load_data(filename) | ||||
| 	local f = io.open(filename) | ||||
| 	if f then | ||||
| 		local s = f:read('*all') | ||||
| 		f:close() | ||||
| 		return JSON.decode(s) | ||||
| 	else | ||||
| 		return {} | ||||
| 	end | ||||
|     local f = io.open(filename) | ||||
|     if f then | ||||
|         local s = f:read('*all') | ||||
|         f:close() | ||||
|         return JSON.decode(s) | ||||
|     else | ||||
|         return {} | ||||
|     end | ||||
| end | ||||
|  | ||||
|  -- Saves a table to a JSON file. | ||||
| function utilities.save_data(filename, data) | ||||
| 	local s = JSON.encode(data) | ||||
| 	local f = io.open(filename, 'w') | ||||
| 	f:write(s) | ||||
| 	f:close() | ||||
|     local s = JSON.encode(data) | ||||
|     local f = io.open(filename, 'w') | ||||
|     f:write(s) | ||||
|     f:close() | ||||
| 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 = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. URL.escape(input) | ||||
|  | ||||
| 	local jstr, res = HTTP.request(url) | ||||
| 	if res ~= 200 then | ||||
| 		return config.errors.connection | ||||
| 	end | ||||
|     local jstr, res = HTTP.request(url) | ||||
|     if res ~= 200 then | ||||
|         return config.errors.connection | ||||
|     end | ||||
|  | ||||
| 	local jdat = JSON.decode(jstr) | ||||
| 	if jdat.status == 'ZERO_RESULTS' then | ||||
| 		return config.errors.results | ||||
| 	end | ||||
|     local jdat = JSON.decode(jstr) | ||||
|     if jdat.status == 'ZERO_RESULTS' then | ||||
|         return config.errors.results | ||||
|     end | ||||
|  | ||||
| 	return { | ||||
| 		lat = jdat.results[1].geometry.location.lat, | ||||
| 		lon = jdat.results[1].geometry.location.lng | ||||
| 	} | ||||
|     return { | ||||
|         lat = jdat.results[1].geometry.location.lat, | ||||
|         lon = jdat.results[1].geometry.location.lng | ||||
|     } | ||||
|  | ||||
| end | ||||
|  | ||||
|  -- Get the number of values in a key/value table. | ||||
| function utilities.table_size(tab) | ||||
| 	local i = 0 | ||||
| 	for _,_ in pairs(tab) do | ||||
| 		i = i + 1 | ||||
| 	end | ||||
| 	return i | ||||
|     local i = 0 | ||||
|     for _,_ in pairs(tab) do | ||||
|         i = i + 1 | ||||
|     end | ||||
|     return i | ||||
| end | ||||
|  | ||||
|  -- Just an easy way to get a user's full name. | ||||
|  -- Alternatively, abuse it to concat two strings like I do. | ||||
| function utilities.build_name(first, last) | ||||
| 	if last then | ||||
| 		return first .. ' ' .. last | ||||
| 	else | ||||
| 		return first | ||||
| 	end | ||||
|     if last then | ||||
|         return first .. ' ' .. last | ||||
|     else | ||||
|         return first | ||||
|     end | ||||
| end | ||||
|  | ||||
| function utilities:resolve_username(input) | ||||
| 	input = input:gsub('^@', '') | ||||
| 	for _, user in pairs(self.database.users) do | ||||
| 		if user.username and user.username:lower() == input:lower() then | ||||
| 			local t = {} | ||||
| 			for key, val in pairs(user) do | ||||
| 				t[key] = val | ||||
| 			end | ||||
| 			return t | ||||
| 		end | ||||
| 	end | ||||
|     input = input:gsub('^@', '') | ||||
|     for _, user in pairs(self.database.users) do | ||||
|         if user.username and user.username:lower() == input:lower() then | ||||
|             local t = {} | ||||
|             for key, val in pairs(user) do | ||||
|                 t[key] = val | ||||
|             end | ||||
|             return t | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| function utilities:handle_exception(err, message, config) | ||||
| 	local output = string.format( | ||||
| 		'\n[%s]\n%s: %s\n%s\n', | ||||
| 		os.date('%F %T'), | ||||
| 		self.info.username, | ||||
| 		err or '', | ||||
| 		message | ||||
| 	) | ||||
| 	if config.log_chat then | ||||
| 		output = '```' .. output .. '```' | ||||
| 		utilities.send_message(self, config.log_chat, output, true, nil, true) | ||||
| 	else | ||||
| 		print(output) | ||||
| 	end | ||||
|     local output = string.format( | ||||
|         '\n[%s]\n%s: %s\n%s\n', | ||||
|         os.date('%F %T'), | ||||
|         self.info.username, | ||||
|         err or '', | ||||
|         message | ||||
|     ) | ||||
|     if config.log_chat then | ||||
|         output = '```' .. output .. '```' | ||||
|         utilities.send_message(self, config.log_chat, output, true, nil, true) | ||||
|     else | ||||
|         print(output) | ||||
|     end | ||||
|  | ||||
| end | ||||
|  | ||||
| function utilities.download_file(url, filename) | ||||
| 	if not filename then | ||||
| 		filename = url:match('.+/(.-)$') or os.time() | ||||
| 		filename = '/tmp/' .. filename | ||||
| 	end | ||||
| 	local body = {} | ||||
| 	local doer = HTTP | ||||
| 	local do_redir = true | ||||
| 	if url:match('^https') then | ||||
| 		doer = HTTPS | ||||
| 		do_redir = false | ||||
| 	end | ||||
| 	local _, res = doer.request{ | ||||
| 		url = url, | ||||
| 		sink = ltn12.sink.table(body), | ||||
| 		redirect = do_redir | ||||
| 	} | ||||
| 	if res ~= 200 then return false end | ||||
| 	local file = io.open(filename, 'w+') | ||||
| 	file:write(table.concat(body)) | ||||
| 	file:close() | ||||
| 	return filename | ||||
|     if not filename then | ||||
|         filename = url:match('.+/(.-)$') or os.time() | ||||
|         filename = '/tmp/' .. filename | ||||
|     end | ||||
|     local body = {} | ||||
|     local doer = HTTP | ||||
|     local do_redir = true | ||||
|     if url:match('^https') then | ||||
|         doer = HTTPS | ||||
|         do_redir = false | ||||
|     end | ||||
|     local _, res = doer.request{ | ||||
|         url = url, | ||||
|         sink = ltn12.sink.table(body), | ||||
|         redirect = do_redir | ||||
|     } | ||||
|     if res ~= 200 then return false end | ||||
|     local file = io.open(filename, 'w+') | ||||
|     file:write(table.concat(body)) | ||||
|     file:close() | ||||
|     return filename | ||||
| end | ||||
|  | ||||
| function utilities.md_escape(text) | ||||
| 	return text:gsub('_', '\\_') | ||||
| 			:gsub('%[', '\\['):gsub('%]', '\\]') | ||||
| 			:gsub('%*', '\\*'):gsub('`', '\\`') | ||||
|     return text:gsub('_', '\\_') | ||||
|             :gsub('%[', '\\['):gsub('%]', '\\]') | ||||
|             :gsub('%*', '\\*'):gsub('`', '\\`') | ||||
| end | ||||
|  | ||||
| function utilities.html_escape(text) | ||||
| 	return text:gsub('&', '&'):gsub('<', '<'):gsub('>', '>') | ||||
|     return text:gsub('&', '&'):gsub('<', '<'):gsub('>', '>') | ||||
| end | ||||
|  | ||||
| utilities.triggers_meta = {} | ||||
| utilities.triggers_meta.__index = utilities.triggers_meta | ||||
| function utilities.triggers_meta:t(pattern, has_args) | ||||
| 	local username = self.username:lower() | ||||
| 	table.insert(self.table, '^'..self.cmd_pat..pattern..'$') | ||||
| 	table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'$') | ||||
| 	if has_args then | ||||
| 		table.insert(self.table, '^'..self.cmd_pat..pattern..'%s+[^%s]*') | ||||
| 		table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'%s+[^%s]*') | ||||
| 	end | ||||
| 	return self | ||||
|     local username = self.username:lower() | ||||
|     table.insert(self.table, '^'..self.cmd_pat..pattern..'$') | ||||
|     table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'$') | ||||
|     if has_args then | ||||
|         table.insert(self.table, '^'..self.cmd_pat..pattern..'%s+[^%s]*') | ||||
|         table.insert(self.table, '^'..self.cmd_pat..pattern..'@'..username..'%s+[^%s]*') | ||||
|     end | ||||
|     return self | ||||
| end | ||||
|  | ||||
| function utilities.triggers(username, cmd_pat, trigger_table) | ||||
| 	local self = setmetatable({}, utilities.triggers_meta) | ||||
| 	self.username = username | ||||
| 	self.cmd_pat = cmd_pat | ||||
| 	self.table = trigger_table or {} | ||||
| 	return self | ||||
|     local self = setmetatable({}, utilities.triggers_meta) | ||||
|     self.username = username | ||||
|     self.cmd_pat = cmd_pat | ||||
|     self.table = trigger_table or {} | ||||
|     return self | ||||
| end | ||||
|  | ||||
| function utilities.with_http_timeout(timeout, fun) | ||||
| 	local original = HTTP.TIMEOUT | ||||
| 	HTTP.TIMEOUT = timeout | ||||
| 	fun() | ||||
| 	HTTP.TIMEOUT = original | ||||
|     local original = HTTP.TIMEOUT | ||||
|     HTTP.TIMEOUT = timeout | ||||
|     fun() | ||||
|     HTTP.TIMEOUT = original | ||||
| end | ||||
|  | ||||
| function utilities.pretty_float(x) | ||||
| 	if x % 1 == 0 then | ||||
| 		return tostring(math.floor(x)) | ||||
| 	else | ||||
| 		return tostring(x) | ||||
| 	end | ||||
|     if x % 1 == 0 then | ||||
|         return tostring(math.floor(x)) | ||||
|     else | ||||
|         return tostring(x) | ||||
|     end | ||||
| end | ||||
|  | ||||
|  -- This table will store unsavory characters that are not properly displayed, | ||||
|  -- or are just not fun to type. | ||||
| utilities.char = { | ||||
| 	zwnj = '', | ||||
| 	arabic = '[\216-\219][\128-\191]', | ||||
| 	rtl_override = '', | ||||
| 	rtl_mark = '', | ||||
| 	em_dash = '—', | ||||
| 	utf_8 = '[%z\1-\127\194-\244][\128-\191]', | ||||
|     zwnj = '', | ||||
|     arabic = '[\216-\219][\128-\191]', | ||||
|     rtl_override = '', | ||||
|     rtl_mark = '', | ||||
|     em_dash = '—', | ||||
|     utf_8 = '[%z\1-\127\194-\244][\128-\191]', | ||||
| } | ||||
|  | ||||
| utilities.set_meta = {} | ||||
| @@ -283,7 +283,7 @@ end | ||||
|  -- More to be added. | ||||
| utilities.style = {} | ||||
| utilities.style.enquote = function(title, body) | ||||
| 	return '*' .. title:gsub('*', '\\*') .. ':*\n"' .. utilities.md_escape(body) .. '"' | ||||
|     return '*' .. title:gsub('*', '\\*') .. ':*\n"' .. utilities.md_escape(body) .. '"' | ||||
| end | ||||
|  | ||||
| return utilities | ||||
|   | ||||
| @@ -4,8 +4,8 @@ | ||||
| # config.lua), delete state file after stop, wait five seconds, and restart. | ||||
|  | ||||
| while true; do | ||||
| 	tg/bin/telegram-cli -P 4567 -E | ||||
| 	[ -f ~/.telegram-cli/state ] && rm ~/.telegram-cli/state | ||||
| 	echo 'tg has stopped. ^C to exit.' | ||||
| 	sleep 5s | ||||
|     tg/bin/telegram-cli -P 4567 -E | ||||
|     [ -f ~/.telegram-cli/state ] && rm ~/.telegram-cli/state | ||||
|     echo 'tg has stopped. ^C to exit.' | ||||
|     sleep 5s | ||||
| done | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 topkecleon
					topkecleon