<commit_before>--[[
    utilities.lua
    Functions shared among otouto plugins.

    Copyright 2016 topkecleon <<EMAIL>>
    This code is licensed under the GNU AGPLv3. See /LICENSE for details.
]]--

local http = require('socket.http')
local https = require('ssl.https')
-- Global http/s timeout.
http.TIMEOUT = 10
local json = require('dkjson')
local ltn12 = require('ltn12')
local url = require('socket.url')
-- Lua 5.2 compatibility.
-- If no built-in utf8 is available, load the library.
local utf8 = utf8 or require('lua-utf8')

local bindings = require('otouto.bindings')

local utilities = {}

-- For the sake of ease to new contributors and familiarity to old contributors,
-- we'll provide a couple of aliases to real bindings here.
-- 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(
        '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(msg, text, use_markdown)
    return utilities.send_message(msg.chat.id, text, true, 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
end

-- Returns the string after the first space.
function utilities.input(s)
    return s:match('%s+(.+)')
end

function utilities.input_from_msg(msg)
    return msg.text:match('%s+(.+)')
        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
function utilities.utf8_len(s)
    local chars = 0
    for i = 1, string.len(s) do
        local b = string.byte(s, i)
        if b < 128 or b >= 192 then
            chars = chars + 1
        end
    end
    return chars
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
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()
end

-- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua.
-- Returns nil for a connection error and false for zero results.
function utilities.get_coords(input)
    local call_url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. url.escape(input)
    local jstr, res = http.request(call_url)
    if res ~= 200 then
        return
    end
    local jdat = json.decode(jstr)
    if not jdat then
        return
    elseif jdat.status == 'ZERO_RESULTS' or not jdat.results[1] then
        return false
    else
        return jdat.results[1].geometry.location.lat, jdat.results[1].geometry.location.lng
    end
end

utilities.data_table_meta = {
    __index = function (self, key)
        local data = rawget(self, "_data")
        if data._parent then
            local val = data[key]
            return val and
                setmetatable({_data = val, _key = rawget(self, "_key")}, getmetatable(self))
        else
            local val = data[key]
            return val and val[rawget(self, "_key")]
        end
    end,
    __newindex = function (self, key, value)
        local data = rawget(self, "_data")
        if data._parent then
            error("Can't set non-terminal key " .. tostring(key) .. " in a data_table")
            return
        else
            local t = data[key]
            if t == nil then
                t = {}
                data[key] = t
            end
            t[rawget(self, "_key")] = value
            return
        end
    end,
    __pairs = function (self)
        local data = rawget(self, "_data")
        if data._parent then
            local selfmeta = getmetatable(self)
            local function iter(table, index)
                local new_index, val = next(table, index)
                if new_index then
                    return new_index, setmetatable({_data = val, _key = rawget(self, "_key")}, selfmeta)
                else
                    return nil
                end
            end
            return iter, data, nil
        else
            local function iter(table, index)
                local new_index, val = next(table, index)
                if new_index then
                    return new_index, val[rawget(self, "_key")]
                else
                    return nil
                end
            end
            return iter, data, nil
        end
    end,
    __ipairs = function (self)
        local data = rawget(self, "_data")
        if data._parent then
            local selfmeta = getmetatable(self)
            local function iter(table, i)
                i = i + 1
                local val = table[i]
                if val then
                    return i, setmetatable({_data = val, _key = rawget(self, "_key")}, selfmeta)
                else
                    return nil
                end
            end
            return iter, data, 0
        else
            local function iter(table, i)
                i = i + 1
                local val = table[i]
                if val then
                    return i, val[rawget(self, "_key")]
                else
                    return nil
                end
            end
            return iter, data, nil
        end
    end,
}
function utilities.data_table(data, key)
    return setmetatable({_data = data, _key = key}, utilities.data_table_meta)
end

utilities.user_meta = {
    rank = function(self, bot, chat_id)
        return require('otouto.autils').rank(bot, self.data.info.id, chat_id)<commit_msg>The main loop relying on userdata.info being there is bad.<commit_after>--[[
    utilities.lua
    Functions shared among otouto plugins.

    Copyright 2016 topkecleon <<EMAIL>>
    This code is licensed under the GNU AGPLv3. See /LICENSE for details.
]]--

local http = require('socket.http')
local https = require('ssl.https')
-- Global http/s timeout.
http.TIMEOUT = 10
local json = require('dkjson')
local ltn12 = require('ltn12')
local url = require('socket.url')
-- Lua 5.2 compatibility.
-- If no built-in utf8 is available, load the library.
local utf8 = utf8 or require('lua-utf8')

local bindings = require('otouto.bindings')

local utilities = {}

-- For the sake of ease to new contributors and familiarity to old contributors,
-- we'll provide a couple of aliases to real bindings here.
-- 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(
        '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(msg, text, use_markdown)
    return utilities.send_message(msg.chat.id, text, true, 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
end

-- Returns the string after the first space.
function utilities.input(s)
    return s:match('%s+(.+)')
end

function utilities.input_from_msg(msg)
    return msg.text:match('%s+(.+)')
        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
function utilities.utf8_len(s)
    local chars = 0
    for i = 1, string.len(s) do
        local b = string.byte(s, i)
        if b < 128 or b >= 192 then
            chars = chars + 1
        end
    end
    return chars
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
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()
end

-- Gets coordinates for a location. Used by gMaps.lua, time.lua, weather.lua.
-- Returns nil for a connection error and false for zero results.
function utilities.get_coords(input)
    local call_url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' .. url.escape(input)
    local jstr, res = http.request(call_url)
    if res ~= 200 then
        return
    end
    local jdat = json.decode(jstr)
    if not jdat then
        return
    elseif jdat.status == 'ZERO_RESULTS' or not jdat.results[1] then
        return false
    else
        return jdat.results[1].geometry.location.lat, jdat.results[1].geometry.location.lng
    end
end

utilities.data_table_meta = {
    __index = function (self, key)
        local data = rawget(self, "_data")
        if data._parent then
            local val = data[key]
            return val and
                setmetatable({_data = val, _key = rawget(self, "_key")}, getmetatable(self))
        else
            local val = data[key]
            return val and val[rawget(self, "_key")]
        end
    end,
    __newindex = function (self, key, value)
        local data = rawget(self, "_data")
        if data._parent then
            error("Can't set non-terminal key " .. tostring(key) .. " in a data_table")
            return
        else
            local t = data[key]
            if t == nil then
                t = {}
                data[key] = t
            end
            t[rawget(self, "_key")] = value
            return
        end
    end,
    __pairs = function (self)
        local data = rawget(self, "_data")
        if data._parent then
            local selfmeta = getmetatable(self)
            local function iter(table, index)
                local new_index, val = next(table, index)
                if new_index then
                    return new_index, setmetatable({_data = val, _key = rawget(self, "_key")}, selfmeta)
                else
                    return nil
                end
            end
            return iter, data, nil
        else
            local function iter(table, index)
                local new_index, val = next(table, index)
                if new_index then
                    return new_index, val[rawget(self, "_key")]
                else
                    return nil
                end
            end
            return iter, data, nil
        end
    end,
    __ipairs = function (self)
        local data = rawget(self, "_data")
        if data._parent then
            local selfmeta = getmetatable(self)
            local function iter(table, i)
                i = i + 1
                local val = table[i]
                if val then
                    return i, setmetatable({_data = val, _key = rawget(self, "_key")}, selfmeta)
                else
                    return nil
                end
            end
            return iter, data, 0
        else
            local function iter(table, i)
                i = i + 1
                local val = table[i]
                if val then
                    return i, val[rawget(self, "_key")]
                else
                    return nil
                end
            end
            return iter, data, nil
        end
    end,
}
function utilities.data_table(data, key)
    return setmetatable({_data = data, _key = key}, utilities.data_table_meta)
end

utilities.user_meta = {
    rank = function(self, bot, chat_id)
        if self.data.info then
            return require('otouto.autils').rank(bot, self.data.info.id, chat_id)
        else
            return 1
        end
{'fraction_non_alphanumeric': 0.09011943539630836, 'fraction_numerical': 0.00643710252830774, 'mean_word_length': 1.8649189069095757, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1417293', 'n_tokens_mistral': 3894, 'n_tokens_neox': 3706, 'n_words': 1225}