summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-asterisk/luasrc/asterisk.lua
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-asterisk/luasrc/asterisk.lua')
-rw-r--r--applications/luci-app-asterisk/luasrc/asterisk.lua759
1 files changed, 759 insertions, 0 deletions
diff --git a/applications/luci-app-asterisk/luasrc/asterisk.lua b/applications/luci-app-asterisk/luasrc/asterisk.lua
new file mode 100644
index 000000000..15081cc9a
--- /dev/null
+++ b/applications/luci-app-asterisk/luasrc/asterisk.lua
@@ -0,0 +1,759 @@
+--[[
+LuCI - Lua Configuration Interface
+Asterisk PBX interface library
+
+Copyright 2009 Jo-Philipp Wich <xm@subsignal.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+
+]]--
+
+module("luci.asterisk", package.seeall)
+require("luci.asterisk.cc_idd")
+
+local _io = require("io")
+local uci = require("luci.model.uci").cursor()
+local sys = require("luci.sys")
+local util = require("luci.util")
+
+AST_BIN = "/usr/sbin/asterisk"
+AST_FLAGS = "-r -x"
+
+
+--- LuCI Asterisk - Resync uci context
+function uci_resync()
+ uci = luci.model.uci.cursor()
+end
+
+--- LuCI Asterisk io interface
+-- Handles low level io.
+-- @type module
+io = luci.util.class()
+
+--- Execute command and return output
+-- @param command String containing the command to execute
+-- @return String containing the command output
+function io.exec(command)
+ local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
+ assert(fh, "Failed to invoke asterisk")
+
+ local buffer = fh:read("*a")
+ fh:close()
+ return buffer
+end
+
+--- Execute command and invoke given callback for each readed line
+-- @param command String containing the command to execute
+-- @param callback Function to call back for each line
+-- @return Always true
+function io.execl(command, callback)
+ local ln
+ local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
+ assert(fh, "Failed to invoke asterisk")
+
+ repeat
+ ln = fh:read("*l")
+ callback(ln)
+ until not ln
+
+ fh:close()
+ return true
+end
+
+--- Execute command and return an iterator that returns one line per invokation
+-- @param command String containing the command to execute
+-- @return Iterator function
+function io.execi(command)
+ local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
+ assert(fh, "Failed to invoke asterisk")
+
+ return function()
+ local ln = fh:read("*l")
+ if not ln then fh:close() end
+ return ln
+ end
+end
+
+
+--- LuCI Asterisk - core status
+core = luci.util.class()
+
+--- Retrive version string.
+-- @return String containing the reported asterisk version
+function core.version(self)
+ local version = io.exec("core show version")
+ return version:gsub(" *\n", "")
+end
+
+
+--- LuCI Asterisk - SIP information.
+-- @type module
+sip = luci.util.class()
+
+--- Get a list of known SIP peers
+-- @return Table containing each SIP peer
+function sip.peers(self)
+ local head = false
+ local peers = { }
+
+ for line in io.execi("sip show peers") do
+ if not head then
+ head = true
+ elseif not line:match(" sip peers ") then
+ local online, delay, id, uid
+ local name, host, dyn, nat, acl, port, status =
+ line:match("(.-) +(.-) +([D ]) ([N ]) (.) (%d+) +(.+)")
+
+ if host == '(Unspecified)' then host = nil end
+ if port == '0' then port = nil else port = tonumber(port) end
+
+ dyn = ( dyn == 'D' and true or false )
+ nat = ( nat == 'N' and true or false )
+ acl = ( acl ~= ' ' and true or false )
+
+ online, delay = status:match("(OK) %((%d+) ms%)")
+
+ if online == 'OK' then
+ online = true
+ delay = tonumber(delay)
+ elseif status ~= 'Unmonitored' then
+ online = false
+ delay = 0
+ else
+ online = nil
+ delay = 0
+ end
+
+ id, uid = name:match("(.+)/(.+)")
+
+ if not ( id and uid ) then
+ id = name .. "..."
+ uid = nil
+ end
+
+ peers[#peers+1] = {
+ online = online,
+ delay = delay,
+ name = id,
+ user = uid,
+ dynamic = dyn,
+ nat = nat,
+ acl = acl,
+ host = host,
+ port = port
+ }
+ end
+ end
+
+ return peers
+end
+
+--- Get informations of given SIP peer
+-- @param peer String containing the name of the SIP peer
+function sip.peer(peer)
+ local info = { }
+ local keys = { }
+
+ for line in io.execi("sip show peer " .. peer) do
+ if #line > 0 then
+ local key, val = line:match("(.-) *: +(.*)")
+ if key and val then
+
+ key = key:gsub("^ +",""):gsub(" +$", "")
+ val = val:gsub("^ +",""):gsub(" +$", "")
+
+ if key == "* Name" then
+ key = "Name"
+ elseif key == "Addr->IP" then
+ info.address, info.port = val:match("(.+) Port (.+)")
+ info.port = tonumber(info.port)
+ elseif key == "Status" then
+ info.online, info.delay = val:match("(OK) %((%d+) ms%)")
+ if info.online == 'OK' then
+ info.online = true
+ info.delay = tonumber(info.delay)
+ elseif status ~= 'Unmonitored' then
+ info.online = false
+ info.delay = 0
+ else
+ info.online = nil
+ info.delay = 0
+ end
+ end
+
+ if val == 'Yes' or val == 'yes' or val == '<Set>' then
+ val = true
+ elseif val == 'No' or val == 'no' then
+ val = false
+ elseif val == '<Not set>' or val == '(none)' then
+ val = nil
+ end
+
+ keys[#keys+1] = key
+ info[key] = val
+ end
+ end
+ end
+
+ return info, keys
+end
+
+
+--- LuCI Asterisk - Internal helpers
+-- @type module
+tools = luci.util.class()
+
+--- Convert given value to a list of tokens. Split by white space.
+-- @param val String or table value
+-- @return Table containing tokens
+function tools.parse_list(v)
+ local tokens = { }
+
+ v = type(v) == "table" and v or { v }
+ for _, v in ipairs(v) do
+ if type(v) == "string" then
+ for v in v:gmatch("(%S+)") do
+ tokens[#tokens+1] = v
+ end
+ end
+ end
+
+ return tokens
+end
+
+--- Convert given list to a collection of hyperlinks
+-- @param list Table of tokens
+-- @param url String pattern or callback function to construct urls (optional)
+-- @param sep String containing the seperator (optional, default is ", ")
+-- @return String containing the html fragment
+function tools.hyperlinks(list, url, sep)
+ local html
+
+ local function mkurl(p, t)
+ if type(p) == "string" then
+ return p:format(t)
+ elseif type(p) == "function" then
+ return p(t)
+ else
+ return '#'
+ end
+ end
+
+ list = list or { }
+ url = url or "%s"
+ sep = sep or ", "
+
+ for _, token in ipairs(list) do
+ html = ( html and html .. sep or '' ) ..
+ '<a href="%s">%s</a>' %{ mkurl(url, token), token }
+ end
+
+ return html or ''
+end
+
+
+--- LuCI Asterisk - International Direct Dialing Prefixes
+-- @type module
+idd = luci.util.class()
+
+--- Lookup the country name for the given IDD code.
+-- @param country String containing IDD code
+-- @return String containing the country name
+function idd.country(c)
+ for _, v in ipairs(cc_idd.CC_IDD) do
+ if type(v[3]) == "table" then
+ for _, v2 in ipairs(v[3]) do
+ if v2 == tostring(c) then
+ return v[1]
+ end
+ end
+ elseif v[3] == tostring(c) then
+ return v[1]
+ end
+ end
+end
+
+--- Lookup the country code for the given IDD code.
+-- @param country String containing IDD code
+-- @return Table containing the country code(s)
+function idd.cc(c)
+ for _, v in ipairs(cc_idd.CC_IDD) do
+ if type(v[3]) == "table" then
+ for _, v2 in ipairs(v[3]) do
+ if v2 == tostring(c) then
+ return type(v[2]) == "table"
+ and v[2] or { v[2] }
+ end
+ end
+ elseif v[3] == tostring(c) then
+ return type(v[2]) == "table"
+ and v[2] or { v[2] }
+ end
+ end
+end
+
+--- Lookup the IDD code(s) for the given country.
+-- @param idd String containing the country name
+-- @return Table containing the IDD code(s)
+function idd.idd(c)
+ for _, v in ipairs(cc_idd.CC_IDD) do
+ if v[1]:lower():match(c:lower()) then
+ return type(v[3]) == "table"
+ and v[3] or { v[3] }
+ end
+ end
+end
+
+--- Populate given CBI field with IDD codes.
+-- @param field CBI option object
+-- @return (nothing)
+function idd.cbifill(o)
+ for i, v in ipairs(cc_idd.CC_IDD) do
+ o:value("_%i" % i, util.pcdata(v[1]))
+ end
+
+ o.formvalue = function(...)
+ local val = luci.cbi.Value.formvalue(...)
+ if val:sub(1,1) == "_" then
+ val = tonumber((val:gsub("^_", "")))
+ if val then
+ return type(cc_idd.CC_IDD[val][3]) == "table"
+ and cc_idd.CC_IDD[val][3] or { cc_idd.CC_IDD[val][3] }
+ end
+ end
+ return val
+ end
+
+ o.cfgvalue = function(...)
+ local val = luci.cbi.Value.cfgvalue(...)
+ if val then
+ val = tools.parse_list(val)
+ for i, v in ipairs(cc_idd.CC_IDD) do
+ if type(v[3]) == "table" then
+ if v[3][1] == val[1] then
+ return "_%i" % i
+ end
+ else
+ if v[3] == val[1] then
+ return "_%i" % i
+ end
+ end
+ end
+ end
+ return val
+ end
+end
+
+
+--- LuCI Asterisk - Country Code Prefixes
+-- @type module
+cc = luci.util.class()
+
+--- Lookup the country name for the given CC code.
+-- @param country String containing CC code
+-- @return String containing the country name
+function cc.country(c)
+ for _, v in ipairs(cc_idd.CC_IDD) do
+ if type(v[2]) == "table" then
+ for _, v2 in ipairs(v[2]) do
+ if v2 == tostring(c) then
+ return v[1]
+ end
+ end
+ elseif v[2] == tostring(c) then
+ return v[1]
+ end
+ end
+end
+
+--- Lookup the international dialing code for the given CC code.
+-- @param cc String containing CC code
+-- @return String containing IDD code
+function cc.idd(c)
+ for _, v in ipairs(cc_idd.CC_IDD) do
+ if type(v[2]) == "table" then
+ for _, v2 in ipairs(v[2]) do
+ if v2 == tostring(c) then
+ return type(v[3]) == "table"
+ and v[3] or { v[3] }
+ end
+ end
+ elseif v[2] == tostring(c) then
+ return type(v[3]) == "table"
+ and v[3] or { v[3] }
+ end
+ end
+end
+
+--- Lookup the CC code(s) for the given country.
+-- @param country String containing the country name
+-- @return Table containing the CC code(s)
+function cc.cc(c)
+ for _, v in ipairs(cc_idd.CC_IDD) do
+ if v[1]:lower():match(c:lower()) then
+ return type(v[2]) == "table"
+ and v[2] or { v[2] }
+ end
+ end
+end
+
+--- Populate given CBI field with CC codes.
+-- @param field CBI option object
+-- @return (nothing)
+function cc.cbifill(o)
+ for i, v in ipairs(cc_idd.CC_IDD) do
+ o:value("_%i" % i, util.pcdata(v[1]))
+ end
+
+ o.formvalue = function(...)
+ local val = luci.cbi.Value.formvalue(...)
+ if val:sub(1,1) == "_" then
+ val = tonumber((val:gsub("^_", "")))
+ if val then
+ return type(cc_idd.CC_IDD[val][2]) == "table"
+ and cc_idd.CC_IDD[val][2] or { cc_idd.CC_IDD[val][2] }
+ end
+ end
+ return val
+ end
+
+ o.cfgvalue = function(...)
+ local val = luci.cbi.Value.cfgvalue(...)
+ if val then
+ val = tools.parse_list(val)
+ for i, v in ipairs(cc_idd.CC_IDD) do
+ if type(v[2]) == "table" then
+ if v[2][1] == val[1] then
+ return "_%i" % i
+ end
+ else
+ if v[2] == val[1] then
+ return "_%i" % i
+ end
+ end
+ end
+ end
+ return val
+ end
+end
+
+
+--- LuCI Asterisk - Dialzone
+-- @type module
+dialzone = luci.util.class()
+
+--- Parse a dialzone section
+-- @param zone Table containing the zone info
+-- @return Table with parsed information
+function dialzone.parse(z)
+ if z['.name'] then
+ return {
+ trunks = tools.parse_list(z.uses),
+ name = z['.name'],
+ description = z.description or z['.name'],
+ addprefix = z.addprefix,
+ matches = tools.parse_list(z.match),
+ intlmatches = tools.parse_list(z.international),
+ countrycode = z.countrycode,
+ localzone = z.localzone,
+ localprefix = z.localprefix
+ }
+ end
+end
+
+--- Get a list of known dial zones
+-- @return Associative table of zones and table of zone names
+function dialzone.zones()
+ local zones = { }
+ local znames = { }
+ uci:foreach("asterisk", "dialzone",
+ function(z)
+ zones[z['.name']] = dialzone.parse(z)
+ znames[#znames+1] = z['.name']
+ end)
+ return zones, znames
+end
+
+--- Get a specific dial zone
+-- @param name Name of the dial zone
+-- @return Table containing zone information
+function dialzone.zone(n)
+ local zone
+ uci:foreach("asterisk", "dialzone",
+ function(z)
+ if z['.name'] == n then
+ zone = dialzone.parse(z)
+ end
+ end)
+ return zone
+end
+
+--- Find uci section hash for given zone number
+-- @param idx Zone number
+-- @return String containing the uci hash pointing to the section
+function dialzone.ucisection(i)
+ local hash
+ local index = 1
+ i = tonumber(i)
+ uci:foreach("asterisk", "dialzone",
+ function(z)
+ if not hash and index == i then
+ hash = z['.name']
+ end
+ index = index + 1
+ end)
+ return hash
+end
+
+
+--- LuCI Asterisk - Voicemailbox
+-- @type module
+voicemail = luci.util.class()
+
+--- Parse a voicemail section
+-- @param zone Table containing the mailbox info
+-- @return Table with parsed information
+function voicemail.parse(z)
+ if z.number and #z.number > 0 then
+ local v = {
+ id = '%s@%s' %{ z.number, z.context or 'default' },
+ number = z.number,
+ context = z.context or 'default',
+ name = z.name or z['.name'] or 'OpenWrt',
+ zone = z.zone or 'homeloc',
+ password = z.password or '0000',
+ email = z.email or '',
+ page = z.page or '',
+ dialplans = { }
+ }
+
+ uci:foreach("asterisk", "dialplanvoice",
+ function(s)
+ if s.dialplan and #s.dialplan > 0 and
+ s.voicebox == v.number
+ then
+ v.dialplans[#v.dialplans+1] = s.dialplan
+ end
+ end)
+
+ return v
+ end
+end
+
+--- Get a list of known voicemail boxes
+-- @return Associative table of boxes and table of box numbers
+function voicemail.boxes()
+ local vboxes = { }
+ local vnames = { }
+ uci:foreach("asterisk", "voicemail",
+ function(z)
+ local v = voicemail.parse(z)
+ if v then
+ local n = '%s@%s' %{ v.number, v.context }
+ vboxes[n] = v
+ vnames[#vnames+1] = n
+ end
+ end)
+ return vboxes, vnames
+end
+
+--- Get a specific voicemailbox
+-- @param number Number of the voicemailbox
+-- @return Table containing mailbox information
+function voicemail.box(n)
+ local box
+ n = n:gsub("@.+$","")
+ uci:foreach("asterisk", "voicemail",
+ function(z)
+ if z.number == tostring(n) then
+ box = voicemail.parse(z)
+ end
+ end)
+ return box
+end
+
+--- Find all voicemailboxes within the given dialplan
+-- @param plan Dialplan name or table
+-- @return Associative table containing extensions mapped to mailbox info
+function voicemail.in_dialplan(p)
+ local plan = type(p) == "string" and p or p.name
+ local boxes = { }
+ uci:foreach("asterisk", "dialplanvoice",
+ function(s)
+ if s.extension and #s.extension > 0 and s.dialplan == plan then
+ local box = voicemail.box(s.voicebox)
+ if box then
+ boxes[s.extension] = box
+ end
+ end
+ end)
+ return boxes
+end
+
+--- Remove voicemailbox and associated extensions from config
+-- @param box Voicemailbox number or table
+-- @param ctx UCI context to use (optional)
+-- @return Boolean indicating success
+function voicemail.remove(v, ctx)
+ ctx = ctx or uci
+ local box = type(v) == "string" and v or v.number
+ local ok1 = ctx:delete_all("asterisk", "voicemail", {number=box})
+ local ok2 = ctx:delete_all("asterisk", "dialplanvoice", {voicebox=box})
+ return ( ok1 or ok2 ) and true or false
+end
+
+
+--- LuCI Asterisk - MeetMe Conferences
+-- @type module
+meetme = luci.util.class()
+
+--- Parse a meetme section
+-- @param room Table containing the room info
+-- @return Table with parsed information
+function meetme.parse(r)
+ if r.room and #r.room > 0 then
+ local v = {
+ room = r.room,
+ pin = r.pin or '',
+ adminpin = r.adminpin or '',
+ description = r._description or '',
+ dialplans = { }
+ }
+
+ uci:foreach("asterisk", "dialplanmeetme",
+ function(s)
+ if s.dialplan and #s.dialplan > 0 and s.room == v.room then
+ v.dialplans[#v.dialplans+1] = s.dialplan
+ end
+ end)
+
+ return v
+ end
+end
+
+--- Get a list of known meetme rooms
+-- @return Associative table of rooms and table of room numbers
+function meetme.rooms()
+ local mrooms = { }
+ local mnames = { }
+ uci:foreach("asterisk", "meetme",
+ function(r)
+ local v = meetme.parse(r)
+ if v then
+ mrooms[v.room] = v
+ mnames[#mnames+1] = v.room
+ end
+ end)
+ return mrooms, mnames
+end
+
+--- Get a specific meetme room
+-- @param number Number of the room
+-- @return Table containing room information
+function meetme.room(n)
+ local room
+ uci:foreach("asterisk", "meetme",
+ function(r)
+ if r.room == tostring(n) then
+ room = meetme.parse(r)
+ end
+ end)
+ return room
+end
+
+--- Find all meetme rooms within the given dialplan
+-- @param plan Dialplan name or table
+-- @return Associative table containing extensions mapped to room info
+function meetme.in_dialplan(p)
+ local plan = type(p) == "string" and p or p.name
+ local rooms = { }
+ uci:foreach("asterisk", "dialplanmeetme",
+ function(s)
+ if s.extension and #s.extension > 0 and s.dialplan == plan then
+ local room = meetme.room(s.room)
+ if room then
+ rooms[s.extension] = room
+ end
+ end
+ end)
+ return rooms
+end
+
+--- Remove meetme room and associated extensions from config
+-- @param room Voicemailbox number or table
+-- @param ctx UCI context to use (optional)
+-- @return Boolean indicating success
+function meetme.remove(v, ctx)
+ ctx = ctx or uci
+ local room = type(v) == "string" and v or v.number
+ local ok1 = ctx:delete_all("asterisk", "meetme", {room=room})
+ local ok2 = ctx:delete_all("asterisk", "dialplanmeetme", {room=room})
+ return ( ok1 or ok2 ) and true or false
+end
+
+
+--- LuCI Asterisk - Dialplan
+-- @type module
+dialplan = luci.util.class()
+
+--- Parse a dialplan section
+-- @param plan Table containing the plan info
+-- @return Table with parsed information
+function dialplan.parse(z)
+ if z['.name'] then
+ local plan = {
+ zones = { },
+ name = z['.name'],
+ description = z.description or z['.name']
+ }
+
+ -- dialzones
+ for _, name in ipairs(tools.parse_list(z.include)) do
+ local zone = dialzone.zone(name)
+ if zone then
+ plan.zones[#plan.zones+1] = zone
+ end
+ end
+
+ -- voicemailboxes
+ plan.voicemailboxes = voicemail.in_dialplan(plan)
+
+ -- meetme conferences
+ plan.meetmerooms = meetme.in_dialplan(plan)
+
+ return plan
+ end
+end
+
+--- Get a list of known dial plans
+-- @return Associative table of plans and table of plan names
+function dialplan.plans()
+ local plans = { }
+ local pnames = { }
+ uci:foreach("asterisk", "dialplan",
+ function(p)
+ plans[p['.name']] = dialplan.parse(p)
+ pnames[#pnames+1] = p['.name']
+ end)
+ return plans, pnames
+end
+
+--- Get a specific dial plan
+-- @param name Name of the dial plan
+-- @return Table containing plan information
+function dialplan.plan(n)
+ local plan
+ uci:foreach("asterisk", "dialplan",
+ function(p)
+ if p['.name'] == n then
+ plan = dialplan.parse(p)
+ end
+ end)
+ return plan
+end