diff --git a/.gitignore b/.gitignore index 41c51bc..e96dc3c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *~ \#*# +src/*.lua diff --git a/src/cmds/eval.lua b/src/cmds/eval.lua deleted file mode 100644 index e2dec1b..0000000 --- a/src/cmds/eval.lua +++ /dev/null @@ -1,51 +0,0 @@ -local function prind(...) - local t = {...} - local s = '' - for i = 1, #t do - if i > 1 then s = s..'\t' end - s = s .. tostring(t[i] or 'nil') - end - return s .. '\n' -end - -local env = { - assert = assert, - error = error, - ipairs = ipairs, - pairs = pairs, - next = next, - tonumber = tonumber, - tostring = tostring, - type = type, - pcall = pcall, - xpcall = xpcall, - - math = math, - string = string, - table = table, - - dump = dump, -} - -return { - private = true, - run = function(C, msg, owner) - local s = '' - local t = { - msg = msg, - print = function(...) s = s .. prind(...) end, - - C = owner and C or nil, - api = owner and C.api or nil, - } - for k,v in pairs(env) do t[k] = v end - local e, err = load(C.api.unparseArgs(msg.args), 'eval', 'bt', t) - xpcall(function() - if err then error(err) end - e = tostring(e() or '...') - end, function(err) e = err end) - s = s ..'\n'.. e - s = s:gsub(C.api.token:escp(), '') - C.api:reply(msg, s) - end -} diff --git a/src/cmds/eval.moon b/src/cmds/eval.moon new file mode 100644 index 0000000..cd89cc4 --- /dev/null +++ b/src/cmds/eval.moon @@ -0,0 +1,54 @@ +prind = (...) -> + t = {...} + s = '' + for i = 1, #t + if i > 1 + s ..= '\t' + s ..= tostring t[i] or 'nil' + s .. '\n' + +env = + assert: assert + error: error + ipairs: ipairs + pairs: pairs + next: next + tonumber: tonumber + tostring: tostring + type: type + pcall: pcall + xpcall: xpcall + + math: math + string: string + table: table + + dump: dump + +{ + private: true + run: (msg, owner) => + s = '' + t = + msg: msg + print: (...) -> s ..= prind ... + + C: owner and @ or nil + api: owner and @api or nil + + for k, v in pairs env + t[k] = v + + e, err = load @api.unparseArgs(msg.args), 'eval', 'bt', t + + xpcall (-> + error err if err + e = tostring e! or '...' + ), (err) -> e = err + + s ..= '\n'.. e + s = s\gsub @api.token\escp!, '' + + @api\reply msg, s + return +} diff --git a/src/cmds/ping.lua b/src/cmds/ping.lua deleted file mode 100644 index 78e0726..0000000 --- a/src/cmds/ping.lua +++ /dev/null @@ -1,11 +0,0 @@ -return { - run = function(C, msg) - local t = os.time() - local ps, ls, lm, lh, ld - ps, ls = t - msg.date, t - C.loaded - lm = ls / 60 - lh = lm / 60 - ld = lh / 24 - C.api:send(msg, msg.loc.pat:format(ps, ld, lh, lm, ls)) - end -} diff --git a/src/cmds/ping.moon b/src/cmds/ping.moon new file mode 100644 index 0000000..ba17429 --- /dev/null +++ b/src/cmds/ping.moon @@ -0,0 +1,10 @@ +{ + run: (msg) => + t = os.time! + ps, ls = t - msg.date, t - @loaded + lm = ls / 60 + lh = lm / 60 + ld = lh / 24 + @api\send msg, msg.loc.pat\format ps, ld, lh, lm, ls + return +} diff --git a/src/cmds/reload.lua b/src/cmds/reload.lua deleted file mode 100644 index 26b0665..0000000 --- a/src/cmds/reload.lua +++ /dev/null @@ -1,22 +0,0 @@ -return { - private = true, - run = function(C, msg) - local cat, sub = table.unpack(msg.args) - if not (cat and sub) then - return C.api:reply(msg, '/reload cmds ping') - end - - local path = 'src.'..cat..'.'..sub - C.api:off(package.loaded[path]) - package.loaded[path] = nil - local suc, m = pcall(require, path) - - if not suc then return C.api:reply(msg, 'Reload failed. ' .. m) - elseif cat == 'events' then C.api:on(sub, m) - elseif cat == 'cmds' then C.cmds[sub] = m - elseif cat == 'parts' then m(C) - end - - C.api:reply(msg, 'Reloaded. ' .. tostring(m)) - end -} diff --git a/src/cmds/reload.moon b/src/cmds/reload.moon new file mode 100644 index 0000000..74dc69a --- /dev/null +++ b/src/cmds/reload.moon @@ -0,0 +1,23 @@ +{ + private: true + run: (msg) => + cat, sub = table.unpack msg.args + if not (cat and sub) + return @api\reply msg, '/reload cmds ping' + + path = "src.#{cat}.#{sub}" + + @api\off package.loaded[path] + package.loaded[path] = nil + + suc, m = pcall require, path + + if not suc then return @api\reply msg, 'Reload failed. '.. m + else switch cat + when 'events' then @api\on sub, m + when 'cmds' then @cmds[sub] = m + when 'parts' then m @ + + @api\reply msg, "Reloaded. #{m}" + return +} diff --git a/src/cmds/rub.lua b/src/cmds/rub.lua deleted file mode 100644 index d0a02ec..0000000 --- a/src/cmds/rub.lua +++ /dev/null @@ -1,79 +0,0 @@ --- It uses data from central bank of Russia ---- and external service to get json from xml --- Privacy and security is unknown - -local rub = { - url = 'https://api.factmaven.com/xml-to-json/?xml=' - .. 'https://www.cbr.ru/scripts/XML_daily.asp', - - tools = require 'etc.api.tools', -} - -function rub:course(wants) - local resp, succ = self.tools._req(self.url, 'GET') - if not succ then return 'err' end - resp = self.tools.json.decode(resp or '{}') - - resp = resp.ValCurs - table.insert(resp.Valute, { - ID = 'R01000', - NumCode = '001', - CharCode = 'RUB', - Nominal = 1, - Name = 'Российский рубль', - Value = '1' - }) - local uah = table.findV(resp.Valute, {CharCode = 'UAH'}) - table.insert(resp.Valute, { - ID = 'R02000', - NumCode = '200', - CharCode = 'SHT', - Nominal = 1, - Name = 'Штаны', - Value = ('%f'):format(tonumber(uah.Value:gsub(',', '.'), nil) / uah.Nominal * 40) - }) - - wants = type(wants) == 'table' and wants or {} - local r, founds = {}, {} - - if table.find(wants, 'HELP') - or table.find(wants, 'ALL') - then - for _, v in pairs(resp.Valute) do - table.insert(r, ('%d %s (%s) = %f Руб.'):format(v.Nominal, v.Name, v.CharCode, v.Value:gsub(',', '.'))) - end - return r, resp.Date, wants - end - - for i = 1, #resp.Valute do - local v = resp.Valute[i] - if table.find(wants, v.CharCode) then - table.insert(founds, v.CharCode) - table.insert(r, ('%d %s (%s) - %f ₽'):format(v.Nominal, v.Name, v.CharCode, v.Value:gsub(',', '.'))) - end - end - - return r, resp.Date, founds -end - -return { - run = function(C, msg) - local wants = {'USD', 'EUR', table.unpack(msg.args)} - for i = 1, #wants do wants[i] = wants[i]:upper() end -- uppercase - - local v, d, f = rub:course(wants) - if v == 'error' then - return C.api:reply(msg, C.locale:get('error', 'req_err', msg.l)) - end - - local nf = {} - for _, i in pairs(wants) do - if not table.find(f, i) then table.insert(nf, i) end - end - - local s = msg.loc.cur:format(d, table.concat(v, '\n')) - if #nf > 0 then s = s .. msg.loc.notf .. table.concat(nf, ',') end - - C.api:reply(msg, s .. msg.loc.prov) - end -} diff --git a/src/cmds/rub.moon b/src/cmds/rub.moon new file mode 100644 index 0000000..83af9b6 --- /dev/null +++ b/src/cmds/rub.moon @@ -0,0 +1,74 @@ +-- It uses data from central bank of Russia +--- and external service to get json from xml +-- Privacy and security is unknown + +rub = + url: 'https://api.factmaven.com/xml-to-json/?xml=https://www.cbr.ru/scripts/XML_daily.asp' + + tools: require 'etc.api.tools' + + pat: '%d %s (%s) - %f ₽' + + getPat: (val) => + @pat\format val.Nominal, val.Name, val.CharCode, val.Value\gsub ',', '.' + + course: (wants) => + res, suc = @tools._req @url, 'GET' + return 'err' if not suc + res = @tools.json.decode res or '{}' + + res = res.ValCurs + table.insert res.Valute, { + ID: 'R01000' + NumCode: '001' + CharCode: 'RUB' + Nominal: 1 + Name: 'Российский рубль', + Value: '1' + } + uah = table.findV res.Valute, CharCode: 'UAH' + table.insert res.Valute, { + ID: 'R02000' + NumCode: '200' + CharCode: 'SHT' + Nominal: 1 + Name: 'Штаны' + Value: ('%f')\format tonumber(uah.Value\gsub(',', '.'), nil) / uah.Nominal * 40 + } + + wants = type(wants) == 'table' and wants or {} + r, founds = {}, {} + + if table.find wants, 'ALL' + for _, v in pairs res.Valute + table.insert r, @getPat v + return r, res.Date, wants + + for _, v in pairs res.Valute + if table.find wants, v.CharCode + table.insert founds, v.CharCode + table.insert r, @getPat v + + return r, res.Date, founds + +{ + run: (msg) => + wants = {'USD', 'EUR', table.unpack msg.args} + for i = 1, #wants + wants[i] = wants[i]\upper! + + v, d, f = rub\course wants + if v == 'error' + return @api\reply msg, @locale\get 'error', 'req_err', msg.l + + nf = {} + for _, i in pairs wants + table.insert nf, i if not table.find f, i + + s = msg.loc.cur\format d, table.concat v, '\n' + if #nf > 0 + s = s .. msg.loc.notf.. table.concat nf, ',' + + @api\reply msg, s.. msg.loc.prov + return +} diff --git a/src/cmds/start.lua b/src/cmds/start.lua deleted file mode 100644 index 4dd620e..0000000 --- a/src/cmds/start.lua +++ /dev/null @@ -1,6 +0,0 @@ -return { - hide = true, - run = function(C, msg) - C.api:reply(msg, msg.loc.msg) - end -} diff --git a/src/cmds/start.moon b/src/cmds/start.moon new file mode 100644 index 0000000..d65f196 --- /dev/null +++ b/src/cmds/start.moon @@ -0,0 +1,6 @@ +{ + hide: true + run: (msg) => + @api\reply msg, msg.loc.msg + return +} diff --git a/src/events/command.lua b/src/events/command.lua deleted file mode 100644 index 70f0dfd..0000000 --- a/src/events/command.lua +++ /dev/null @@ -1,29 +0,0 @@ -return function(C, api, msg) - local cmd = C.cmds[msg.cmd] - local owner = msg.from.id == C.config.owner - local l = msg.from.language_code - - msg.l = l - - if cmd == nil then - api:send(msg, C.locale:get('error', 'inv_cmd', l)) - - elseif type(cmd.run) ~= 'function' then - api:send(msg, C.locale:get('error', 'cmd_run', l)) - - elseif cmd.private and not owner then - api:send(msg, C.locale:get('error', 'adm_cmd', l)) - - else - if cmd.useQArgs then msg.args = api.parseArgs(api.unparseArgs(msg.args)) end - msg.loc = C.locale:get('cmds', msg.cmd, l) - local succ, err = pcall(cmd.run, C, msg, owner) - if not succ then - print(err) - local cid = C.config.owner - api:forward(cid, msg.chat.id, msg.message_id, false) - api:send(cid, err) - api:reply(msg, C.locale:get('error', 'not_suc', l)) - end - end -end diff --git a/src/events/command.moon b/src/events/command.moon new file mode 100644 index 0000000..cc0fcf9 --- /dev/null +++ b/src/events/command.moon @@ -0,0 +1,28 @@ +(api, msg) => + l = msg.from.language_code + owner = msg.from.id == @config.owner + cmd = @cmds[msg.cmd] + + msg.l = l + + if not cmd + api\send msg, @locale\get 'error', 'inv_cmd', l + + elseif type(cmd.run) ~= 'function' + api\send msg, @locale\get 'error', 'cmd_run', l + + elseif cmd.private and not owner + api\send msg, @locale\get 'error', 'adm_cmd', l + + else + msg.args = api.parseArgs api.unparseArgs msg.args if cmd.useQArgs + msg.loc = @locale\get 'cmds', msg.cmd, l + + suc, err = pcall cmd.run, @, msg, owner + if not suc + -- whoops + print err + api\forward @config.owner, msg.chat.id, msg.message_id, false + api\send @config.owner, err + api\reply msg, @locale\get 'error', 'not_suc', l + return diff --git a/src/events/inlineQuery.lua b/src/events/inlineQuery.lua deleted file mode 100644 index b521fa6..0000000 --- a/src/events/inlineQuery.lua +++ /dev/null @@ -1,8 +0,0 @@ -return function(C, api, q) - api.inline - :answer(q.id, api.inline.result( - 'photo', '1', - 'https://cdn.discordapp.com/attachments/871474214270554124/876506659311202395/unknown.png' - ):thumb 'https://cdn.discordapp.com/attachments/871474214270554124/876506659311202395/unknown.png' - ) -end diff --git a/src/events/inlineQuery.moon b/src/events/inlineQuery.moon new file mode 100644 index 0000000..eeba63d --- /dev/null +++ b/src/events/inlineQuery.moon @@ -0,0 +1,6 @@ +(api, q) => + api.inline\answer q.id, api.inline.result( + 'photo', '1', + 'https://cdn.discordapp.com/attachments/871474214270554124/876506659311202395/unknown.png' + )\thumb 'https://cdn.discordapp.com/attachments/871474214270554124/876506659311202395/unknown.png' + return diff --git a/src/events/message.lua b/src/events/message.lua deleted file mode 100644 index 7a58c92..0000000 --- a/src/events/message.lua +++ /dev/null @@ -1,25 +0,0 @@ -local reg = { - {'эх', 'хуех'}, -- надоели эхать - {'мета', 'хуета'}, - {'meta', 'xueta'}, - {'цукерберг', 'цукерхуй'}, - {'zuckerberg', 'zuckerhui'}, - {'whatsapp', 'вадзад'}, - {'tiktok', 'деградация'}, - {'.*че%?*$', 'пиши ё, грамотей'}, - {'.*чё%?*$', 'ничё'} -} - -return function(C, api, msg) - if msg.text then - msg.text = utf8.lower(msg.text) - local t = '' - for _, v in pairs(reg) do - if msg.text:match(v[1]) - then t = t.. v[2] ..' ' - end - end - if t ~= '' - then api:reply(msg, t) end - end -end diff --git a/src/events/message.moon b/src/events/message.moon new file mode 100644 index 0000000..3cf078e --- /dev/null +++ b/src/events/message.moon @@ -0,0 +1,22 @@ +reg = { + {'эх', 'хуех'} -- надоели эхать + {'мета', 'хуета'} + {'meta', 'xueta'} + {'цукерберг', 'цукерхуй'} + {'zuckerberg', 'zuckerhui'} + {'whatsapp', 'вадзад'} + {'tiktok', 'деградация'} + {'.*че%?*$', 'пиши ё, грамотей'} + {'.*чё%?*$', 'ничё'} +} + +(api, msg) => + if msg.text + msg.text = utf8.lower msg.text + t = '' + for _, v in pairs reg + if msg.text\match v[1] + t ..= "#{v[2]} " + + api\reply msg, t if t ~= '' + return diff --git a/src/events/ready.lua b/src/events/ready.lua deleted file mode 100644 index 0455b87..0000000 --- a/src/events/ready.lua +++ /dev/null @@ -1,61 +0,0 @@ -function table.indexOf(t, w) - local i = {} - for k,v in pairs(t) do i[v] = k end - return i[w] -end - -function table.find(t, w) - for _,v in pairs(t) do - if v == w then return v end - end -end - -function table.findV(t, w) - local b - for _,v in pairs(t) do - for k,x in pairs(w) do - if x ~= v[k] then b=1; break end - end - if b then b = nil - else return v end - end -end - -function string.escp(s) - return s:gsub('[%^%$%%%(%)%.%[%]%*%+%-%?]', '%%%0') -end - -function dump(t, d) - if not tonumber(d) or d < 0 then d = 0 end - local c = '' - for k,v in pairs(t) do - if type(v) == 'table' then v = '\n' .. dump(v, d + 1) end - c = c .. string.format('%s%s = %s\n', (' '):rep(d), k, v) - end - return c -end - -return function(C, api) - C:load 'cmds' - local a - for _, lang in pairs(C.locale.list) do - a = {} - for k, v in pairs(C.cmds) do - if not (v.private or v.hide) then - local cmd = C.locale:get('cmds', k, lang) or {} - table.insert(a, { - command = k, - description = (cmd.args and cmd.args .. ' - ' or '') .. (cmd.desc or C.locale:get('cmds', 'not_des')) - }) - end - end - api:setMyCommands(a, lang) - end - ---[[ - a = {'levels', } - for i = 1, #a do - if not C.db[a[i] ] then C.db[a[i] ] = {} end - end ---]] -end diff --git a/src/events/ready.moon b/src/events/ready.moon new file mode 100644 index 0000000..e0e1551 --- /dev/null +++ b/src/events/ready.moon @@ -0,0 +1,50 @@ + +table.indexOf = (t, w) -> + i = {} + for k, v in pairs t + i[v] = k + i[w] + +table.find = (t, w) -> + for _, v in pairs t + return v if v == w + +table.findV = (t, w) -> + b = nil + for _, v in pairs t + for k, x in pairs w + if x ~= v[k] + b = 1 + break + if b then b = nil -- continue + else return v + +string.escp = (s) -> + s\gsub '[%^%$%%%(%)%.%[%]%*%+%-%?]', '%%%0' + +export dump = (t, d) -> + d = 0 if not tonumber(d) or d < 0 + c = '' + for k, v in pairs t + if type(v) == 'table' + v = '\n'.. dump v, d + 1 + + elseif type(v) == 'userdata' + v = '' + + c ..= ('%s%s = %s\n')\format (' ')\rep d, k, v + c + +(api) => + @\load 'cmds' + + for _, lang in pairs @locale.list + a = {} + for k, v in pairs @cmds + if not (v.private or v.hide) + cmd = @locale\get 'cmds', k, lang or {} + table.insert a, {command: k, + description: (cmd.args and cmd.args .. ' - ' or '') .. (cmd.desc or @locale\get 'cmds', 'not_des') + } + api\setMyCommands a, lang + return diff --git a/src/parts/client.lua b/src/parts/client.lua deleted file mode 100644 index d544e89..0000000 --- a/src/parts/client.lua +++ /dev/null @@ -1,35 +0,0 @@ -local tg = require 'etc.api' - -return function(Core) - local self = Core - - self.api = tg { norun = true } - self.cmds = {} - - print 'Client initialization...' - - function Core._ev(ev, ...) self:ev(...) end - function self.api._ev(_, t, i, n, ...) - self._ev(_, t, i, n, self.api, ...) - end - - self:load 'events' - - self.api:login(self.config.token, function() - print('Logged on as @' .. self.api.info.username) - self.config.token = nil - self.api:emit 'ready' - end) - - local offs, o = 0 - self.api.runs = true - self:on('ready', function() - while self.api.runs do - self:emit 'tick' - - o = self.api:_getUpd(1, offs, 0) - offs = o and o or offs - end - self.api:getUpdates(1, offs, 0) - end) -end diff --git a/src/parts/client.moon b/src/parts/client.moon new file mode 100644 index 0000000..19f3c2e --- /dev/null +++ b/src/parts/client.moon @@ -0,0 +1,32 @@ +tg = require 'etc.api' + +=> + @api = tg { norun: true } + @cmds = {} + + print 'Client initialization...' + + @_ev = (ev, ...) -> @\ev ... + @api._ev = (_, t, i, n, ...) -> + @._ev _, t, i, n, @api, ... + + @\load 'events' + + @api\login @config.token, -> + print "Logged on as @#{@api.info.username}" + @config.token = nil + @api\emit 'ready' + return + + offs, o = 0 + @api.runs = true + @\on 'ready', -> + while @api.runs + @\emit 'tick' + + o = @api\_getUpd 1, offs, 0 + offs = o and o or offs + + @api\getUpdates 1, offs, 0 + return + return diff --git a/src/parts/core.lua b/src/parts/core.lua deleted file mode 100644 index fa3523f..0000000 --- a/src/parts/core.lua +++ /dev/null @@ -1,57 +0,0 @@ -local config = require 'config' - -local Core = { - config = config, - loaded = 0, -} -(require 'etc.events')(Core) -- add events - -function Core:load(what) - local c = config[what] - local s = #c - for i = 1, s do - local v = c[i] - - print(('Loading %s (%d / %d) %s...'):format(what:sub(0, -2), i, s, v)) - -- Lint - local e, a = pcall(require, 'src.'.. what ..'.'.. v) - print(e, a) - if e then - if what == 'events' then self.api:on(v, a) - elseif what == 'cmds' then self.cmds[v] = a - elseif what == 'parts' then a(self) - end - else print 'fail' end - end - print(('Loaded %d %s'):format(s, what)) - self.loaded = os.time() -end - -function Core:ev(t, i, name, ...) - local v = t[i] - if v.name == name then - local succ, err = pcall(v.fn, self, ...) - if not succ then - print('event "' .. name .. '" was failed') - print(err) - end - if v.type == 'once' then table.remove(t, i) end - end -end - -function Core:init() - self:load 'parts' - - utf8 = require 'etc.utf8' - - print 'Done!' - self:emit 'ready' -end - -function Core:stop() - self.api:destroy() - print 'Stopped' - print('Uptime: '.. os.time() - self.loaded.. ' seconds') -end - -Core:init() diff --git a/src/parts/core.moon b/src/parts/core.moon new file mode 100644 index 0000000..95c084f --- /dev/null +++ b/src/parts/core.moon @@ -0,0 +1,52 @@ +config = require 'config' + +Core = + config: config + loaded: 0 + + load: (what) => + c = config[what] + + for i = 1, #c + v = c[i] + + print "Loading #{what\sub 0, -2} (#{i} / #{#c}) #{v}..." + -- Lint + e, a = pcall require, "src.#{what}.#{v}" + print e, a + if e + switch what + when 'events' then @api\on v, a + when 'cmds' then @cmds[v] = a + when 'parts' then a @ + else print 'fail' + print "Loaded #{#c} #{what}" + @loaded = os.time! + + ev: (t, i, name, ...) => + v = t[i] + if v.name == name + suc, err = pcall v.fn, @, ... + if not suc + print "event \"#{name}\" was failed" + print err + table.remove t, i if v.type == 'once' + + init: => + @\load 'parts' + + export utf8 = require 'etc.utf8' + + print 'Done!' + @\emit 'ready' + return + + stop: => + @api\destroy! + print 'Stopped' + print "Uptime: #{os.time! - @loaded} seconds" + return + +require('etc.events')(Core) -- add events +Core\init! +return diff --git a/src/parts/locale.lua b/src/parts/locale.lua deleted file mode 100644 index dce7141..0000000 --- a/src/parts/locale.lua +++ /dev/null @@ -1,33 +0,0 @@ -local Locale = { - list = { - 'en', - 'ru' - }, - main = 'en', - - __newindex = function()end -- ro -} -Locale.__index = Locale - -function Locale:get(cat, k, lang) - assert(cat, 'Give category') - assert(k, 'Give key') - lang = lang or self.main - - local v = (self[lang] or {})[cat] - if not v then - return self[self.main][cat][k] - else return v[k] end -end - -return function(C) - local json = require 'etc.json' - - for i = 1, #Locale.list do - local n = Locale.list[i] - local f = io.open(('src/locales/%s.json'):format(n)) - Locale[n] = json.decode(f:read 'a') - end - - C.locale = setmetatable({}, Locale) -end diff --git a/src/parts/locale.moon b/src/parts/locale.moon new file mode 100644 index 0000000..c345055 --- /dev/null +++ b/src/parts/locale.moon @@ -0,0 +1,30 @@ +Locale = + __newindex: -> -- ro + + list: { + 'en' + 'ru' + } + main: 'en' + + get: (cat, k, lang) => + assert cat, 'Give category' + assert k, 'Give key' + lang or= @main + + v = (@[lang] or {})[cat] + if not v + @[@main][cat][k] or {} + else v[k] or {} + +Locale.__index = Locale + +(C) -> + json = require 'etc.json' + + for i = 1, #Locale.list + n = Locale.list[i] + f = io.open "src/locales/#{n}.json" + Locale[n] = json.decode f\read 'a' + + C.locale = setmetatable {}, Locale