Merge branch 'master' into cross-platform-delete

This commit is contained in:
Joseph Mansy 2023-03-11 10:10:23 -08:00 committed by GitHub
commit 27c7b1e18b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 337 additions and 277 deletions

View File

@ -5,11 +5,11 @@ jobs:
name: golangci-lint name: golangci-lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with: with:
fetch-depth: 20 fetch-depth: 20
- name: Run golangci-lint - name: Run golangci-lint
uses: golangci/golangci-lint-action@v2 uses: golangci/golangci-lint-action@v3
with: with:
version: latest version: latest
args: "-v --new-from-rev HEAD~5" args: "-v --new-from-rev HEAD~5"
@ -21,12 +21,12 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Install Go - name: Install Go
uses: actions/setup-go@v2 uses: actions/setup-go@v3
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
stable: false stable: false
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Test - name: Test
@ -40,19 +40,19 @@ jobs:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/mac/matterbridge-$VERSION-darwin-amd64 CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/mac/matterbridge-$VERSION-darwin-amd64
- name: Upload linux 64-bit - name: Upload linux 64-bit
if: startsWith(matrix.go-version,'1.20') if: startsWith(matrix.go-version,'1.20')
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: matterbridge-linux-64bit name: matterbridge-linux-64bit
path: output/lin path: output/lin
- name: Upload windows 64-bit - name: Upload windows 64-bit
if: startsWith(matrix.go-version,'1.20') if: startsWith(matrix.go-version,'1.20')
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: matterbridge-windows-64bit name: matterbridge-windows-64bit
path: output/win path: output/win
- name: Upload darwin 64-bit - name: Upload darwin 64-bit
if: startsWith(matrix.go-version,'1.20') if: startsWith(matrix.go-version,'1.20')
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: matterbridge-darwin-64bit name: matterbridge-darwin-64bit
path: output/mac path: output/mac

View File

@ -6,7 +6,7 @@ import (
"sync" "sync"
"time" "time"
"gopkg.in/olahol/melody.v1" "github.com/olahol/melody"
"github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
@ -166,7 +166,9 @@ func (b *API) handleStream(c echo.Context) error {
} }
c.Response().Flush() c.Response().Flush()
for { for {
select {
// TODO: this causes issues, messages should be broadcasted to all connected clients // TODO: this causes issues, messages should be broadcasted to all connected clients
default:
msg := b.Messages.Dequeue() msg := b.Messages.Dequeue()
if msg != nil { if msg != nil {
if err := json.NewEncoder(c.Response()).Encode(msg); err != nil { if err := json.NewEncoder(c.Response()).Encode(msg); err != nil {
@ -174,7 +176,10 @@ func (b *API) handleStream(c echo.Context) error {
} }
c.Response().Flush() c.Response().Flush()
} }
time.Sleep(200 * time.Millisecond) time.Sleep(100 * time.Millisecond)
case <-c.Request().Context().Done():
return nil
}
} }
} }

View File

@ -81,17 +81,6 @@ func (b *Bdiscord) Connect() error {
return err return err
} }
b.Log.Info("Connection succeeded") b.Log.Info("Connection succeeded")
b.c.AddHandler(b.messageCreate)
b.c.AddHandler(b.messageTyping)
b.c.AddHandler(b.messageUpdate)
b.c.AddHandler(b.messageDelete)
b.c.AddHandler(b.messageDeleteBulk)
b.c.AddHandler(b.memberAdd)
b.c.AddHandler(b.memberRemove)
b.c.AddHandler(b.memberUpdate)
if b.GetInt("debuglevel") == 1 {
b.c.AddHandler(b.messageEvent)
}
// Add privileged intent for guild member tracking. This is needed to track nicks // Add privileged intent for guild member tracking. This is needed to track nicks
// for display names and @mention translation // for display names and @mention translation
b.c.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged | b.c.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged |
@ -233,6 +222,19 @@ func (b *Bdiscord) Connect() error {
b.nickMemberMap[member.Nick] = member b.nickMemberMap[member.Nick] = member
} }
} }
b.c.AddHandler(b.messageCreate)
b.c.AddHandler(b.messageTyping)
b.c.AddHandler(b.messageUpdate)
b.c.AddHandler(b.messageDelete)
b.c.AddHandler(b.messageDeleteBulk)
b.c.AddHandler(b.memberAdd)
b.c.AddHandler(b.memberRemove)
b.c.AddHandler(b.memberUpdate)
if b.GetInt("debuglevel") == 1 {
b.c.AddHandler(b.messageEvent)
}
return nil return nil
} }

View File

@ -161,7 +161,7 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
if err != nil { if err != nil {
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err) b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err)
} }
if post.RootId != "" { if post != nil && post.RootId != "" {
msg.ParentID = post.RootId msg.ParentID = post.RootId
} }
} }

View File

@ -42,7 +42,14 @@ func (b *Bmumble) handleTextMessage(event *gumble.TextMessageEvent) {
if part.Image == nil { if part.Image == nil {
rmsg.Text = part.Text rmsg.Text = part.Text
} else { } else {
fname := b.Account + "_" + strconv.FormatInt(now.UnixNano(), 10) + "_" + strconv.Itoa(i) + part.FileExtension fileExt := part.FileExtension
if fileExt == ".jfif" {
fileExt = ".jpg"
}
if fileExt == ".jpe" {
fileExt = ".jpg"
}
fname := b.Account + "_" + strconv.FormatInt(now.UnixNano(), 10) + "_" + strconv.Itoa(i) + fileExt
rmsg.Extra = make(map[string][]interface{}) rmsg.Extra = make(map[string][]interface{})
if err = helper.HandleDownloadSize(b.Log, &rmsg, fname, int64(len(part.Image)), b.General); err != nil { if err = helper.HandleDownloadSize(b.Log, &rmsg, fname, int64(len(part.Image)), b.General); err != nil {
b.Log.WithError(err).Warn("not including image in message") b.Log.WithError(err).Warn("not including image in message")
@ -62,7 +69,6 @@ func (b *Bmumble) handleConnect(event *gumble.ConnectEvent) {
} }
// No need to talk or listen // No need to talk or listen
event.Client.Self.SetSelfDeafened(true) event.Client.Self.SetSelfDeafened(true)
event.Client.Self.SetSelfMuted(true)
// if the Channel variable is set, this is a reconnect -> rejoin channel // if the Channel variable is set, this is a reconnect -> rejoin channel
if b.Channel != nil { if b.Channel != nil {
if err := b.doJoin(event.Client, *b.Channel); err != nil { if err := b.doJoin(event.Client, *b.Channel); err != nil {

View File

@ -250,10 +250,15 @@ func (b *Bmumble) processMessage(msg *config.Message) {
// If there is a maximum message length, split and truncate the lines // If there is a maximum message length, split and truncate the lines
var msgLines []string var msgLines []string
if maxLength := b.serverConfig.MaximumMessageLength; maxLength != nil { if maxLength := b.serverConfig.MaximumMessageLength; maxLength != nil {
if *maxLength != 0 { // Some servers will have unlimited message lengths.
// Not doing this makes underflows happen.
msgLines = helper.GetSubLines(msg.Text, *maxLength-len(msg.Username), b.GetString("MessageClipped")) msgLines = helper.GetSubLines(msg.Text, *maxLength-len(msg.Username), b.GetString("MessageClipped"))
} else { } else {
msgLines = helper.GetSubLines(msg.Text, 0, b.GetString("MessageClipped")) msgLines = helper.GetSubLines(msg.Text, 0, b.GetString("MessageClipped"))
} }
} else {
msgLines = helper.GetSubLines(msg.Text, 0, b.GetString("MessageClipped"))
}
// Send the individual lines // Send the individual lines
for i := range msgLines { for i := range msgLines {
// Remove unnecessary newline character, since either way we're sending it as individual lines // Remove unnecessary newline character, since either way we're sending it as individual lines

View File

@ -31,7 +31,7 @@ func (b *Bwhatsapp) handleMessage(message *events.Message) {
return return
} }
b.Log.Infof("Receiving message %#v", msg) b.Log.Debugf("Receiving message %#v", msg)
switch { switch {
case msg.Conversation != nil || msg.ExtendedTextMessage != nil: case msg.Conversation != nil || msg.ExtendedTextMessage != nil:
@ -44,6 +44,8 @@ func (b *Bwhatsapp) handleMessage(message *events.Message) {
b.handleDocumentMessage(message) b.handleDocumentMessage(message)
case msg.ImageMessage != nil: case msg.ImageMessage != nil:
b.handleImageMessage(message) b.handleImageMessage(message)
case msg.ProtocolMessage != nil && *msg.ProtocolMessage.Type == proto.ProtocolMessage_REVOKE:
b.handleDelete(msg.ProtocolMessage)
} }
} }
@ -359,3 +361,20 @@ func (b *Bwhatsapp) handleDocumentMessage(msg *events.Message) {
b.Remote <- rmsg b.Remote <- rmsg
} }
func (b *Bwhatsapp) handleDelete(messageInfo *proto.ProtocolMessage) {
sender, _ := types.ParseJID(*messageInfo.Key.Participant)
rmsg := config.Message{
Account: b.Account,
Protocol: b.Protocol,
ID: getMessageIdFormat(sender, *messageInfo.Key.Id),
Event: config.EventMsgDelete,
Text: config.EventMsgDelete,
Channel: *messageInfo.Key.RemoteJid,
}
b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
b.Log.Debugf("<= Message is %#v", rmsg)
b.Remote <- rmsg
}

View File

@ -243,6 +243,7 @@ func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (st
FileSha256: resp.FileSHA256, FileSha256: resp.FileSHA256,
FileLength: goproto.Uint64(resp.FileLength), FileLength: goproto.Uint64(resp.FileLength),
Url: &resp.URL, Url: &resp.URL,
DirectPath: &resp.DirectPath,
ContextInfo: ctx, ContextInfo: ctx,
} }
@ -280,6 +281,7 @@ func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (strin
FileSha256: resp.FileSHA256, FileSha256: resp.FileSHA256,
FileLength: goproto.Uint64(resp.FileLength), FileLength: goproto.Uint64(resp.FileLength),
Url: &resp.URL, Url: &resp.URL,
DirectPath: &resp.DirectPath,
ContextInfo: ctx, ContextInfo: ctx,
} }
@ -313,6 +315,7 @@ func (b *Bwhatsapp) PostVideoMessage(msg config.Message, filetype string) (strin
FileSha256: resp.FileSHA256, FileSha256: resp.FileSHA256,
FileLength: goproto.Uint64(resp.FileLength), FileLength: goproto.Uint64(resp.FileLength),
Url: &resp.URL, Url: &resp.URL,
DirectPath: &resp.DirectPath,
ContextInfo: ctx, ContextInfo: ctx,
} }
@ -345,6 +348,7 @@ func (b *Bwhatsapp) PostAudioMessage(msg config.Message, filetype string) (strin
FileSha256: resp.FileSHA256, FileSha256: resp.FileSHA256,
FileLength: goproto.Uint64(resp.FileLength), FileLength: goproto.Uint64(resp.FileLength),
Url: &resp.URL, Url: &resp.URL,
DirectPath: &resp.DirectPath,
ContextInfo: ctx, ContextInfo: ctx,
} }

2
go.mod
View File

@ -34,6 +34,7 @@ require (
github.com/mattn/godown v0.0.1 github.com/mattn/godown v0.0.1
github.com/mdp/qrterminal v1.0.1 github.com/mdp/qrterminal v1.0.1
github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94 github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
github.com/olahol/melody v1.1.2
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
github.com/rs/xid v1.4.0 github.com/rs/xid v1.4.0
github.com/russross/blackfriday v1.6.0 github.com/russross/blackfriday v1.6.0
@ -53,7 +54,6 @@ require (
golang.org/x/text v0.8.0 golang.org/x/text v0.8.0
gomod.garykim.dev/nc-talk v0.3.0 gomod.garykim.dev/nc-talk v0.3.0
google.golang.org/protobuf v1.29.0 google.golang.org/protobuf v1.29.0
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14 layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14
modernc.org/sqlite v1.21.0 modernc.org/sqlite v1.21.0
) )

4
go.sum
View File

@ -1246,6 +1246,8 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olahol/melody v1.1.2 h1:9PZ5kYv/CUy0PRZcJCKja1MUxAh6olVeHkyqaQxO7n0=
github.com/olahol/melody v1.1.2/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
@ -2465,8 +2467,6 @@ gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3M
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJcorb+a0hJIQ6uakU5gybjfLWHlXo=
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M=
gopkg.in/olivere/elastic.v6 v6.2.35/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4= gopkg.in/olivere/elastic.v6 v6.2.35/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4=
gopkg.in/olivere/elastic.v6 v6.2.37/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4= gopkg.in/olivere/elastic.v6 v6.2.37/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

View File

@ -1,3 +1,11 @@
## 2022-09-12 (v1.1.0)
* Create Go module.
* Update examples.
* Fix concurrent panic (PR-65).
* Add `Sessions` to get all sessions (PR-53).
* Add `LocalAddr` and `RemoteAddr` (PR-55).
## 2017-05-18 ## 2017-05-18
* Fix `HandleSentMessageBinary`. * Fix `HandleSentMessageBinary`.

157
vendor/github.com/olahol/melody/README.md generated vendored Normal file
View File

@ -0,0 +1,157 @@
# melody
![Build Status](https://github.com/olahol/melody/actions/workflows/test.yml/badge.svg)
[![Codecov](https://img.shields.io/codecov/c/github/olahol/melody)](https://app.codecov.io/github/olahol/melody)
[![Go Report Card](https://goreportcard.com/badge/github.com/olahol/melody)](https://goreportcard.com/report/github.com/olahol/melody)
[![GoDoc](https://godoc.org/github.com/olahol/melody?status.svg)](https://godoc.org/github.com/olahol/melody)
> :notes: Minimalist websocket framework for Go.
Melody is websocket framework based on [github.com/gorilla/websocket](https://github.com/gorilla/websocket)
that abstracts away the tedious parts of handling websockets. It gets out of
your way so you can write real-time apps. Features include:
* [x] Clear and easy interface similar to `net/http` or Gin.
* [x] A simple way to broadcast to all or selected connected sessions.
* [x] Message buffers making concurrent writing safe.
* [x] Automatic handling of sending ping/pong heartbeats that timeout broken sessions.
* [x] Store data on sessions.
## Install
```bash
go get github.com/olahol/melody
```
## [Example: chat](https://github.com/olahol/melody/tree/master/examples/chat)
[![Chat](https://cdn.rawgit.com/olahol/melody/master/examples/chat/demo.gif "Demo")](https://github.com/olahol/melody/tree/master/examples/chat)
```go
package main
import (
"net/http"
"github.com/olahol/melody"
)
func main() {
m := melody.New()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
})
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
m.HandleRequest(w, r)
})
m.HandleMessage(func(s *melody.Session, msg []byte) {
m.Broadcast(msg)
})
http.ListenAndServe(":5000", nil)
}
```
## [Example: gophers](https://github.com/olahol/melody/tree/master/examples/gophers)
[![Gophers](https://cdn.rawgit.com/olahol/melody/master/examples/gophers/demo.gif "Demo")](https://github.com/olahol/melody/tree/master/examples/gophers)
```go
package main
import (
"net/http"
"strings"
"github.com/google/uuid"
"github.com/olahol/melody"
)
type GopherInfo struct {
ID, X, Y string
}
func main() {
m := melody.New()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
})
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
m.HandleRequest(w, r)
})
m.HandleConnect(func(s *melody.Session) {
ss, _ := m.Sessions()
for _, o := range ss {
value, exists := o.Get("info")
if !exists {
continue
}
info := value.(*GopherInfo)
s.Write([]byte("set " + info.ID + " " + info.X + " " + info.Y))
}
id := uuid.NewString()
s.Set("info", &GopherInfo{id, "0", "0"})
s.Write([]byte("iam " + id))
})
m.HandleDisconnect(func(s *melody.Session) {
value, exists := s.Get("info")
if !exists {
return
}
info := value.(*GopherInfo)
m.BroadcastOthers([]byte("dis "+info.ID), s)
})
m.HandleMessage(func(s *melody.Session, msg []byte) {
p := strings.Split(string(msg), " ")
value, exists := s.Get("info")
if len(p) != 2 || !exists {
return
}
info := value.(*GopherInfo)
info.X = p[0]
info.Y = p[1]
m.BroadcastOthers([]byte("set "+info.ID+" "+info.X+" "+info.Y), s)
})
http.ListenAndServe(":5000", nil)
}
```
### [More examples](https://github.com/olahol/melody/tree/master/examples)
## [Documentation](https://godoc.org/github.com/olahol/melody)
## Contributors
<a href="https://github.com/olahol/melody/graphs/contributors">
<img src="https://contrib.rocks/image?repo=olahol/melody" />
</a>
## FAQ
If you are getting a `403` when trying to connect to your websocket you can [change allow all origin hosts](http://godoc.org/github.com/gorilla/websocket#hdr-Origin_Considerations):
```go
m := melody.New()
m.Upgrader.CheckOrigin = func(r *http.Request) bool { return true }
```

View File

@ -9,14 +9,14 @@
// A broadcasting echo server: // A broadcasting echo server:
// //
// func main() { // func main() {
// r := gin.Default()
// m := melody.New() // m := melody.New()
// r.GET("/ws", func(c *gin.Context) { // http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
// m.HandleRequest(c.Writer, c.Request) // m.HandleRequest(w, r)
// }) // })
// m.HandleMessage(func(s *melody.Session, msg []byte) { // m.HandleMessage(func(s *melody.Session, msg []byte) {
// m.Broadcast(msg) // m.Broadcast(msg)
// }) // })
// r.Run(":5000") // http.ListenAndServe(":5000", nil)
// } // }
package melody package melody

10
vendor/github.com/olahol/melody/errors.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
package melody
import "errors"
var (
ErrClosed = errors.New("melody instance is closed")
ErrSessionClosed = errors.New("session is closed")
ErrWriteClosed = errors.New("tried to write to closed a session")
ErrMessageBufferFull = errors.New("session message buffer is full")
)

View File

@ -78,3 +78,14 @@ func (h *hub) len() int {
return len(h.sessions) return len(h.sessions)
} }
func (h *hub) all() []*Session {
h.rwmutex.RLock()
defer h.rwmutex.RUnlock()
s := make([]*Session, 0, len(h.sessions))
for k := range h.sessions {
s = append(s, k)
}
return s
}

View File

@ -1,7 +1,6 @@
package melody package melody
import ( import (
"errors"
"net/http" "net/http"
"sync" "sync"
@ -164,10 +163,10 @@ func (m *Melody) HandleRequest(w http.ResponseWriter, r *http.Request) error {
// HandleRequestWithKeys does the same as HandleRequest but populates session.Keys with keys. // HandleRequestWithKeys does the same as HandleRequest but populates session.Keys with keys.
func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]interface{}) error { func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]interface{}) error {
if m.hub.closed() { if m.hub.closed() {
return errors.New("melody instance is closed") return ErrClosed
} }
conn, err := m.Upgrader.Upgrade(w, r, nil) conn, err := m.Upgrader.Upgrade(w, r, w.Header())
if err != nil { if err != nil {
return err return err
@ -178,6 +177,7 @@ func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, k
Keys: keys, Keys: keys,
conn: conn, conn: conn,
output: make(chan *envelope, m.Config.MessageBufferSize), output: make(chan *envelope, m.Config.MessageBufferSize),
outputDone: make(chan struct{}),
melody: m, melody: m,
open: true, open: true,
rwmutex: &sync.RWMutex{}, rwmutex: &sync.RWMutex{},
@ -205,7 +205,7 @@ func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, k
// Broadcast broadcasts a text message to all sessions. // Broadcast broadcasts a text message to all sessions.
func (m *Melody) Broadcast(msg []byte) error { func (m *Melody) Broadcast(msg []byte) error {
if m.hub.closed() { if m.hub.closed() {
return errors.New("melody instance is closed") return ErrClosed
} }
message := &envelope{t: websocket.TextMessage, msg: msg} message := &envelope{t: websocket.TextMessage, msg: msg}
@ -217,7 +217,7 @@ func (m *Melody) Broadcast(msg []byte) error {
// BroadcastFilter broadcasts a text message to all sessions that fn returns true for. // BroadcastFilter broadcasts a text message to all sessions that fn returns true for.
func (m *Melody) BroadcastFilter(msg []byte, fn func(*Session) bool) error { func (m *Melody) BroadcastFilter(msg []byte, fn func(*Session) bool) error {
if m.hub.closed() { if m.hub.closed() {
return errors.New("melody instance is closed") return ErrClosed
} }
message := &envelope{t: websocket.TextMessage, msg: msg, filter: fn} message := &envelope{t: websocket.TextMessage, msg: msg, filter: fn}
@ -246,7 +246,7 @@ func (m *Melody) BroadcastMultiple(msg []byte, sessions []*Session) error {
// BroadcastBinary broadcasts a binary message to all sessions. // BroadcastBinary broadcasts a binary message to all sessions.
func (m *Melody) BroadcastBinary(msg []byte) error { func (m *Melody) BroadcastBinary(msg []byte) error {
if m.hub.closed() { if m.hub.closed() {
return errors.New("melody instance is closed") return ErrClosed
} }
message := &envelope{t: websocket.BinaryMessage, msg: msg} message := &envelope{t: websocket.BinaryMessage, msg: msg}
@ -258,7 +258,7 @@ func (m *Melody) BroadcastBinary(msg []byte) error {
// BroadcastBinaryFilter broadcasts a binary message to all sessions that fn returns true for. // BroadcastBinaryFilter broadcasts a binary message to all sessions that fn returns true for.
func (m *Melody) BroadcastBinaryFilter(msg []byte, fn func(*Session) bool) error { func (m *Melody) BroadcastBinaryFilter(msg []byte, fn func(*Session) bool) error {
if m.hub.closed() { if m.hub.closed() {
return errors.New("melody instance is closed") return ErrClosed
} }
message := &envelope{t: websocket.BinaryMessage, msg: msg, filter: fn} message := &envelope{t: websocket.BinaryMessage, msg: msg, filter: fn}
@ -274,10 +274,18 @@ func (m *Melody) BroadcastBinaryOthers(msg []byte, s *Session) error {
}) })
} }
// Sessions returns all sessions. An error is returned if the melody session is closed.
func (m *Melody) Sessions() ([]*Session, error) {
if m.hub.closed() {
return nil, ErrClosed
}
return m.hub.all(), nil
}
// Close closes the melody instance and all connected sessions. // Close closes the melody instance and all connected sessions.
func (m *Melody) Close() error { func (m *Melody) Close() error {
if m.hub.closed() { if m.hub.closed() {
return errors.New("melody instance is already closed") return ErrClosed
} }
m.hub.exit <- &envelope{t: websocket.CloseMessage, msg: []byte{}} m.hub.exit <- &envelope{t: websocket.CloseMessage, msg: []byte{}}
@ -289,7 +297,7 @@ func (m *Melody) Close() error {
// Use the FormatCloseMessage function to format a proper close message payload. // Use the FormatCloseMessage function to format a proper close message payload.
func (m *Melody) CloseWithMsg(msg []byte) error { func (m *Melody) CloseWithMsg(msg []byte) error {
if m.hub.closed() { if m.hub.closed() {
return errors.New("melody instance is already closed") return ErrClosed
} }
m.hub.exit <- &envelope{t: websocket.CloseMessage, msg: msg} m.hub.exit <- &envelope{t: websocket.CloseMessage, msg: msg}

View File

@ -1,7 +1,7 @@
package melody package melody
import ( import (
"errors" "net"
"net/http" "net/http"
"sync" "sync"
"time" "time"
@ -15,6 +15,7 @@ type Session struct {
Keys map[string]interface{} Keys map[string]interface{}
conn *websocket.Conn conn *websocket.Conn
output chan *envelope output chan *envelope
outputDone chan struct{}
melody *Melody melody *Melody
open bool open bool
rwmutex *sync.RWMutex rwmutex *sync.RWMutex
@ -22,20 +23,20 @@ type Session struct {
func (s *Session) writeMessage(message *envelope) { func (s *Session) writeMessage(message *envelope) {
if s.closed() { if s.closed() {
s.melody.errorHandler(s, errors.New("tried to write to closed a session")) s.melody.errorHandler(s, ErrWriteClosed)
return return
} }
select { select {
case s.output <- message: case s.output <- message:
default: default:
s.melody.errorHandler(s, errors.New("session message buffer is full")) s.melody.errorHandler(s, ErrMessageBufferFull)
} }
} }
func (s *Session) writeRaw(message *envelope) error { func (s *Session) writeRaw(message *envelope) error {
if s.closed() { if s.closed() {
return errors.New("tried to write to a closed session") return ErrWriteClosed
} }
s.conn.SetWriteDeadline(time.Now().Add(s.melody.Config.WriteWait)) s.conn.SetWriteDeadline(time.Now().Add(s.melody.Config.WriteWait))
@ -56,12 +57,13 @@ func (s *Session) closed() bool {
} }
func (s *Session) close() { func (s *Session) close() {
if !s.closed() {
s.rwmutex.Lock() s.rwmutex.Lock()
open := s.open
s.open = false s.open = false
s.conn.Close()
close(s.output)
s.rwmutex.Unlock() s.rwmutex.Unlock()
if open {
s.conn.Close()
close(s.outputDone)
} }
} }
@ -76,11 +78,7 @@ func (s *Session) writePump() {
loop: loop:
for { for {
select { select {
case msg, ok := <-s.output: case msg := <-s.output:
if !ok {
break loop
}
err := s.writeRaw(msg) err := s.writeRaw(msg)
if err != nil { if err != nil {
@ -101,8 +99,14 @@ loop:
} }
case <-ticker.C: case <-ticker.C:
s.ping() s.ping()
case _, ok := <-s.outputDone:
if !ok {
break loop
} }
} }
}
s.close()
} }
func (s *Session) readPump() { func (s *Session) readPump() {
@ -142,7 +146,7 @@ func (s *Session) readPump() {
// Write writes message to session. // Write writes message to session.
func (s *Session) Write(msg []byte) error { func (s *Session) Write(msg []byte) error {
if s.closed() { if s.closed() {
return errors.New("session is closed") return ErrSessionClosed
} }
s.writeMessage(&envelope{t: websocket.TextMessage, msg: msg}) s.writeMessage(&envelope{t: websocket.TextMessage, msg: msg})
@ -153,7 +157,7 @@ func (s *Session) Write(msg []byte) error {
// WriteBinary writes a binary message to session. // WriteBinary writes a binary message to session.
func (s *Session) WriteBinary(msg []byte) error { func (s *Session) WriteBinary(msg []byte) error {
if s.closed() { if s.closed() {
return errors.New("session is closed") return ErrSessionClosed
} }
s.writeMessage(&envelope{t: websocket.BinaryMessage, msg: msg}) s.writeMessage(&envelope{t: websocket.BinaryMessage, msg: msg})
@ -164,7 +168,7 @@ func (s *Session) WriteBinary(msg []byte) error {
// Close closes session. // Close closes session.
func (s *Session) Close() error { func (s *Session) Close() error {
if s.closed() { if s.closed() {
return errors.New("session is already closed") return ErrSessionClosed
} }
s.writeMessage(&envelope{t: websocket.CloseMessage, msg: []byte{}}) s.writeMessage(&envelope{t: websocket.CloseMessage, msg: []byte{}})
@ -176,7 +180,7 @@ func (s *Session) Close() error {
// Use the FormatCloseMessage function to format a proper close message payload. // Use the FormatCloseMessage function to format a proper close message payload.
func (s *Session) CloseWithMsg(msg []byte) error { func (s *Session) CloseWithMsg(msg []byte) error {
if s.closed() { if s.closed() {
return errors.New("session is already closed") return ErrSessionClosed
} }
s.writeMessage(&envelope{t: websocket.CloseMessage, msg: msg}) s.writeMessage(&envelope{t: websocket.CloseMessage, msg: msg})
@ -184,9 +188,12 @@ func (s *Session) CloseWithMsg(msg []byte) error {
return nil return nil
} }
// Set is used to store a new key/value pair exclusivelly for this session. // Set is used to store a new key/value pair exclusively for this session.
// It also lazy initializes s.Keys if it was not used previously. // It also lazy initializes s.Keys if it was not used previously.
func (s *Session) Set(key string, value interface{}) { func (s *Session) Set(key string, value interface{}) {
s.rwmutex.Lock()
defer s.rwmutex.Unlock()
if s.Keys == nil { if s.Keys == nil {
s.Keys = make(map[string]interface{}) s.Keys = make(map[string]interface{})
} }
@ -197,6 +204,9 @@ func (s *Session) Set(key string, value interface{}) {
// Get returns the value for the given key, ie: (value, true). // Get returns the value for the given key, ie: (value, true).
// If the value does not exists it returns (nil, false) // If the value does not exists it returns (nil, false)
func (s *Session) Get(key string) (value interface{}, exists bool) { func (s *Session) Get(key string) (value interface{}, exists bool) {
s.rwmutex.RLock()
defer s.rwmutex.RUnlock()
if s.Keys != nil { if s.Keys != nil {
value, exists = s.Keys[key] value, exists = s.Keys[key]
} }
@ -217,3 +227,13 @@ func (s *Session) MustGet(key string) interface{} {
func (s *Session) IsClosed() bool { func (s *Session) IsClosed() bool {
return s.closed() return s.closed()
} }
// LocalAddr returns the local addr of the connection.
func (s *Session) LocalAddr() net.Addr {
return s.conn.LocalAddr()
}
// RemoteAddr returns the remote addr of the connection.
func (s *Session) RemoteAddr() net.Addr {
return s.conn.RemoteAddr()
}

View File

@ -1,10 +0,0 @@
language: go
sudo: required
go:
- 1.6
- 1.7
- 1.8
install:
- go get github.com/gorilla/websocket
script:
- go test

View File

@ -1,185 +0,0 @@
# melody
[![Build Status](https://travis-ci.org/olahol/melody.svg)](https://travis-ci.org/olahol/melody)
[![Coverage Status](https://img.shields.io/coveralls/olahol/melody.svg?style=flat)](https://coveralls.io/r/olahol/melody)
[![GoDoc](https://godoc.org/github.com/olahol/melody?status.svg)](https://godoc.org/github.com/olahol/melody)
> :notes: Minimalist websocket framework for Go.
Melody is websocket framework based on [github.com/gorilla/websocket](https://github.com/gorilla/websocket)
that abstracts away the tedious parts of handling websockets. It gets out of
your way so you can write real-time apps. Features include:
* [x] Clear and easy interface similar to `net/http` or Gin.
* [x] A simple way to broadcast to all or selected connected sessions.
* [x] Message buffers making concurrent writing safe.
* [x] Automatic handling of ping/pong and session timeouts.
* [x] Store data on sessions.
## Install
```bash
go get gopkg.in/olahol/melody.v1
```
## [Example: chat](https://github.com/olahol/melody/tree/master/examples/chat)
[![Chat](https://cdn.rawgit.com/olahol/melody/master/examples/chat/demo.gif "Demo")](https://github.com/olahol/melody/tree/master/examples/chat)
Using [Gin](https://github.com/gin-gonic/gin):
```go
package main
import (
"github.com/gin-gonic/gin"
"gopkg.in/olahol/melody.v1"
"net/http"
)
func main() {
r := gin.Default()
m := melody.New()
r.GET("/", func(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "index.html")
})
r.GET("/ws", func(c *gin.Context) {
m.HandleRequest(c.Writer, c.Request)
})
m.HandleMessage(func(s *melody.Session, msg []byte) {
m.Broadcast(msg)
})
r.Run(":5000")
}
```
Using [Echo](https://github.com/labstack/echo):
```go
package main
import (
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
"gopkg.in/olahol/melody.v1"
"net/http"
)
func main() {
e := echo.New()
m := melody.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/", func(c echo.Context) error {
http.ServeFile(c.Response().(*standard.Response).ResponseWriter, c.Request().(*standard.Request).Request, "index.html")
return nil
})
e.GET("/ws", func(c echo.Context) error {
m.HandleRequest(c.Response().(*standard.Response).ResponseWriter, c.Request().(*standard.Request).Request)
return nil
})
m.HandleMessage(func(s *melody.Session, msg []byte) {
m.Broadcast(msg)
})
e.Run(standard.New(":5000"))
}
```
## [Example: gophers](https://github.com/olahol/melody/tree/master/examples/gophers)
[![Gophers](https://cdn.rawgit.com/olahol/melody/master/examples/gophers/demo.gif "Demo")](https://github.com/olahol/melody/tree/master/examples/gophers)
```go
package main
import (
"github.com/gin-gonic/gin"
"gopkg.in/olahol/melody.v1"
"net/http"
"strconv"
"strings"
"sync"
)
type GopherInfo struct {
ID, X, Y string
}
func main() {
router := gin.Default()
mrouter := melody.New()
gophers := make(map[*melody.Session]*GopherInfo)
lock := new(sync.Mutex)
counter := 0
router.GET("/", func(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "index.html")
})
router.GET("/ws", func(c *gin.Context) {
mrouter.HandleRequest(c.Writer, c.Request)
})
mrouter.HandleConnect(func(s *melody.Session) {
lock.Lock()
for _, info := range gophers {
s.Write([]byte("set " + info.ID + " " + info.X + " " + info.Y))
}
gophers[s] = &GopherInfo{strconv.Itoa(counter), "0", "0"}
s.Write([]byte("iam " + gophers[s].ID))
counter += 1
lock.Unlock()
})
mrouter.HandleDisconnect(func(s *melody.Session) {
lock.Lock()
mrouter.BroadcastOthers([]byte("dis "+gophers[s].ID), s)
delete(gophers, s)
lock.Unlock()
})
mrouter.HandleMessage(func(s *melody.Session, msg []byte) {
p := strings.Split(string(msg), " ")
lock.Lock()
info := gophers[s]
if len(p) == 2 {
info.X = p[0]
info.Y = p[1]
mrouter.BroadcastOthers([]byte("set "+info.ID+" "+info.X+" "+info.Y), s)
}
lock.Unlock()
})
router.Run(":5000")
}
```
### [More examples](https://github.com/olahol/melody/tree/master/examples)
## [Documentation](https://godoc.org/github.com/olahol/melody)
## Contributors
* Ola Holmström (@olahol)
* Shogo Iwano (@shiwano)
* Matt Caldwell (@mattcaldwell)
* Heikki Uljas (@huljas)
* Robbie Trencheny (@robbiet480)
* yangjinecho (@yangjinecho)
## FAQ
If you are getting a `403` when trying to connect to your websocket you can [change allow all origin hosts](http://godoc.org/github.com/gorilla/websocket#hdr-Origin_Considerations):
```go
m := melody.New()
m.Upgrader.CheckOrigin = func(r *http.Request) bool { return true }
```

6
vendor/modules.txt vendored
View File

@ -333,6 +333,9 @@ github.com/mrexodia/wray
# github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94 # github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
## explicit; go 1.12 ## explicit; go 1.12
github.com/nelsonken/gomf github.com/nelsonken/gomf
# github.com/olahol/melody v1.1.2
## explicit; go 1.19
github.com/olahol/melody
# github.com/opentracing/opentracing-go v1.2.0 # github.com/opentracing/opentracing-go v1.2.0
## explicit; go 1.14 ## explicit; go 1.14
github.com/opentracing/opentracing-go github.com/opentracing/opentracing-go
@ -698,9 +701,6 @@ gopkg.in/ini.v1
# gopkg.in/natefinch/lumberjack.v2 v2.0.0 # gopkg.in/natefinch/lumberjack.v2 v2.0.0
## explicit ## explicit
gopkg.in/natefinch/lumberjack.v2 gopkg.in/natefinch/lumberjack.v2
# gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
## explicit
gopkg.in/olahol/melody.v1
# gopkg.in/yaml.v2 v2.4.0 # gopkg.in/yaml.v2 v2.4.0
## explicit; go 1.15 ## explicit; go 1.15
gopkg.in/yaml.v2 gopkg.in/yaml.v2