From 238b0dc0046899ac13875a94f13d1df81f068528 Mon Sep 17 00:00:00 2001 From: Finn Date: Mon, 8 Oct 2018 17:37:39 -0700 Subject: [PATCH] Initial commit, including signald library and signald-cli tool --- .gitignore | 12 +++++++ .gitlab-ci.yml | 28 +++++++++++++++ signald-cli/cmd/root.go | 55 ++++++++++++++++++++++++++++ signald-cli/cmd/send.go | 76 +++++++++++++++++++++++++++++++++++++++ signald-cli/main.go | 7 ++++ signald/signald.go | 50 ++++++++++++++++++++++++++ signald/signaldrequest.go | 40 +++++++++++++++++++++ 7 files changed, 268 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 signald-cli/cmd/root.go create mode 100644 signald-cli/cmd/send.go create mode 100644 signald-cli/main.go create mode 100644 signald/signald.go create mode 100644 signald/signaldrequest.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1c181e --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..750cfab --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,28 @@ +stages: + - lint + - build + +lint: + image: golang:latest + stage: lint + prepare_script: + - mkdir -p /go/git.callpipe.com/finn/signald-go + - cp -r * /go/git.callpipe.com/finn/signald-go + - cd /go/git.callpipe.com/finn/signald-go + script: + - gofmt -d . + +build: + stage: build + image: golang:latest + prepare_script: + - mkdir -p /go/git.callpipe.com/finn/signald-go + - cp -r * /go/git.callpipe.com/finn/signald-go + - cd /go/git.callpipe.com/finn/signald-go + script: + - go get ./... # TODO: Improve how dependencies are handled + - go build -o "${CI_PROJECT_DIR}/signald-cli" signald-cli + artifacts: + paths: + - signald-cli + expire_in: 1 month diff --git a/signald-cli/cmd/root.go b/signald-cli/cmd/root.go new file mode 100644 index 0000000..1b8048c --- /dev/null +++ b/signald-cli/cmd/root.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "git.callpipe.com/finn/signald-go/signald" +) + +var cfgFile string +var socketPath string +var s *signald.Signald + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "signald-cli", + Short: "Interact with a running siangld instance", + Long: `signald-cli is a command line tool to interact with signald.`, +} + +// Execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.signald-cli.yaml)") + RootCmd.PersistentFlags().StringVarP(&socketPath, "socket", "s", "/var/run/signald/signald.socket", "the path to the signald socket file") + s = &signald.Signald{} + s.Connect() +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + } + + viper.SetConfigName(".signald-cli") // name of config file (without extension) + viper.AddConfigPath("$HOME") // adding home directory as first search path + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/signald-cli/cmd/send.go b/signald-cli/cmd/send.go new file mode 100644 index 0000000..8ccb7d7 --- /dev/null +++ b/signald-cli/cmd/send.go @@ -0,0 +1,76 @@ +// Copyright © 2018 Finn Herzfeld +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "log" + + "github.com/spf13/cobra" + + "git.callpipe.com/finn/signald-go/signald" +) + +var ( + username string + toUser string + toGroup string + messageBody string + attachment string +) + +// sendCmd represents the send command +var sendCmd = &cobra.Command{ + Use: "send", + Short: "send a message to another user or group", + Long: `send a message to another user or group on Signal`, + Run: func(cmd *cobra.Command, args []string) { + request := signald.Request{ + Type: "send", + Username: username, + } + + if toUser != "" { + request.RecipientNumber = toUser + } else if toGroup != "" { + request.RecipientGroupID = toGroup + } else { + log.Fatal("--to or --group must be specified!") + } + + if messageBody != "" { + request.MessageBody = messageBody + } + + if attachment != "" { + request.AttachmentFilenames = []string{attachment} + } + s.Send(request) + }, +} + +func init() { + RootCmd.AddCommand(sendCmd) + + sendCmd.Flags().StringVarP(&username, "username", "u", "", "The username to send from (required)") + sendCmd.MarkFlagRequired("username") // Misleading: cobra doesn't actually do anything to enforce "required" flags + + sendCmd.Flags().StringVarP(&toUser, "to", "t", "", "The user to send the message to (cannot be combined with --group)") + + sendCmd.Flags().StringVarP(&toGroup, "group", "g", "", "The group to send the message to (cannot be combined with --to)") + + sendCmd.Flags().StringVarP(&messageBody, "message", "m", "", "The text of the message to send") + + sendCmd.Flags().StringVarP(&attachment, "attachment", "a", "", "A file to attach to the message") +} diff --git a/signald-cli/main.go b/signald-cli/main.go new file mode 100644 index 0000000..20b494d --- /dev/null +++ b/signald-cli/main.go @@ -0,0 +1,7 @@ +package main + +import "git.callpipe.com/finn/signald-go/signald-cli/cmd" + +func main() { + cmd.Execute() +} diff --git a/signald/signald.go b/signald/signald.go new file mode 100644 index 0000000..6ba6d31 --- /dev/null +++ b/signald/signald.go @@ -0,0 +1,50 @@ +package signald + +import ( + "encoding/json" + "net" + "log" +) + + +// Signald is a connection to a signald instance. +type Signald struct { + socket net.Conn +} + +func crash(err error) { + if err != nil { + panic(err) + } +} + +// Connect connects to the signad socket +func (s *Signald) Connect() { + socket, err := net.Dial("unix", "/var/run/signald/signald.sock") + crash(err) + s.socket = socket + log.Print("Connected to signald socket ", socket.RemoteAddr().String()) +} + +// Listen listens for events from signald +func (s *Signald) Listen(c chan interface{}) { + // we create a decoder that reads directly from the socket + d := json.NewDecoder(s.socket) + + var msg interface{} + + for { + crash(d.Decode(&msg)) + log.Print(msg) + c <- msg + } +} + +// Send sends a request to signald. +func (s *Signald) Send(request Request) { + b, err := json.Marshal(request) + crash(err) + log.Print("Sending ", string(b)) + e := json.NewEncoder(s.socket) + e.Encode(request) +} diff --git a/signald/signaldrequest.go b/signald/signaldrequest.go new file mode 100644 index 0000000..68612e6 --- /dev/null +++ b/signald/signaldrequest.go @@ -0,0 +1,40 @@ +package signald + +/* The class in signald: +class JsonRequest { + public String type; + public String id; + public String username; + public String messageBody; + public String recipientNumber; + public String recipientGroupId; + public Boolean voice; + public String code; + public String deviceName; + public List attachmentFilenames; + public String uri; + public String groupName; + public List members; + public String avatar; + + JsonRequest() {} +} +*/ + +// Request represents a message sent to signald +type Request struct { + Type string `json:"type"` + ID string `json:"id,omitempty"` + Username string `json:"username,omitempty"` + MessageBody string `json:"messageBody,omitempty"` + RecipientNumber string `json:"recipientNumber,omitempty"` + RecipientGroupID string `json:"recipientGroupId,omitempty"` + Voice bool `json:"voice,omitempty"` + Code string `json:"code,omitempty"` + DeviceName string `json:"deviceName,omitempty"` + AttachmentFilenames []string `json:"attachmentFilenames,omitempty"` + URI string `json:"uri,omitempty"` + GroupName string `json:"groupName,omitempty"` + Members []string `json:"members,omitempty"` + Avatar string `json:"avatar,omitempty"` +}