123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746 |
- -- Copyright 2009 Jo-Philipp Wich <jow@openwrt.org>
- -- Licensed to the public under the Apache License 2.0.
- 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
|