otouto 3.11

"things occurred"

Added some utilities (id_from_username, id_from_message), removed some utilities (latcyr, others?).
Removed cycle-wasting "shortcuts" -- no more automatic id_str or name; text_lower remains.
Moved userdata (nicknames, lastfm, etc) to a different tree in the database (automatic migration will occur). /me now returns userdata.
Speaking of migration, database now stores the latest version run to make future automigration easy.
Database now saves hourly rather than minutely.
Changed readme and some plugins to reflect above changes.
Removed broken rockspec (Brayden, feel free to re-add once it's working).
Added option to automatically block people (via drua) when blacklisted.
Fixed about.lua trigger problems.

administration 1.11 -  Removed /kickme and /broadcast. Users should leave manually, and announcements should be made via channel rather than spam. /setqotd now handles forwarded messages correctly. /kick, /ban, /hammer,
/mod, /admin now support multiple arguments. Added get_targets function. No migration is necessary.
This commit is contained in:
topkecleon 2016-07-05 03:29:11 -04:00
parent 69f0702365
commit 9ebdbd9d3c
19 changed files with 666 additions and 534 deletions

View File

@ -13,8 +13,9 @@ otouto is free software; you are free to redistribute it and/or modify it under
|:----------------------------------------------|:------------------------------|
| [Setup](#setup) | [Plugins](#plugins) |
| [Control plugins](#control-plugins) | [Bindings](#bindings) |
| [Group Administration](#group-administration) | [Output style](#output-style) |
| [List of plugins](#list-of-plugins) | [Contributors](#contributors) |
| [Group administration](#group-administration) | [Database](#database) |
| [List of plugins](#list-of-plugins) | [Output style](#output-style) |
| | [Contributors](#contributors) |
* * *
@ -201,21 +202,22 @@ otouto uses a robust plugin system, similar to yagop's [Telegram-Bot](http://git
Most plugins are intended for public use, but a few are for other purposes, like those for [use by the bot's owner](#control-plugins). See [here](#list-of-plugins) for a list of plugins.
A plugin can have five components, and two of them are required:
There are five standard plugin components.
| Component | Description | Required? |
|:------------------|:---------------------------------------------|:----------|
| `plugin:action` | Main function. Expects `msg` table as an argument. | Y |
| `plugin.triggers` | Table of triggers for the plugin. Uses Lua patterns. | Y |
| `plugin:init` | Optional function run when the plugin is loaded. | N |
| `plugin:cron` | Optional function to be called every minute. | N |
| `plugin.command` | Basic command and syntax. Listed in the help text. | N |
| `plugin.doc` | Usage for the plugin. Returned by "/help $command". | N |
| `plugin.error` | Plugin-specific error message; false for no message. | N |
| Component | Description |
|:-----------|:-----------------------------------------------------|
| `action` | Main function. Expects `msg` table as an argument. |
| `triggers` | Table of triggers for the plugin. Uses Lua patterns. |
| `init` | Optional function run when the plugin is loaded. |
| `cron` | Optional function to be called every minute. |
| `command` | Basic command and syntax. Listed in the help text. |
| `doc` | Usage for the plugin. Returned by "/help $command". |
| `error` | Plugin-specific error message; false for no message. |
The `bot:on_msg_receive` function adds a few variables to the `msg` table for your convenience. These are self-explanatory: `msg.from.id_str`, `msg.to.id_str`, `msg.chat.id_str`, `msg.text_lower`, `msg.from.name`.
Return values from `plugin:action` are optional, but they do effect the flow. If it returns a table, that table will become `msg`, and `on_msg_receive` will continue with that. If it returns `true`, it will continue with the current `msg`.
No component is required, but some depend on others. For example, `action` will never be run if there's no `triggers`, and `doc` will never be seen if there's no `command`.
Return values from `action` are optional, but they do affect the flow. If it returns a table, that table will become `msg`, and `on_msg_receive` will continue with that. If it returns `true`, it will continue with the current `msg`.
When an action or cron function fails, the exception is caught and passed to the `handle_exception` utilty and is either printed to the console or send to the chat/channel defined in `log_chat` in config.lua.
@ -266,7 +268,7 @@ utilities.send_message(self, 987654321, 'Quick brown fox.', false, 54321, true)
Uploading a file for the `sendPhoto` method would look like this:
```
bindings.sendPhoto(self, { chat_id = 987654321 }, { photo = 'rarepepe.jpg' } )
bindings.sendPhoto(self, { chat_id = 987654321 }, { photo = 'dankmeme.jpg' } )
```
and using `sendPhoto` with a file ID would look like this:
@ -279,6 +281,40 @@ Upon success, bindings will return the deserialized result from the API. Upon fa
* * *
## Database
otouto doesn't use one. This isn't because of dedication to lightweightedness or some clever design choice. Interfacing with databases through Lua is never a simple, easy-to-learn process. As one of the goals of otouto is that it should be a bot which is easy to write plugins for, our approach to storing data is to treat our datastore like any ordinary Lua data structure. The "database" is a table accessible in the `database` value of the bot instance (usually `self.database`), and is saved as a JSON-encoded plaintext file each hour, or when the bot is told to halt. This way, keeping and interacting with persistent data is no different than interacting with a Lua table -- with one exception: Keys in tables used as associative arrays must not be numbers. If the index keys are too sparse, the JSON encoder/decoder will either change them to keys or throw an error.
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"
}
```
`database.users` will store user information (usernames, IDs, etc) when the bot sees the user. Each table's key is the user's ID as a string.
`database.userdata` is meant to store miscellanea from various plugins.
`database.version` stores the last bot version that used it. This is to simplify migration to the next version of the bot an easy, automatic process.
Data from other plugins is usually saved in a table with the same name of that plugin. For example, administration.lua stores data in `database.administration`.
* * *
## Output style
otouto plugins should maintain a consistent visual style in their output. This provides a recognizable and comfortable user experience.

View File

@ -20,6 +20,8 @@ Send /help to get started.
]],
-- 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? (and vice-versa)
drua_block_on_blacklist = false,
-- https://datamarket.azure.com/dataset/bing/search
bing_api_key = '',

View File

@ -151,4 +151,12 @@ drua.channel_set_about = function(chat, text)
return drua.send(command)
end
drua.block = function(user)
return drua.send('block_user user#' .. user)
end
drua.unblock = function(user)
return drua.send('unblock_user user#' .. user)
end
return drua

View File

@ -1,22 +0,0 @@
package = 'otouto'
version = 'dev-1'
source = {
url = 'git://github.com/topkecleon/otouto.git'
}
description = {
summary = 'The plugin-wielding, multipurpose Telegram bot!',
detailed = 'A plugin-wielding, multipurpose bot for the Telegram API.',
homepage = 'http://otou.to',
maintainer = 'Drew <drew@otou.to>',
license = 'GPL-2'
}
dependencies = {
'lua >= 5.2',
'LuaSocket ~> 3.0',
'LuaSec ~> 0.6',
'dkjson ~> 2.5',
'LPeg ~> 1.0'
}

View File

@ -4,7 +4,7 @@ local bot = {}
local bindings -- Load Telegram bindings.
local utilities -- Load miscellaneous and cross-plugin functions.
bot.version = '3.10'
bot.version = '3.11'
function bot:init(config) -- The function run when the bot is started or reloaded.
@ -29,7 +29,28 @@ function bot:init(config) -- The function run when the bot is started or reloade
self.database = utilities.load_data(self.info.username..'.db')
end
self.database.users = self.database.users or {} -- Table to cache userdata.
-- MIGRATION CODE 3.10 -> 3.11
if self.database.users and self.database.version ~= '3.11' then
self.database.userdata = {}
for id, user in pairs(self.database.users) do
self.database.userdata[id] = {}
self.database.userdata[id].nickname = user.nickname
self.database.userdata[id].lastfm = user.lastfm
user.nickname = nil
user.lastfm = nil
user.id_str = nil
user.name = nil
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 {}
-- 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
self.plugins = {} -- Load plugins.
@ -43,31 +64,38 @@ function bot:init(config) -- The function run when the bot is started or reloade
self.last_update = self.last_update or 0 -- Set loop variables: Update offset,
self.last_cron = self.last_cron or os.date('%M') -- the time of the last cron job,
self.last_database_save = self.last_database_save or os.date('%H') -- the time of the last database save,
self.is_started = true -- and whether or not the bot should be running.
end
function bot:on_msg_receive(msg, config) -- The fn run whenever a message is received.
-- Cache user info for those involved.
utilities.create_user_entry(self, msg.from)
if msg.forward_from and msg.forward_from.id ~= msg.from.id then
utilities.create_user_entry(self, msg.forward_from)
elseif msg.reply_to_message and msg.reply_to_message.from.id ~= msg.from.id then
utilities.create_user_entry(self, msg.reply_to_message.from)
end
if msg.date < os.time() - 5 then return end -- Do not process old messages.
msg = utilities.enrich_message(msg)
-- Cache user info for those involved.
self.database.users[tostring(msg.from.id)] = 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
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
msg.text = msg.text or msg.caption or ''
msg.text_lower = msg.text:lower()
-- 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
for _, plugin in ipairs(self.plugins) do
for _, trigger in pairs(plugin.triggers) do
for _, trigger in pairs(plugin.triggers or {}) do
if string.match(msg.text_lower, trigger) then
local success, result = pcall(function()
return plugin.action(self, msg, config)
@ -116,7 +144,6 @@ function bot:run(config)
if self.last_cron ~= os.date('%M') then -- Run cron jobs every minute.
self.last_cron = os.date('%M')
utilities.save_data(self.info.username..'.db', self.database) -- Save the database.
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)
@ -127,6 +154,11 @@ function bot:run(config)
end
end
if self.last_database_save ~= os.date('%H') then
utilities.save_data(self.info.username..'.db', self.database) -- Save the database.
self.last_database_save = os.date('%H')
end
end
-- Save the database before exiting.

View File

@ -19,10 +19,11 @@ function about:action(msg, config)
local output = config.about_text .. '\nBased on [otouto](http://github.com/topkecleon/otouto) v'..bot.version..' by topkecleon.'
if
(msg.new_chat_participant and msg.new_chat_participant.id == self.info.id)
or msg.text_lower:match('^'..config.cmd_pat..'about')
or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower())
or msg.text_lower:match('^'..config.cmd_pat..'start')
(msg.new_chat_member and msg.new_chat_member.id == self.info.id)
or msg.text_lower:match('^'..config.cmd_pat..'about$')
or msg.text_lower:match('^'..config.cmd_pat..'about@'..self.info.username:lower()..'$')
or msg.text_lower:match('^'..config.cmd_pat..'start$')
or msg.text_lower:match('^'..config.cmd_pat..'start@'..self.info.username:lower()..'$')
then
utilities.send_message(self, msg.chat.id, output, true, nil, true)
return

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,8 @@ blacklist.triggers = {
function blacklist:action(msg, config)
if self.database.blacklist[msg.from.id_str] then return end
if self.database.blacklist[msg.chat.id_str] then return end
if self.database.blacklist[tostring(msg.from.id)] then return end
if self.database.blacklist[tostring(msg.chat.id)] then return end
if not msg.text:match('^'..config.cmd_pat..'blacklist') then return true end
if msg.from.id ~= config.admin then return end
@ -35,9 +35,15 @@ function blacklist:action(msg, config)
if self.database.blacklist[tostring(target.id)] then
self.database.blacklist[tostring(target.id)] = nil
utilities.send_reply(self, msg, target.name .. ' has been removed from the blacklist.')
if config.drua_block_on_blacklist then
require('drua-tg').unblock(target.id)
end
else
self.database.blacklist[tostring(target.id)] = true
utilities.send_reply(self, msg, target.name .. ' has been added to the blacklist.')
if config.drua_block_on_blacklist then
require('drua-tg').block(target.id)
end
end
end

View File

@ -17,7 +17,7 @@ function control:action(msg, config)
return
end
if msg.date < os.time() - 1 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

View File

@ -41,12 +41,16 @@ end
function greetings:action(msg, config)
local nick = self.database.users[msg.from.id_str].nickname or msg.from.first_name
local nick = utilities.build_name(msg.from.first_name, msg.from.last_name)
if self.database.userdata[tostring(msg.from.id)] then
nick = self.database.userdata[tostring(msg.from.id)].nickname or nick
end
for trigger,responses in pairs(config.greetings) do
for _,response in pairs(responses) do
if msg.text_lower:match(response..',? '..self.info.first_name:lower()) then
utilities.send_message(self, msg.chat.id, utilities.latcyr(trigger:gsub('#NAME', nick)))
local output = utilities.char.zwnj .. trigger:gsub('#NAME', nick)
utilities.send_message(self, msg.chat.id, output)
return
end
end

View File

@ -30,6 +30,8 @@ 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 {}
if string.match(msg.text, '^'..config.cmd_pat..'lastfm') then
utilities.send_message(self, msg.chat.id, lastfm.doc, true, msg.message_id, true)
@ -38,10 +40,10 @@ function lastfm:action(msg, config)
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.users[msg.from.id_str].lastfm = nil
self.database.userdata[from_id_str].lastfm = nil
utilities.send_reply(self, msg, 'Your last.fm username has been forgotten.')
else
self.database.users[msg.from.id_str].lastfm = input
self.database.userdata[from_id_str].lastfm = input
utilities.send_reply(self, msg, 'Your last.fm username has been set to "' .. input .. '".')
end
return
@ -53,12 +55,12 @@ function lastfm:action(msg, config)
local alert = ''
if input then
username = input
elseif self.database.users[msg.from.id_str].lastfm then
username = self.database.users[msg.from.id_str].lastfm
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.users[msg.from.id_str].lastfm = 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

View File

@ -4,24 +4,37 @@ 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.`'
end
function me:action(msg, config)
local target = self.database.users[msg.from.id_str]
local userdata = self.database.userdata[tostring(msg.from.id)] or {}
if msg.from.id == config.admin and (msg.reply_to_message or utilities.input(msg.text)) then
target = utilities.user_from_message(self, msg, true)
if target.err then
utilities.send_reply(self, msg, target.err)
return
if msg.from.id == config.admin then
if msg.reply_to_message then
userdata = self.database.userdata[tostring(msg.reply_to_message.from.id)]
else
local input = utilities.input(msg.text)
if input then
local user_id = utilities.id_from_username(self, input)
if user_id then
userdata = self.database.userdata[tostring(user_id)] or {}
end
end
end
end
local output = ''
for k,v in pairs(target) do
for k,v in pairs(userdata) do
output = output .. '*' .. k .. ':* `' .. tostring(v) .. '`\n'
end
if output == '' then
output = 'There is no data stored for this user.'
end
utilities.send_message(self, msg.chat.id, output, true, nil, true)
end

View File

@ -14,34 +14,35 @@ end
function nick:action(msg, config)
local target = msg.from
local id_str, name
if msg.from.id == config.admin and msg.reply_to_message then
target = msg.reply_to_message.from
target.id_str = tostring(target.id)
target.name = target.first_name
if target.last_name then
target.name = target.first_name .. ' ' .. target.last_name
end
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 {}
local output
local input = utilities.input(msg.text)
if not input then
if self.database.users[target.id_str].nickname then
output = target.name .. '\'s nickname is "' .. self.database.users[target.id_str].nickname .. '".'
if self.database.userdata[id_str].nickname then
output = name .. '\'s nickname is "' .. self.database.userdata[id_str].nickname .. '".'
else
output = target.name .. ' currently has no nickname.'
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.users[target.id_str].nickname = nil
output = target.name .. '\'s nickname has been deleted.'
self.database.userdata[id_str].nickname = nil
output = name .. '\'s nickname has been deleted.'
else
input = input:gsub('\n', ' ')
self.database.users[target.id_str].nickname = input
output = target.name .. '\'s nickname has been set to "' .. input .. '".'
self.database.userdata[id_str].nickname = input
output = name .. '\'s nickname has been set to "' .. input .. '".'
end
utilities.send_reply(self, msg, output)

View File

@ -40,13 +40,14 @@ function remind:action(msg)
utilities.send_message(self, msg.chat.id, remind.doc, true, msg.message_id, true)
return
end
local chat_id_str = tostring(msg.chat.id)
-- Make a database entry for the group/user if one does not exist.
self.database.reminders[msg.chat.id_str] = self.database.reminders[msg.chat.id_str] or {}
self.database.reminders[chat_id_str] = self.database.reminders[chat_id_str] or {}
-- Limit group reminders to 10 and private reminders to 50.
if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 9 then
if msg.chat.type ~= 'private' and utilities.table_size(self.database.reminders[chat_id_str]) > 9 then
utilities.send_reply(self, msg, 'Sorry, this group already has ten reminders.')
return
elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[msg.chat.id_str]) > 49 then
elseif msg.chat.type == 'private' and utilities.table_size(self.database.reminders[chat_id_str]) > 49 then
utilities.send_reply(msg, 'Sorry, you already have fifty reminders.')
return
end
@ -55,7 +56,7 @@ function remind:action(msg)
time = os.time() + duration * 60,
message = message
}
table.insert(self.database.reminders[msg.chat.id_str], reminder)
table.insert(self.database.reminders[chat_id_str], reminder)
local output = 'I will remind you in ' .. duration
if duration == 1 then
output = output .. ' minute!'

View File

@ -17,8 +17,9 @@ 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[msg.chat.id_str] = self.database.setandget[msg.chat.id_str] or {}
self.database.setandget[chat_id_str] = self.database.setandget[chat_id_str] or {}
if msg.text_lower:match('^'..config.cmd_pat..'set') then
@ -33,10 +34,10 @@ function setandget:action(msg, config)
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[msg.chat.id_str][name] = nil
self.database.setandget[chat_id_str][name] = nil
utilities.send_message(self, msg.chat.id, 'That value has been deleted.')
else
self.database.setandget[msg.chat.id_str][name] = value
self.database.setandget[chat_id_str][name] = value
utilities.send_message(self, msg.chat.id, '"' .. name .. '" has been set to "' .. value .. '".', true)
end
@ -44,11 +45,11 @@ function setandget:action(msg, config)
if not input then
local output
if utilities.table_size(self.database.setandget[msg.chat.id_str]) == 0 then
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[msg.chat.id_str]) do
for k,v in pairs(self.database.setandget[chat_id_str]) do
output = output .. '' .. k .. ': `' .. v .. '`\n'
end
end
@ -57,8 +58,8 @@ function setandget:action(msg, config)
end
local output
if self.database.setandget[msg.chat.id_str][input:lower()] then
output = '`' .. self.database.setandget[msg.chat.id_str][input:lower()] .. '`'
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

View File

@ -61,7 +61,7 @@ local slaps = {
'VICTIM died. I blame VICTOR.',
'VICTIM was axe-murdered by VICTOR.',
'VICTIM\'s melon was split by VICTOR.',
'VICTIM was slice and diced 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!',
@ -102,29 +102,52 @@ local slaps = {
'VICTIM was impeached.',
'VICTIM was one-hit KO\'d by VICTOR.',
'VICTOR sent VICTIM to /dev/null.',
'VICTOR sent VICTIM down the memory hole.'
'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 victor = self.database.users[msg.from.id_str]
local victim = utilities.user_from_message(self, msg, true)
local input = utilities.input(msg.text)
local victim_name = victim.nickname or victim.first_name or input
local victor_name = victor.nickname or victor.first_name
if not victim_name or victim_name == victor_name then
victim_name = victor_name
local victor_id = msg.from.id
local victim_id = utilities.id_from_message(self, msg)
-- 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 = slaps[math.random(#slaps)]
output = output:gsub('VICTIM', victim_name)
output = output:gsub('VICTOR', victor_name)
output = utilities.char.zwnj .. output
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

View File

@ -1,6 +1,7 @@
local weather = {}
local HTTP = require('socket.http')
HTTP.TIMEOUT = 2
local JSON = require('dkjson')
local utilities = require('otouto.utilities')

View File

@ -16,9 +16,10 @@ function whoami:action(msg)
if msg.reply_to_message then
msg = msg.reply_to_message
msg.from.name = utilities.build_name(msg.from.first_name, msg.from.last_name)
end
local from_name = utilities.build_name(msg.from.first_name, msg.from.last_name)
local chat_id = math.abs(msg.chat.id)
if chat_id > 1000000000000 then
chat_id = chat_id - 1000000000000
@ -26,10 +27,10 @@ function whoami:action(msg)
local user = 'You are @%s, also known as *%s* `[%s]`'
if msg.from.username then
user = user:format(utilities.markdown_escape(msg.from.username), msg.from.name, msg.from.id)
user = user:format(utilities.markdown_escape(msg.from.username), from_name, msg.from.id)
else
user = 'You are *%s* `[%s]`,'
user = user:format(msg.from.name, msg.from.id)
user = user:format(from_name, msg.from.id)
end
local group = '@%s, also known as *%s* `[%s]`.'

View File

@ -73,49 +73,12 @@ function utilities.utf8_len(s)
return chars
end
-- I swear, I copied this from PIL, not yago! :)
function utilities.trim(str) -- Trims whitespace from a string.
-- Trims whitespace from a string.
function utilities.trim(str)
local s = str:gsub('^%s*(.-)%s*$', '%1')
return s
end
local lc_list = {
-- Latin = 'Cyrillic'
['A'] = 'А',
['B'] = 'В',
['C'] = 'С',
['E'] = 'Е',
['I'] = 'І',
['J'] = 'Ј',
['K'] = 'К',
['M'] = 'М',
['H'] = 'Н',
['O'] = 'О',
['P'] = 'Р',
['S'] = 'Ѕ',
['T'] = 'Т',
['X'] = 'Х',
['Y'] = 'Ү',
['a'] = 'а',
['c'] = 'с',
['e'] = 'е',
['i'] = 'і',
['j'] = 'ј',
['o'] = 'о',
['s'] = 'ѕ',
['x'] = 'х',
['y'] = 'у',
['!'] = 'ǃ'
}
-- Replaces letters with corresponding Cyrillic characters.
function utilities.latcyr(str)
for k,v in pairs(lc_list) do
str = str:gsub(k, v)
end
return str
end
-- Loads a JSON file as a table.
function utilities.load_data(filename)
local f = io.open(filename)
@ -179,9 +142,41 @@ end
function utilities:resolve_username(input)
input = input:gsub('^@', '')
for _,v in pairs(self.database.users) do
if v.username and v.username:lower() == input:lower() then
return v
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
-- Simpler than above function; only returns an ID.
-- Returns nil if no ID is available.
function utilities:id_from_username(input)
input = input:gsub('^@', '')
for _, user in pairs(self.database.users) do
if user.username and user.username:lower() == input:lower() then
return user.id
end
end
end
-- Simpler than below function; only returns an ID.
-- Returns nil if no ID is available.
function utilities:id_from_message(msg)
if msg.reply_to_message then
return msg.reply_to_message.from.id
else
local input = utilities.input(msg.text)
if input then
if tonumber(input) then
return tonumber(input)
elseif input:match('^@') then
return utilities.id_from_username(self, input)
end
end
end
end
@ -309,37 +304,6 @@ function utilities.with_http_timeout(timeout, fun)
HTTP.TIMEOUT = original
end
function utilities.enrich_user(user)
user.id_str = tostring(user.id)
user.name = utilities.build_name(user.first_name, user.last_name)
return user
end
function utilities.enrich_message(msg)
if not msg.text then msg.text = msg.caption or '' end
msg.text_lower = msg.text:lower()
msg.from = utilities.enrich_user(msg.from)
msg.chat.id_str = tostring(msg.chat.id)
if msg.reply_to_message then
if not msg.reply_to_message.text then
msg.reply_to_message.text = msg.reply_to_message.caption or ''
end
msg.reply_to_message.text_lower = msg.reply_to_message.text:lower()
msg.reply_to_message.from = utilities.enrich_user(msg.reply_to_message.from)
msg.reply_to_message.chat.id_str = tostring(msg.reply_to_message.chat.id)
end
if msg.forward_from then
msg.forward_from = utilities.enrich_user(msg.forward_from)
end
if msg.new_chat_participant then
msg.new_chat_participant = utilities.enrich_user(msg.new_chat_participant)
end
if msg.left_chat_participant then
msg.left_chat_participant = utilities.enrich_user(msg.left_chat_participant)
end
return msg
end
function utilities.pretty_float(x)
if x % 1 == 0 then
return tostring(math.floor(x))
@ -348,21 +312,6 @@ function utilities.pretty_float(x)
end
end
function utilities:create_user_entry(user)
local id = tostring(user.id)
-- Clear things that may no longer exist, or create a user entry.
if self.database.users[id] then
self.database.users[id].username = nil
self.database.users[id].last_name = nil
else
self.database.users[id] = {}
end
-- Add all the user info to the entry.
for k,v in pairs(user) do
self.database.users[id][k] = v
end
end
-- This table will store unsavory characters that are not properly displayed,
-- or are just not fun to type.
utilities.char = {