Asterisk Dialplan Extension Mask Generator
The system administrator who works with Asterisk often needs to compile masks for dialplan extensions at intervals of DEF codes. Manually doing it is time consuming, and almost the only working script that can be found on the Internet is actually not that working.
I hope someone can use a small cli generator for go, which I recently had to write in order to update the existing dialplan.
Opportunities:
Sources and binaries for Mac OS and Linux on GitHub
I hope someone can use a small cli generator for go, which I recently had to write in order to update the existing dialplan.
Opportunities:
- filter by region
- filter by operator
- simple output formatting
- asterisk style interval comments
- carrier grouping
Sources and binaries for Mac OS and Linux on GitHub
Code
package main
import (
"encoding/csv""fmt""io/ioutil""log""net/http""os""sort""strconv""strings""golang.org/x/text/encoding/charmap"
)
type params struct {
URL string
Region string
Operator string
Comment bool
Prefix string
Suffix string
Group bool
}
funcmain() {
p := readArgs()
var values [][]stringif p.Region != "" {
values = filterRegion(parse(getCodes(p.URL)), p.Region)
} else {
values = parse(getCodes(p.URL))
}
if p.Operator != "" {
values = filterOperator(values, p.Operator)
}
if p.Group {
sort.Slice(values, func(i, j int)bool { return values[i][4] < values[j][4] })
}
op := ""for _, v := range values {
_, min, max, dif := convert(v)
if !validate(min, max, dif) {
fmt.Printf("wrong interval: from %d to %d != %d\n", min, max, dif)
continue
}
if p.Comment {
fmt.Printf("; %v, %v, %v, %v, %v, %v\n", v[0], v[1], v[2], v[3], v[4], v[5])
}
if p.Group {
if v[4] != op {
fmt.Printf("; %s\n", v[4])
op = v[4]
}
}
iflen([]rune(v[0])) != 3 || len([]rune(v[1])) != 7 || len([]rune(v[2])) != 7 {
fmt.Printf("wrong interval: from %d to %d != %d\n", min, max, dif)
continue
}
compute(p.Prefix+v[0], v[1], v[2], ""+p.Suffix)
}
}
funcgetCodes(url string)string {
var client http.Client
res, err := client.Get(url)
if err != nil {
log.Panic(err)
}
defer res.Body.Close()
if res.StatusCode == http.StatusOK {
b, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Panic(err)
}
dec := charmap.Windows1251.NewDecoder()
body := make([]byte, len(b)*2)
n, _, err := dec.Transform(body, []byte(b), false)
if err != nil {
log.Println(err)
}
returnstring(body[:n])
}
log.Panic("bad response")
return""
}
// returns slice without row names// element 0: code// 1: min value of interval// 2: max value of interval// 3: interval length// 4: cellular operator// 5: regionfuncparse(data string) [][]string {
codes := csv.NewReader(strings.NewReader(fixCodes(data, 6)))
codes.LazyQuotes = true
codes.Comma = ';'
ext, err := codes.ReadAll()
if err != nil {
log.Println(err)
}
return ext[1:]
}
// I found three records with excess separators// in https://rossvyaz.gov.ru/docs/articles/DEF-9x.csv// (start at 955;5550000;5559999;10000 ...)funcfixCodes(data string, f int)string {
c := 0var x []runefor _, v := range []rune(data) {
if v == '\n' {
c = 0
}
// replace excess separators with spacesif v == ';' {
c++
if c > f-1 {
v = ' '
}
}
x = append(x, v)
}
returnstring(x)
}
funcfilterRegion(values [][]string, region string) [][]string {
var res [][]stringfor _, v := range values {
if strings.Contains(strings.ToLower(v[5]), strings.ToLower(region)) {
res = append(res, v)
}
}
return res
}
funcfilterOperator(values [][]string, operator string) [][]string {
var res [][]stringfor _, v := range values {
if strings.Contains(strings.ToLower(v[4]), strings.ToLower(operator)) {
res = append(res, v)
}
}
return res
}
funcvalidate(min, max, dif int)bool {
if max-min == dif-1 {
returntrue
}
returnfalse
}
funcconvert(v []string)(pre, min, max, dif int) {
pre, err := strconv.Atoi(v[0])
if err != nil {
log.Panic(err)
}
min, err = strconv.Atoi(v[1])
if err != nil {
log.Panic(err)
}
max, err = strconv.Atoi(v[2])
if err != nil {
log.Panic(err)
}
dif, err = strconv.Atoi(v[3])
if err != nil {
log.Panic(err)
}
return
}
type runes []runefuncincrement(r runes)runes {
iflen(r) <= 1 {
return r
}
var res runes
i, err := strconv.Atoi(string(r))
if err != nil {
log.Panic(err)
}
i++
res = runes(strconv.Itoa(i))
iflen(res) == len(r) {
return res
}
res = res.reverse()
l := len(res)
for i := 0; i < len(r)-l; i++ {
res = append(res, '0')
}
return res.reverse()
}
func(r runes)reverse()(rev []rune) {
for i := len(r) - 1; i >= 0; i-- {
rev = append(rev, r[i])
}
return
}
funchi(r runes)runes {
var res runes
for i := len(r) - 1; i >= 1; i-- {
res = append(res, '9')
if r[i] == '0' {
continue
}
break
}
l := len(res)
r = r.reverse()
for i := l; i < len(r); i++ {
res = append(res, r[i])
}
return res.reverse()
}
funcdecrement(r runes)runes {
iflen(r) <= 1 {
return r
}
var res runes
i, err := strconv.Atoi(string(r))
if err != nil {
log.Panic(err)
}
i--
res = runes(strconv.Itoa(i))
iflen(res) == len(r) {
return res
}
res = res.reverse()
l := len(res)
for i := 0; i < len(r)-l; i++ {
res = append(res, '0')
}
return res.reverse()
}
funclow(r runes)runes {
var res runes
res = append(res, r[0])
for i := 2; i <= len(r); i++ {
res = append(res, '0')
}
return res
}
funccompute(pre, min, max, suf string) {
// mask found if min and max are equalif min == max {
fmt.Printf("%v%v%v\n", pre, min, suf)
return
}
var prefix []rune
mi := runes(min)
ma := runes(max)
iflen(mi) != len(ma) {
log.Panic("the length of min and max values is not equal")
}
for k, v := range ma {
if v == mi[k] {
prefix = append(prefix, v)
continue
}
break
}
if l := len(prefix); l != 0 {
compute(pre+string(prefix), string(mi[l:]), string(ma[l:]), suf)
return
}
var suffix runes
for k, v := range mi.reverse() {
if ma.reverse()[k]-v == 9 {
suffix = append(suffix, 'X')
continue
}
break
}
if l := len(suffix); l != 0 {
compute(pre,
string(mi)[:len(mi)-l],
string(ma)[:len(ma)-l],
string(suffix)+suf)
return
}
iflen(mi) == 1 {
compute(pre+"["+string(mi)+"-"+string(ma)+"]", "", "", suf)
return
}
zc, err := strconv.Atoi(min)
if err != nil {
log.Panic(err)
}
if zc == 0 {
compute(pre, string(mi), string(decrement(low(ma))), suf)
compute(pre, string(low(ma)), max, suf)
return
}
compute(pre, string(mi), string(hi(mi)), suf)
if increment(hi(mi))[0] == mi[0] {
compute(pre, string(increment(hi(mi))), max, suf)
} else {
if increment(hi(mi))[0] == ma[0] {
compute(pre, string(increment(hi(mi))), max, suf)
} else {
compute(pre, string(increment(hi(mi))), string(decrement(low(ma))), suf)
compute(pre, string(low(ma)), max, suf)
}
}
}
funcreadArgs()params {
p := params{
URL: "https://rossvyaz.gov.ru/docs/articles/DEF-9x.csv",
Region: "",
Operator: "",
Comment: false,
Prefix: "",
Suffix: "",
Group: false,
}
wait := false
key := ""for _, v := range os.Args[1:] {
if wait {
switch key {
case"-u":
p.URL = v
case"-r":
p.Region = v
case"-o":
p.Operator = v
case"-p":
p.Prefix = v
case"-s":
p.Suffix = v
}
wait = false
} else {
if v == "-c" {
p.Comment = truecontinue
}
if v == "-g" {
p.Group = truecontinue
}
switch v {
case"-u", "-r", "-o", "-p", "-s":
key = v
wait = truecase"-h":
help()
os.Exit(0)
default:
fmt.Println("unknown option:", v)
fmt.Println("show help: genmask -h")
os.Exit(1)
}
}
}
if wait == true {
fmt.Printf("missing value of %s argument\n", key)
os.Exit(1)
}
return p
}
funchelp() {
fmt.Println("usage: genmask [-u <url>] [-r <region filter>] [-c] [-p <prefix>] [-s <suffix>]")
fmt.Println("\t-u <value>: url to csv file. Default is https://rossvyaz.gov.ru/docs/articles/DEF-9x.csv")
fmt.Println("\t-r <value>: find entries in the csv file that contain the value in region field.")
fmt.Println("\t It's better to use short masks, because errors and typos are possible in the csv file.")
fmt.Println("\t-o <value>: find entries in the csv file that contain the value in operator field.")
fmt.Println("\t It's better to use short masks, because errors and typos are possible in the csv file.")
fmt.Println("\t-c Print a comment: <; code, min, max, length, cellular operator, region> before each interval")
fmt.Println("\t-p <value>: Print a prefix for each mask")
fmt.Println("\t-s <value>: Print a suffix for each mask")
fmt.Println("\t-g <value>: Group output by cellular operator")
fmt.Println("show this help: genmask -h")
}
Sample output
genmask -r амурск -g
; АО "Компания ТрансТелеКом"
958034XXXX
; ОАО "МТТ"
95840430XX
; ООО "Газпром телеком"
9584586XXX
; ООО "Глобал Телеком"
958385[0-1]XXX
; ООО "Скартел"
99916[5-6]XXXX
99646[0-2]XXXX
99638[3-7]XXXX
999681[0-4]XXX
991116[8-9]XXX
9911170XXX
; ООО "Т2 Мобайл"
90101368XX
994200XXXX
; ПАО "Вымпел-Коммуникации"
968246XXXX
9696392XXX
965677XXXX
96567[0-2]XXXX
96384[2-9]XXXX
96381[8-9]XXXX
96381[0-7]XXXX
90989[3-5]XXXX
96380XXXXX
96228[3-5]XXXX
96195XXXXX
90988[3-5]XXXX
90981XXXXX
96813[2-4]XXXX
96229[3-5]XXXX
; ПАО "МегаФон"
999251[5-9]XXX
92949[3-4]XXXX
92949[0-1]XXXX
92947[5-9]XXXX
924748XXXX
92484XXXXX
92474[4-6]XXXX
92467XXXXX
92468[0-4]XXXX
92458[0-4]XXXX
92444XXXXX
92434XXXXX
934476XXXX
92404[0-1]XXXX
924025[4-7]XXX
999254[7-8]XXX
92414XXXXX
; ПАО "Мобильные ТелеСистемы"
91453[0-2]XXXX
91406[0-4]XXXX
91404XXXXX
914538XXXX
914539[0-2]XXX
9145[5-9]XXXXX
9146[0-1]XXXXX
9143[8-9]XXXXX