diff --git a/.gitignore b/.gitignore
index 485dee6..138bbf8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
.idea
+*.session
diff --git a/config/bridge.go b/config/bridge.go
new file mode 100644
index 0000000..97ebaf8
--- /dev/null
+++ b/config/bridge.go
@@ -0,0 +1,76 @@
+// mautrix-whatsapp - A Matrix-Whatsapp puppeting bridge.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package config
+
+import (
+ "bytes"
+ "text/template"
+)
+
+type BridgeConfig struct {
+ RawUsernameTemplate string `yaml:"username_template"`
+ RawDisplaynameTemplate string `yaml:"displayname_template"`
+ UsernameTemplate *template.Template `yaml:"-"`
+ DisplaynameTemplate *template.Template `yaml:"-"`
+}
+
+func (bc BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ err := unmarshal(bc)
+ if err != nil {
+ return err
+ }
+
+ bc.UsernameTemplate, err = template.New("username").Parse(bc.RawUsernameTemplate)
+ if err != nil {
+ return err
+ }
+
+ bc.DisplaynameTemplate, err = template.New("displayname").Parse(bc.RawDisplaynameTemplate)
+ return err
+}
+
+type DisplaynameTemplateArgs struct {
+ Displayname string
+}
+
+type UsernameTemplateArgs struct {
+ Receiver string
+ UserID string
+}
+
+func (bc BridgeConfig) FormatDisplayname(displayname string) string {
+ var buf bytes.Buffer
+ bc.DisplaynameTemplate.Execute(&buf, DisplaynameTemplateArgs{
+ Displayname: displayname,
+ })
+ return buf.String()
+}
+
+func (bc BridgeConfig) FormatUsername(receiver, userID string) string {
+ var buf bytes.Buffer
+ bc.UsernameTemplate.Execute(&buf, UsernameTemplateArgs{
+ Receiver: receiver,
+ UserID: userID,
+ })
+ return buf.String()
+}
+
+func (bc BridgeConfig) MarshalYAML() (interface{}, error) {
+ bc.RawDisplaynameTemplate = bc.FormatDisplayname("{{.Displayname}}")
+ bc.RawUsernameTemplate = bc.FormatUsername("{{.Receiver}}", "{{.UserID}}")
+ return bc, nil
+}
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..9690258
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,81 @@
+// mautrix-whatsapp - A Matrix-Whatsapp puppeting bridge.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package config
+
+import (
+ "maunium.net/go/mautrix-appservice"
+ "io/ioutil"
+ "gopkg.in/yaml.v2"
+)
+
+type Config struct {
+ Homeserver struct {
+ Address string `yaml:"address"`
+ Domain string `yaml:"domain"`
+ } `yaml:"homeserver"`
+
+ AppService struct {
+ Address string `yaml:"address"`
+ Hostname string `yaml:"hostname"`
+ Port uint16 `yaml:"port"`
+
+ Database string `yaml:"database"`
+
+ ID string `yaml:"id"`
+ Bot struct {
+ Username string `yaml:"username"`
+ Displayname string `yaml:"displayname"`
+ Avatar string `yaml:"avatar"`
+ } `yaml:"bot"`
+
+ ASToken string `yaml:"as_token"`
+ HSToken string `yaml:"hs_token"`
+ } `yaml:"appservice"`
+
+ Bridge BridgeConfig `yaml:"bridge"`
+
+ Logging appservice.LogConfig `yaml:"logging"`
+}
+
+func Load(path string) (*Config, error) {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ var config = &Config{}
+ err = yaml.Unmarshal(data, config)
+ return config, err
+}
+
+func (config *Config) Save(path string) error {
+ data, err := yaml.Marshal(config)
+ if err != nil {
+ return err
+ }
+ return ioutil.WriteFile(path, data, 0600)
+}
+
+func (config *Config) Appservice() (*appservice.Config, error) {
+ as := appservice.Create()
+ as.LogConfig = config.Logging
+ as.HomeserverDomain = config.Homeserver.Domain
+ as.HomeserverURL = config.Homeserver.Address
+ var err error
+ as.Registration, err = config.GetRegistration()
+ return as, err
+}
diff --git a/config/registration.go b/config/registration.go
new file mode 100644
index 0000000..429e3b3
--- /dev/null
+++ b/config/registration.go
@@ -0,0 +1,62 @@
+// mautrix-whatsapp - A Matrix-Whatsapp puppeting bridge.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package config
+
+import (
+ "maunium.net/go/mautrix-appservice"
+ "regexp"
+)
+
+func (config *Config) NewRegistration() (*appservice.Registration, error) {
+ registration := appservice.CreateRegistration("mautrix-whatsapp")
+
+ err := config.copyToRegistration(registration)
+ if err != nil {
+ return nil, err
+ }
+
+ config.AppService.ASToken = registration.AppToken
+ config.AppService.HSToken = registration.ServerToken
+ return registration, nil
+}
+
+func (config *Config) GetRegistration() (*appservice.Registration, error) {
+ registration := appservice.CreateRegistration("mautrix-whatsapp")
+
+ err := config.copyToRegistration(registration)
+ if err != nil {
+ return nil, err
+ }
+
+ registration.AppToken = config.AppService.ASToken
+ registration.ServerToken = config.AppService.HSToken
+ return registration, nil
+}
+
+func (config *Config) copyToRegistration(registration *appservice.Registration) error {
+ registration.ID = config.AppService.ID
+ registration.URL = config.AppService.Address
+ registration.RateLimited = false
+ registration.SenderLocalpart = config.AppService.Bot.Username
+
+ userIDRegex, err := regexp.Compile(config.Bridge.FormatUsername("[0-9]+", "[0-9]+"))
+ if err != nil {
+ return err
+ }
+ registration.Namespaces.RegisterUserIDs(userIDRegex, true)
+ return nil
+}
diff --git a/example-config.yaml b/example-config.yaml
new file mode 100644
index 0000000..d43b15c
--- /dev/null
+++ b/example-config.yaml
@@ -0,0 +1,60 @@
+# Homeserver details.
+homeserver:
+ # The address that this appservice can use to connect to the homeserver.
+ address: https://matrix.org
+ # The domain of the homeserver (for MXIDs, etc).
+ domain: matrix.org
+
+# Application service host/registration related details.
+# Changing these values requires regeneration of the registration.
+appservice:
+ # The address that the homeserver can use to connect to this appservice.
+ address: http://localhost:8080
+
+ # The hostname and port where this appservice should listen.
+ hostname: 0.0.0.0
+ port: 8080
+
+ # The full URI to the database. Only SQLite is currently supported.
+ database: sqlite:///mautrix-whatsapp.db
+
+ # The unique ID of this appservice.
+ id: whatsapp
+ # Appservice bot details.
+ bot:
+ # Username of the appservice bot.
+ username: whatsappbot
+ # Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
+ # to leave display name/avatar as-is.
+ displayname: WhatsApp bridge bot
+ avatar: mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr
+
+ # Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
+ as_token: "This value is generated when generating the registration"
+ hs_token: "This value is generated when generating the registration"
+
+# Bridge config. Currently unused.
+bridge:
+ # Localpart template of MXIDs for Whatsapp users.
+ # {{.receiver}} is replaced with the Whatsapp user ID of the Matrix user receiving messages.
+ # {{.userid}} is replaced with the user ID of the Whatsapp user.
+ username_template: "whatsapp_{{.Receiver}}_{{.UserID}}"
+ # Displayname template for Whatsapp users.
+ # {{.displayname}} is replaced with the display name of the Whatsapp user.
+ displayname_template: "{{.Displayname}}"
+
+# Logging config.
+logging:
+ # The directory for log files. Will be created if not found.
+ directory: ./logs
+ # Available variables: .date for the file date and .index for different log files on the same day.
+ file_name_format: {{.date}}-{{.index}.log
+ # Date format for file names in the Go time format: https://golang.org/pkg/time/#pkg-constants
+ file_date_format: 2006-01-02
+ # Log file permissions.
+ file_mode: 0600
+ # Timestamp format for log entries in the Go time format.
+ timestamp_format: Jan _2, 2006 15:04:05
+ # Minimum severity for log messages.
+ # Options: debug, info, warn, error, fatal
+ print_level: info
diff --git a/main.go b/main.go
index 2565072..70523be 100644
--- a/main.go
+++ b/main.go
@@ -1,4 +1,4 @@
-// mautrix-whatsapp - A puppeting Matrix-Whatsapp bridge.
+// mautrix-whatsapp - A Matrix-Whatsapp puppeting bridge.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
@@ -16,6 +16,99 @@
package main
+import (
+ "github.com/Rhymen/go-whatsapp"
+ "time"
+ "fmt"
+ "os"
+ "bufio"
+ "encoding/gob"
+ "github.com/mdp/qrterminal"
+)
+
func main() {
-
+ wac, err := whatsapp.NewConn(20 * time.Second)
+ if err != nil {
+ panic(err)
+ }
+
+ wac.AddHandler(myHandler{})
+
+ sess, err := LoadSession("whatsapp.session")
+ if err != nil {
+ fmt.Println(err)
+ sess, err = Login(wac)
+ } else {
+ sess, err = wac.RestoreSession(sess)
+ }
+ if err != nil {
+ panic(err)
+ }
+ SaveSession(sess, "whatsapp.session")
+
+ reader := bufio.NewReader(os.Stdin)
+ for {
+ fmt.Print("receiver> ")
+ receiver, _ := reader.ReadString('\n')
+ fmt.Print("message> ")
+ message, _ := reader.ReadString('\n')
+ wac.Send(whatsapp.TextMessage{
+ Info: whatsapp.MessageInfo{
+ RemoteJid: fmt.Sprintf("%s@s.whatsapp.net", receiver),
+ },
+ Text: message,
+ })
+ fmt.Println(receiver, message)
+ }
+}
+
+func Login(wac *whatsapp.Conn) (whatsapp.Session, error) {
+ qrChan := make(chan string)
+ go func() {
+ qrterminal.Generate(<-qrChan, qrterminal.L, os.Stdout)
+ }()
+ return wac.Login(qrChan)
+}
+
+func SaveSession(session whatsapp.Session, fileName string) {
+ file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0600)
+ if err != nil {
+ panic(err)
+ }
+
+ enc := gob.NewEncoder(file)
+ enc.Encode(session)
+}
+
+func LoadSession(fileName string) (sess whatsapp.Session, err error) {
+ file, err := os.OpenFile(fileName, os.O_RDONLY, 0600)
+ if err != nil {
+ return sess, err
+ }
+
+ dec := gob.NewDecoder(file)
+ dec.Decode(sess)
+ return
+}
+
+type myHandler struct{}
+
+func (myHandler) HandleError(err error) {
+ fmt.Fprintf(os.Stderr, "%v", err)
+}
+
+func (myHandler) HandleTextMessage(message whatsapp.TextMessage) {
+ fmt.Println(message)
+}
+
+func (myHandler) HandleImageMessage(message whatsapp.ImageMessage) {
+ fmt.Println(message)
+}
+
+func (myHandler) HandleVideoMessage(message whatsapp.VideoMessage) {
+ fmt.Println(message)
+}
+
+func (myHandler) HandleJsonMessage(message string) {
+ fmt.Println(message)
}
diff --git a/matrix.go b/matrix.go
new file mode 100644
index 0000000..b78268b
--- /dev/null
+++ b/matrix.go
@@ -0,0 +1,21 @@
+// mautrix-whatsapp - A Matrix-Whatsapp puppeting bridge.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package main
+
+func InitMatrix() {
+
+}