first commit
This commit is contained in:
commit
b7ff98764b
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
config.json
|
19
License.txt
Normal file
19
License.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2021 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Asterisk Outgoing Call API
|
||||||
|
|
||||||
|
Starts an outgoing call based on provided paramters according to the https://wiki.asterisk.org/wiki/display/AST/Asterisk+Call+Files call file format. View https://www.voip-info.org/asterisk-auto-dial-out/ for more details about the call file format. I created this to make my phone ring when my cell phone rings via a tasker profile. Should be useful for many additional things though.
|
||||||
|
|
||||||
|
# Accepted parameters
|
||||||
|
|
||||||
|
- token: The API token to authenticate with the server.
|
||||||
|
- channel: Channel to use for the call.
|
||||||
|
- caller_id: Caller ID, Please note: It may not work if you do not respect the format: CallerID: “Some Name” <1234>
|
||||||
|
- wait_time: Seconds to wait for an answer. Default is 45.
|
||||||
|
- max_retries: Number of retries before failing (not including the initial attempt, e.g. 0 = total of 1 attempt to make the call). Default is 0.
|
||||||
|
- retry_time: Seconds between retries, Don’t hammer an unavailable phone. The default is 300 (5 min).
|
||||||
|
- account: Set the account code to use.
|
||||||
|
- application: Asterisk Application to run (use instead of specifying context, extension and priority).
|
||||||
|
- data: The options to be passed to application.
|
||||||
|
- context: Context in extensions.conf
|
||||||
|
- extension: Extension definition in extensions.conf
|
||||||
|
- priority: Priority of extension to start with.
|
||||||
|
- set_var: Set of variables to set in url query format.
|
||||||
|
- archive: Yes/No – Move to subdir “outgoing_done” with “Status: value”, where value can be Completed, Expired or Failed.
|
||||||
|
- schedule: Schedule call for a later date/time. Can be natrual language input as parsed by https://github.com/olebedev/when
|
87
config.go
Normal file
87
config.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference from:
|
||||||
|
// https://wiki.asterisk.org/wiki/display/AST/Asterisk+Call+Files
|
||||||
|
|
||||||
|
// Config all configurations for this application.
|
||||||
|
type Config struct {
|
||||||
|
HTTPBind string `json:"http_bind"`
|
||||||
|
HTTPPort uint `json:"http_port"`
|
||||||
|
HTTPDebug bool `json:"http_debug"`
|
||||||
|
HTTPSystemDSocket bool `json:"http_systemd_socket"`
|
||||||
|
AsteriskSpoolDir string `json:"asterisk_spool_dir"`
|
||||||
|
DefaultChannel string `json:"default_channel"`
|
||||||
|
DefaultCallerId string `json:"default_caller_id"`
|
||||||
|
DefaultWaitTime uint64 `json:"default_wait_time"` // 5 seconds per ring.
|
||||||
|
DefaultMaxRetries uint64 `json:"default_max_retries"`
|
||||||
|
DefaultRetryTime uint64 `json:"default_retry_time"`
|
||||||
|
DefaultAccount string `json:"default_account"`
|
||||||
|
DefaultApplication string `json:"default_application"`
|
||||||
|
DefaultData string `json:"default_data"`
|
||||||
|
PreventAPIApplication bool `json:"prevent_api_application"` // For security, prevent applications from being executed via API call.
|
||||||
|
DefaultContext string `json:"default_context"`
|
||||||
|
DefaultExtension string `json:"default_extension"`
|
||||||
|
DefaultPriority string `json:"default_priority"`
|
||||||
|
DefaultSetVar map[string]string `json:"default_set_var"`
|
||||||
|
DefaultArchive bool `json:"default_archive"`
|
||||||
|
APIToken string `json:"api_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConfig read the configuration file into the config structure of the app.
|
||||||
|
func (a *App) ReadConfig() {
|
||||||
|
// Get our current user for use in determining the home path.
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different configuration file paths.
|
||||||
|
localConfig, _ := filepath.Abs("./config.json")
|
||||||
|
homeDirConfig := usr.HomeDir + "/.config/asterisk-outgoing-call-api/config.json"
|
||||||
|
etcConfig := "/etc/asterisk/outgoing-call-api.json"
|
||||||
|
|
||||||
|
// Store defaults first.
|
||||||
|
app.config = Config{
|
||||||
|
HTTPPort: 9747,
|
||||||
|
HTTPDebug: false,
|
||||||
|
HTTPSystemDSocket: false,
|
||||||
|
AsteriskSpoolDir: "/var/spool/asterisk",
|
||||||
|
PreventAPIApplication: true,
|
||||||
|
DefaultArchive: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which config file to use.
|
||||||
|
var configFile string
|
||||||
|
if _, err := os.Stat(app.flags.ConfigPath); err == nil && app.flags.ConfigPath != "" {
|
||||||
|
configFile = app.flags.ConfigPath
|
||||||
|
} else if _, err := os.Stat(localConfig); err == nil {
|
||||||
|
configFile = localConfig
|
||||||
|
} else if _, err := os.Stat(homeDirConfig); err == nil {
|
||||||
|
configFile = homeDirConfig
|
||||||
|
} else if _, err := os.Stat(etcConfig); err == nil {
|
||||||
|
configFile = etcConfig
|
||||||
|
} else {
|
||||||
|
log.Fatal("Unable to find a configuration file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the config file.
|
||||||
|
jsonFile, err := ioutil.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error reading JSON file: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the config file into the configuration structure.
|
||||||
|
err = json.Unmarshal(jsonFile, &app.config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error parsing JSON file: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
6
example.call
Normal file
6
example.call
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Channel: pjsip/103
|
||||||
|
WaitTime: 15
|
||||||
|
Context: Phone-Ring-Dummy-Answer
|
||||||
|
Extension: talk
|
||||||
|
Priority: 1
|
||||||
|
Archive: no
|
4
extension.conf
Normal file
4
extension.conf
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[Phone-Ring-Dummy-Answer]
|
||||||
|
exten => talk,1,Answer()
|
||||||
|
same => n,Playback(all-your-base)
|
||||||
|
same => n,Hangup()
|
45
flags.go
Normal file
45
flags.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flags are command line tick options.
|
||||||
|
type Flags struct {
|
||||||
|
ConfigPath string
|
||||||
|
HTTPBind string
|
||||||
|
HTTPPort uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init configures the golang flags and parses the command line provided options.
|
||||||
|
func (f *Flags) Init() {
|
||||||
|
flag.Usage = func() {
|
||||||
|
fmt.Printf("asterisk-outgoing-call-api: Make an outgoing call via an API call.\n\nUsage:\n")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
var printVersion bool
|
||||||
|
flag.BoolVar(&printVersion, "v", false, "Print version")
|
||||||
|
|
||||||
|
var usage string
|
||||||
|
usage = "Load configuration from file."
|
||||||
|
flag.StringVar(&f.ConfigPath, "config", "", usage)
|
||||||
|
flag.StringVar(&f.ConfigPath, "c", "", usage+" (shorthand)")
|
||||||
|
|
||||||
|
usage = "Bind address for http server"
|
||||||
|
flag.StringVar(&f.HTTPBind, "http-bind", "", usage)
|
||||||
|
flag.StringVar(&f.HTTPBind, "b", "", usage+" (shorthand)")
|
||||||
|
|
||||||
|
usage = "Bind port for http server"
|
||||||
|
flag.UintVar(&f.HTTPPort, "http-port", 0, usage)
|
||||||
|
flag.UintVar(&f.HTTPPort, "p", 0, usage+" (shorthand)")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if printVersion {
|
||||||
|
fmt.Println("asterisk-outgoing-call-api: 0.1")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module github.com/GRMrGecko/asterisk-outgoing-call-api
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
|
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254
|
||||||
|
)
|
16
go.sum
Normal file
16
go.sum
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE=
|
||||||
|
github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 h1:JYoQR67E1vv1WGoeW8DkdFs7vrIEe/5wP+qJItd5tUE=
|
||||||
|
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
357
http.go
Normal file
357
http.go
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-systemd/activation"
|
||||||
|
"github.com/olebedev/when"
|
||||||
|
"github.com/olebedev/when/rules/common"
|
||||||
|
"github.com/olebedev/when/rules/en"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPServer the http server structure.
|
||||||
|
type HTTPServer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common strings.
|
||||||
|
const (
|
||||||
|
APIOK = "ok"
|
||||||
|
APIERR = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIGeneralResp General response to API requests.
|
||||||
|
type APIGeneralResp struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONResponse Takes a golang structure and converts it to a JSON object for response.
|
||||||
|
func (s *HTTPServer) JSONResponse(w http.ResponseWriter, resp interface{}) {
|
||||||
|
// Encode response as json.
|
||||||
|
js, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
// Error should not happen normally...
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no err, we can set content type header and send response.
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(js)
|
||||||
|
w.Write([]byte{'\n'})
|
||||||
|
}
|
||||||
|
|
||||||
|
// APISendGeneralResp Send a standard response.
|
||||||
|
func (s *HTTPServer) APISendGeneralResp(w http.ResponseWriter, status, err string) {
|
||||||
|
resp := APIGeneralResp{}
|
||||||
|
resp.Status = status
|
||||||
|
resp.Error = err
|
||||||
|
s.JSONResponse(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerHandlers HTTP server handlers.
|
||||||
|
func (s *HTTPServer) registerHandlers(r *http.ServeMux) {
|
||||||
|
// For this project, we only handle requests to /.
|
||||||
|
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Parse form data.
|
||||||
|
err := r.ParseMultipartForm(32 << 20)
|
||||||
|
if err == http.ErrNotMultipart {
|
||||||
|
err = r.ParseForm()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
s.APISendGeneralResp(w, APIERR, "Bad request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify we are authorized.
|
||||||
|
if r.Form.Get("token") != app.config.APIToken {
|
||||||
|
s.APISendGeneralResp(w, APIERR, "Unauthorized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get call details.
|
||||||
|
channel := r.Form.Get("channel")
|
||||||
|
if channel == "" {
|
||||||
|
channel = app.config.DefaultChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
callerId := r.Form.Get("caller_id")
|
||||||
|
if callerId == "" {
|
||||||
|
callerId = app.config.DefaultCallerId
|
||||||
|
}
|
||||||
|
|
||||||
|
waitTime, err := strconv.ParseUint(r.Form.Get("wait_time"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
waitTime = app.config.DefaultWaitTime
|
||||||
|
}
|
||||||
|
|
||||||
|
maxRetries, err := strconv.ParseUint(r.Form.Get("max_retries"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
maxRetries = app.config.DefaultMaxRetries
|
||||||
|
}
|
||||||
|
|
||||||
|
retryTime, err := strconv.ParseUint(r.Form.Get("retry_time"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
retryTime = app.config.DefaultRetryTime
|
||||||
|
}
|
||||||
|
|
||||||
|
account := r.Form.Get("account")
|
||||||
|
if account == "" {
|
||||||
|
account = app.config.DefaultCallerId
|
||||||
|
}
|
||||||
|
|
||||||
|
application := r.Form.Get("application")
|
||||||
|
if application == "" || app.config.PreventAPIApplication {
|
||||||
|
application = app.config.DefaultApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
data := r.Form.Get("data")
|
||||||
|
if data == "" || app.config.PreventAPIApplication {
|
||||||
|
data = app.config.DefaultData
|
||||||
|
}
|
||||||
|
|
||||||
|
context := r.Form.Get("context")
|
||||||
|
if context == "" {
|
||||||
|
context = app.config.DefaultContext
|
||||||
|
}
|
||||||
|
|
||||||
|
extension := r.Form.Get("extension")
|
||||||
|
if context == "" {
|
||||||
|
extension = app.config.DefaultExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
priority := r.Form.Get("priority")
|
||||||
|
if context == "" {
|
||||||
|
priority = app.config.DefaultPriority
|
||||||
|
}
|
||||||
|
|
||||||
|
setVar := make(map[string]string)
|
||||||
|
parsed, err := url.ParseQuery(r.Form.Get("set_var"))
|
||||||
|
if err != nil {
|
||||||
|
setVar = app.config.DefaultSetVar
|
||||||
|
} else {
|
||||||
|
for key, value := range parsed {
|
||||||
|
setVar[key] = value[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
archiveVal := strings.ToLower(r.Form.Get("archive"))
|
||||||
|
archive := false
|
||||||
|
if archiveVal == "true" || archiveVal == "yes" {
|
||||||
|
archive = true
|
||||||
|
} else if archiveVal != "false" && archiveVal != "no" {
|
||||||
|
archive = app.config.DefaultArchive
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule := r.Form.Get("schedule")
|
||||||
|
|
||||||
|
if channel == "" || (application == "" && context == "") {
|
||||||
|
s.APISendGeneralResp(w, APIERR, "Required options not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup call file details.
|
||||||
|
outgoingCallName := "outgoing-call-" + strconv.Itoa(rand.Int())
|
||||||
|
spoolFileName := path.Join(app.config.AsteriskSpoolDir, outgoingCallName)
|
||||||
|
outgoingFileName := path.Join(app.config.AsteriskSpoolDir, "outgoing", outgoingCallName)
|
||||||
|
|
||||||
|
callFile, err := os.Create(spoolFileName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
s.APISendGeneralResp(w, APIERR, "Unable to create call file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write call details.
|
||||||
|
callFile.WriteString("Channel: " + channel + "\n")
|
||||||
|
|
||||||
|
if callerId != "" {
|
||||||
|
callFile.WriteString("Callerid: " + callerId + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if waitTime != 0 {
|
||||||
|
callFile.WriteString("WaitTime: " + strconv.FormatUint(waitTime, 10) + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxRetries != 0 {
|
||||||
|
callFile.WriteString("MaxRetries: " + strconv.FormatUint(maxRetries, 10) + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if retryTime != 0 {
|
||||||
|
callFile.WriteString("RetryTime: " + strconv.FormatUint(retryTime, 10) + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if account != "" {
|
||||||
|
callFile.WriteString("Account: " + account + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if application != "" {
|
||||||
|
callFile.WriteString("Application: " + application + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if data != "" {
|
||||||
|
callFile.WriteString("Data: " + data + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if context != "" {
|
||||||
|
callFile.WriteString("Context: " + context + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if extension != "" {
|
||||||
|
callFile.WriteString("Extension: " + extension + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if priority != "" {
|
||||||
|
callFile.WriteString("Priority: " + priority + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range setVar {
|
||||||
|
callFile.WriteString("Setvar: " + key + "=" + value + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if archive {
|
||||||
|
callFile.WriteString("Archive: yes\n")
|
||||||
|
} else {
|
||||||
|
callFile.WriteString("Archive: no\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
callFile.Close()
|
||||||
|
|
||||||
|
if schedule != "" {
|
||||||
|
now := time.Now()
|
||||||
|
w := when.New(nil)
|
||||||
|
w.Add(en.All...)
|
||||||
|
w.Add(common.All...)
|
||||||
|
parsedTime, _ := w.Parse(schedule, now)
|
||||||
|
if parsedTime == nil {
|
||||||
|
parsedTime = new(when.Result)
|
||||||
|
parsedTime.Time = now
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Chtimes(spoolFileName, parsedTime.Time, parsedTime.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add call to the outgoing call queue.
|
||||||
|
err = os.Rename(spoolFileName, outgoingFileName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
s.APISendGeneralResp(w, APIERR, "Unable to move call file into outgoing directory")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send final response.
|
||||||
|
s.APISendGeneralResp(w, APIOK, "")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTTPServe() {
|
||||||
|
// Used to reset the app quit timeout for systemd sockets.
|
||||||
|
var timeoutReset chan struct{}
|
||||||
|
|
||||||
|
// Create the server.
|
||||||
|
httpServer := new(HTTPServer)
|
||||||
|
app.httpServer = httpServer
|
||||||
|
|
||||||
|
// Setup the handlers.
|
||||||
|
r := http.NewServeMux()
|
||||||
|
httpServer.registerHandlers(r)
|
||||||
|
|
||||||
|
// The http server handler will be the mux router by default.
|
||||||
|
var handler http.Handler
|
||||||
|
handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if app.config.HTTPSystemDSocket {
|
||||||
|
timeoutReset <- struct{}{}
|
||||||
|
}
|
||||||
|
if app.config.HTTPDebug {
|
||||||
|
log.Println(req.Method + " " + req.URL.String())
|
||||||
|
}
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Determine if we're using a systemd socket activation or just a standard listen.
|
||||||
|
if app.config.HTTPSystemDSocket {
|
||||||
|
done := make(chan struct{})
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
timeoutReset = make(chan struct{})
|
||||||
|
|
||||||
|
// On signal, gracefully shut down the server and wait 5
|
||||||
|
// seconds for current connection to stop.
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// Pull existing listener from systemd.
|
||||||
|
listeners, err := activation.Listeners()
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Cannot retrieve listeners: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already have a asterisk-outgoing-call-api running, then we shouldn't start...
|
||||||
|
if len(listeners) != 1 {
|
||||||
|
log.Panicf("Unexpected number of socket activation (%d != 1)", len(listeners))
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upon signal, close out existing connection and quit.
|
||||||
|
go func() {
|
||||||
|
<-quit
|
||||||
|
log.Println("Server is shutting down")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
server.SetKeepAlivesEnabled(false)
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
log.Panicf("Cannot gracefully shut down the server: %v", err)
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 30 minute time out if no connection is received.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timeoutReset:
|
||||||
|
case <-time.After(30 * time.Minute):
|
||||||
|
close(quit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Listen on existing systemd socket.
|
||||||
|
server.Serve(listeners[0])
|
||||||
|
|
||||||
|
// Wait for existing connections befor exiting.
|
||||||
|
<-done
|
||||||
|
} else {
|
||||||
|
// Get the configuration.
|
||||||
|
httpBind := app.config.HTTPBind
|
||||||
|
httpPort := app.config.HTTPPort
|
||||||
|
if app.flags.HTTPBind != "" {
|
||||||
|
httpBind = app.flags.HTTPBind
|
||||||
|
}
|
||||||
|
if app.flags.HTTPPort != 0 {
|
||||||
|
httpPort = app.flags.HTTPPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the server.
|
||||||
|
log.Println("Starting the http server on port", httpPort)
|
||||||
|
err := http.ListenAndServe(fmt.Sprintf("%s:%d", httpBind, httpPort), handler)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
main.go
Normal file
27
main.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App is the standard structure that allows different parts of the application to access common parameters/configuration.
|
||||||
|
type App struct {
|
||||||
|
flags *Flags
|
||||||
|
httpServer *HTTPServer
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
var app *App
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// We use rand for file naming, best set seed at start.
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
app = new(App)
|
||||||
|
app.flags = new(Flags)
|
||||||
|
app.flags.Init()
|
||||||
|
app.ReadConfig()
|
||||||
|
|
||||||
|
HTTPServe()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user