forked from lug/matterbridge
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6dafebc7cc | ||
![]() |
d23f2560d5 | ||
![]() |
880586bac4 | ||
![]() |
eac2a8c8dc | ||
![]() |
4cf313c4c6 | ||
![]() |
91353d0a4d | ||
![]() |
0a6d64ab48 | ||
![]() |
4fd0a76727 | ||
![]() |
6da9d567dc | ||
![]() |
6d5a3dff22 | ||
![]() |
3ad5deaff1 | ||
![]() |
9bbdf70e69 | ||
![]() |
0c83946983 | ||
![]() |
fda05f2262 | ||
![]() |
7abf1a5884 | ||
![]() |
365acc36ea | ||
![]() |
0482cd191d | ||
![]() |
6a3fc71397 | ||
![]() |
3c4192ebf6 | ||
![]() |
e450e1c447 | ||
![]() |
20f841c513 | ||
![]() |
d07a3e09c9 | ||
![]() |
4649876956 | ||
![]() |
5604d140e3 | ||
![]() |
8751fb4bb1 | ||
![]() |
3819062574 |
8
.github/workflows/development.yml
vendored
8
.github/workflows/development.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
test-build-upload:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.17.x]
|
||||
go-version: [1.18.x]
|
||||
platform: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
@@ -39,19 +39,19 @@ jobs:
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -X github.com/42wim/matterbridge/version.GitHash=$(git log --pretty=format:'%h' -n 1)" -o output/win/matterbridge-$VERSION-windows-amd64.exe
|
||||
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
|
||||
if: startsWith(matrix.go-version,'1.17')
|
||||
if: startsWith(matrix.go-version,'1.18')
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: matterbridge-linux-64bit
|
||||
path: output/lin
|
||||
- name: Upload windows 64-bit
|
||||
if: startsWith(matrix.go-version,'1.17')
|
||||
if: startsWith(matrix.go-version,'1.18')
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: matterbridge-windows-64bit
|
||||
path: output/win
|
||||
- name: Upload darwin 64-bit
|
||||
if: startsWith(matrix.go-version,'1.17')
|
||||
if: startsWith(matrix.go-version,'1.18')
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: matterbridge-darwin-64bit
|
||||
|
@@ -204,6 +204,14 @@ linters:
|
||||
- tagliatelle
|
||||
- errname
|
||||
- typecheck
|
||||
- grouper
|
||||
- decorder
|
||||
- maintidx
|
||||
- exhaustruct
|
||||
- asasalint
|
||||
- execinquery
|
||||
- nosnakecase
|
||||
- exhaustive
|
||||
# rules to deal with reported isues
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
|
@@ -146,6 +146,7 @@ Used by the projects below. Feel free to make a PR to add your project to this l
|
||||
- [Vintage Story](https://github.com/NikkyAI/vs-matterbridge)
|
||||
- [ServUO-matterbridge](https://github.com/kuoushi/ServUO-Matterbridge) (A matterbridge connector for ServUO servers)
|
||||
- [ts-matterbridge](https://github.com/Archeb/ts-matterbridge) (Integrate teamspeak chat with matterbridge)
|
||||
- [beerchat](https://github.com/mt-mods/beerchat) (Matterbridge link for minetest)
|
||||
|
||||
## Chat with us
|
||||
|
||||
@@ -172,7 +173,7 @@ See <https://github.com/42wim/matterbridge/wiki>
|
||||
|
||||
### Binaries
|
||||
|
||||
- Latest stable release [v1.25.1](https://github.com/42wim/matterbridge/releases/latest)
|
||||
- Latest stable release [v1.26.0](https://github.com/42wim/matterbridge/releases/latest)
|
||||
- Development releases (follows master) can be downloaded [here](https://github.com/42wim/matterbridge/actions) selecting the latest green build and then artifacts.
|
||||
|
||||
To install or upgrade just download the latest [binary](https://github.com/42wim/matterbridge/releases/latest). On \*nix platforms you may need to make the binary executable - you can do this by running `chmod a+x` on the binary (example: `chmod a+x matterbridge-1.24.1-linux-64bit`). After downloading (and making the binary executable, if necessary), follow the instructions on the [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration.
|
||||
@@ -188,7 +189,7 @@ To install or upgrade just download the latest [binary](https://github.com/42wim
|
||||
Most people just want to use binaries, you can find those [here](https://github.com/42wim/matterbridge/releases/latest)
|
||||
|
||||
If you really want to build from source, follow these instructions:
|
||||
Go 1.17+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
|
||||
Go 1.18+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed.
|
||||
|
||||
Building the binary with **all** the bridges enabled needs about 3GB RAM to compile.
|
||||
You can reduce this memory requirement to 0,5GB RAM by adding the `nomsteams` tag if you don't need/use the Microsoft Teams bridge.
|
||||
|
@@ -83,12 +83,12 @@ func (b *Bdiscord) Connect() error {
|
||||
b.Log.Info("Connection succeeded")
|
||||
b.c.AddHandler(b.messageCreate)
|
||||
b.c.AddHandler(b.messageTyping)
|
||||
b.c.AddHandler(b.memberUpdate)
|
||||
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)
|
||||
}
|
||||
|
@@ -7,6 +7,10 @@ import (
|
||||
)
|
||||
|
||||
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam
|
||||
if m.GuildID != b.guildID {
|
||||
b.Log.Debugf("Ignoring messageDelete because it originates from a different guild")
|
||||
return
|
||||
}
|
||||
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
|
||||
rmsg.Channel = b.getChannelName(m.ChannelID)
|
||||
|
||||
@@ -17,6 +21,10 @@ func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelet
|
||||
|
||||
// TODO(qaisjp): if other bridges support bulk deletions, it could be fanned out centrally
|
||||
func (b *Bdiscord) messageDeleteBulk(s *discordgo.Session, m *discordgo.MessageDeleteBulk) { //nolint:unparam
|
||||
if m.GuildID != b.guildID {
|
||||
b.Log.Debugf("Ignoring messageDeleteBulk because it originates from a different guild")
|
||||
return
|
||||
}
|
||||
for _, msgID := range m.Messages {
|
||||
rmsg := config.Message{
|
||||
Account: b.Account,
|
||||
@@ -37,6 +45,10 @@ func (b *Bdiscord) messageEvent(s *discordgo.Session, m *discordgo.Event) {
|
||||
}
|
||||
|
||||
func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart) {
|
||||
if m.GuildID != b.guildID {
|
||||
b.Log.Debugf("Ignoring messageTyping because it originates from a different guild")
|
||||
return
|
||||
}
|
||||
if !b.GetBool("ShowUserTyping") {
|
||||
return
|
||||
}
|
||||
@@ -52,6 +64,10 @@ func (b *Bdiscord) messageTyping(s *discordgo.Session, m *discordgo.TypingStart)
|
||||
}
|
||||
|
||||
func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { //nolint:unparam
|
||||
if m.GuildID != b.guildID {
|
||||
b.Log.Debugf("Ignoring messageUpdate because it originates from a different guild")
|
||||
return
|
||||
}
|
||||
if b.GetBool("EditDisable") {
|
||||
return
|
||||
}
|
||||
@@ -67,6 +83,10 @@ func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdat
|
||||
}
|
||||
|
||||
func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { //nolint:unparam
|
||||
if m.GuildID != b.guildID {
|
||||
b.Log.Debugf("Ignoring messageCreate because it originates from a different guild")
|
||||
return
|
||||
}
|
||||
var err error
|
||||
|
||||
// not relay our own messages
|
||||
@@ -144,6 +164,10 @@ func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
||||
}
|
||||
|
||||
func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
|
||||
if m.GuildID != b.guildID {
|
||||
b.Log.Debugf("Ignoring memberUpdate because it originates from a different guild")
|
||||
return
|
||||
}
|
||||
if m.Member == nil {
|
||||
b.Log.Warnf("Received member update with no member information: %#v", m)
|
||||
}
|
||||
@@ -171,6 +195,13 @@ func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUp
|
||||
}
|
||||
|
||||
func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
|
||||
if m.GuildID != b.guildID {
|
||||
b.Log.Debugf("Ignoring memberAdd because it originates from a different guild")
|
||||
return
|
||||
}
|
||||
if b.GetBool("nosendjoinpart") {
|
||||
return
|
||||
}
|
||||
if m.Member == nil {
|
||||
b.Log.Warnf("Received member update with no member information: %#v", m)
|
||||
return
|
||||
@@ -192,6 +223,13 @@ func (b *Bdiscord) memberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd)
|
||||
}
|
||||
|
||||
func (b *Bdiscord) memberRemove(s *discordgo.Session, m *discordgo.GuildMemberRemove) {
|
||||
if m.GuildID != b.guildID {
|
||||
b.Log.Debugf("Ignoring memberRemove because it originates from a different guild")
|
||||
return
|
||||
}
|
||||
if b.GetBool("nosendjoinpart") {
|
||||
return
|
||||
}
|
||||
if m.Member == nil {
|
||||
b.Log.Warnf("Received member update with no member information: %#v", m)
|
||||
return
|
||||
|
@@ -86,6 +86,12 @@ func GetSubLines(message string, maxLineLength int, clippingMessage string) []st
|
||||
|
||||
var lines []string
|
||||
for _, line := range strings.Split(strings.TrimSpace(message), "\n") {
|
||||
if line == "" {
|
||||
// Prevent sending empty messages, so we'll skip this line
|
||||
// if it has no content.
|
||||
continue
|
||||
}
|
||||
|
||||
if maxLineLength == 0 || len([]byte(line)) <= maxLineLength {
|
||||
lines = append(lines, line)
|
||||
continue
|
||||
|
@@ -1,5 +1,4 @@
|
||||
//go:build cgo
|
||||
// +build cgo
|
||||
//go:build cgolottie
|
||||
|
||||
package helper
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// +build !cgo
|
||||
//go:build !cgolottie
|
||||
|
||||
package helper
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@@ -243,6 +243,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
||||
|
||||
func (b *Birc) handleRunCommands() {
|
||||
for _, cmd := range b.GetStringSlice("RunCommands") {
|
||||
cmd = strings.ReplaceAll(cmd, "{BOTNICK}", b.Nick)
|
||||
if err := b.i.Cmd.SendRaw(cmd); err != nil {
|
||||
b.Log.Errorf("RunCommands %s failed: %s", cmd, err)
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ func interface2Struct(in interface{}, out interface{}) error {
|
||||
return json.Unmarshal(jsonObj, out)
|
||||
}
|
||||
|
||||
// getDisplayName retrieves the displayName for mxid, querying the homserver if the mxid is not in the cache.
|
||||
// getDisplayName retrieves the displayName for mxid, querying the homeserver if the mxid is not in the cache.
|
||||
func (b *Bmatrix) getDisplayName(mxid string) string {
|
||||
if b.GetBool("UseUserName") {
|
||||
return mxid[1:]
|
||||
|
@@ -148,12 +148,37 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
|
||||
username := newMatrixUsername(msg.Username)
|
||||
|
||||
body := username.plain + msg.Text
|
||||
formattedBody := username.formatted + helper.ParseMarkdown(msg.Text)
|
||||
|
||||
if b.GetBool("SpoofUsername") {
|
||||
// https://spec.matrix.org/v1.3/client-server-api/#mroommember
|
||||
type stateMember struct {
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
DisplayName string `json:"displayname"`
|
||||
Membership string `json:"membership"`
|
||||
}
|
||||
|
||||
// TODO: reset username afterwards with DisplayName: null ?
|
||||
m := stateMember{
|
||||
AvatarURL: "",
|
||||
DisplayName: username.plain,
|
||||
Membership: "join",
|
||||
}
|
||||
|
||||
_, err := b.mc.SendStateEvent(channel, "m.room.member", b.UserID, m)
|
||||
if err == nil {
|
||||
body = msg.Text
|
||||
formattedBody = helper.ParseMarkdown(msg.Text)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a action /me of the message
|
||||
if msg.Event == config.EventUserAction {
|
||||
m := matrix.TextMessage{
|
||||
MsgType: "m.emote",
|
||||
Body: username.plain + msg.Text,
|
||||
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
|
||||
Body: body,
|
||||
FormattedBody: formattedBody,
|
||||
Format: "org.matrix.custom.html",
|
||||
}
|
||||
|
||||
@@ -224,10 +249,10 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
if msg.ID != "" {
|
||||
rmsg := EditedMessage{
|
||||
TextMessage: matrix.TextMessage{
|
||||
Body: username.plain + msg.Text,
|
||||
Body: body,
|
||||
MsgType: "m.text",
|
||||
Format: "org.matrix.custom.html",
|
||||
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
|
||||
FormattedBody: formattedBody,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -266,8 +291,8 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
if msg.Event == config.EventJoinLeave {
|
||||
m := matrix.TextMessage{
|
||||
MsgType: "m.notice",
|
||||
Body: username.plain + msg.Text,
|
||||
FormattedBody: username.formatted + msg.Text,
|
||||
Body: body,
|
||||
FormattedBody: formattedBody,
|
||||
Format: "org.matrix.custom.html",
|
||||
}
|
||||
|
||||
@@ -297,8 +322,8 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
m := ReplyMessage{
|
||||
TextMessage: matrix.TextMessage{
|
||||
MsgType: "m.text",
|
||||
Body: username.plain + msg.Text,
|
||||
FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
|
||||
Body: body,
|
||||
FormattedBody: formattedBody,
|
||||
Format: "org.matrix.custom.html",
|
||||
},
|
||||
}
|
||||
@@ -338,7 +363,7 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
)
|
||||
|
||||
err = b.retry(func() error {
|
||||
resp, err = b.mc.SendText(channel, username.plain+msg.Text)
|
||||
resp, err = b.mc.SendText(channel, body)
|
||||
|
||||
return err
|
||||
})
|
||||
@@ -356,8 +381,7 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
)
|
||||
|
||||
err = b.retry(func() error {
|
||||
resp, err = b.mc.SendFormattedText(channel, username.plain+msg.Text,
|
||||
username.formatted+helper.ParseMarkdown(msg.Text))
|
||||
resp, err = b.mc.SendFormattedText(channel, body, formattedBody)
|
||||
|
||||
return err
|
||||
})
|
||||
|
@@ -3,10 +3,8 @@ package bmattermost
|
||||
import (
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/42wim/matterbridge/matterclient"
|
||||
matterclient6 "github.com/matterbridge/matterclient"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
model6 "github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/matterbridge/matterclient"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
// handleDownloadAvatar downloads the avatar of userid from channel
|
||||
@@ -26,20 +24,11 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
resp *model.Response
|
||||
)
|
||||
if b.mc6 != nil {
|
||||
data, _, err = b.mc6.Client.GetProfileImage(userid, "")
|
||||
if err != nil {
|
||||
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
data, resp = b.mc.Client.GetProfileImage(userid, "")
|
||||
if resp.Error != nil {
|
||||
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error)
|
||||
return
|
||||
}
|
||||
data, _, err = b.mc.Client.GetProfileImage(userid, "")
|
||||
if err != nil {
|
||||
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = helper.HandleDownloadSize(b.Log, &rmsg, userid+".png", int64(len(data)), b.General)
|
||||
@@ -52,33 +41,10 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
|
||||
}
|
||||
}
|
||||
|
||||
// handleDownloadFile handles file download
|
||||
//nolint:wrapcheck
|
||||
func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error {
|
||||
if b.mc6 != nil {
|
||||
return b.handleDownloadFile6(rmsg, id)
|
||||
}
|
||||
|
||||
url, _ := b.mc.Client.GetFileLink(id)
|
||||
finfo, resp := b.mc.Client.GetFileInfo(id)
|
||||
if resp.Error != nil {
|
||||
return resp.Error
|
||||
}
|
||||
err := helper.HandleDownloadSize(b.Log, rmsg, finfo.Name, finfo.Size, b.General)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, resp := b.mc.Client.DownloadFile(id, true)
|
||||
if resp.Error != nil {
|
||||
return resp.Error
|
||||
}
|
||||
helper.HandleDownloadData(b.Log, rmsg, finfo.Name, rmsg.Text, url, &data, b.General)
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint:wrapcheck
|
||||
func (b *Bmattermost) handleDownloadFile6(rmsg *config.Message, id string) error {
|
||||
url, _, _ := b.mc6.Client.GetFileLink(id)
|
||||
finfo, _, err := b.mc6.Client.GetFileInfo(id)
|
||||
url, _, _ := b.mc.Client.GetFileLink(id)
|
||||
finfo, _, err := b.mc.Client.GetFileInfo(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -86,7 +52,7 @@ func (b *Bmattermost) handleDownloadFile6(rmsg *config.Message, id string) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, _, err := b.mc6.Client.DownloadFile(id, true)
|
||||
data, _, err := b.mc.Client.DownloadFile(id, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,15 +91,10 @@ func (b *Bmattermost) handleMatter() {
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
||||
if b.mc6 != nil {
|
||||
b.Log.Debug("starting matterclient6")
|
||||
b.handleMatterClient6(messages)
|
||||
return
|
||||
}
|
||||
|
||||
for message := range b.mc.MessageChan {
|
||||
b.Log.Debugf("%#v", message.Raw.Data)
|
||||
b.Log.Debugf("%#v %#v", message.Raw.GetData(), message.Raw.EventType())
|
||||
|
||||
if b.skipMessage(message) {
|
||||
b.Log.Debugf("Skipped message: %#v", message)
|
||||
@@ -166,11 +127,11 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
||||
b.handleProps(rmsg, message)
|
||||
|
||||
// create a text for bridges that don't support native editing
|
||||
if message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED && !b.GetBool("EditDisable") {
|
||||
if message.Raw.EventType() == model.WebsocketEventPostEdited && !b.GetBool("EditDisable") {
|
||||
rmsg.Text = message.Text + b.GetString("EditSuffix")
|
||||
}
|
||||
|
||||
if message.Raw.Event == model.WEBSOCKET_EVENT_POST_DELETED {
|
||||
if message.Raw.EventType() == model.WebsocketEventPostDeleted {
|
||||
rmsg.Event = config.EventMsgDelete
|
||||
}
|
||||
|
||||
@@ -192,68 +153,6 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:cyclop
|
||||
func (b *Bmattermost) handleMatterClient6(messages chan *config.Message) {
|
||||
for message := range b.mc6.MessageChan {
|
||||
b.Log.Debugf("%#v %#v", message.Raw.GetData(), message.Raw.EventType())
|
||||
|
||||
if b.skipMessage6(message) {
|
||||
b.Log.Debugf("Skipped message: %#v", message)
|
||||
continue
|
||||
}
|
||||
|
||||
channelName := b.getChannelName(message.Post.ChannelId)
|
||||
if channelName == "" {
|
||||
channelName = message.Channel
|
||||
}
|
||||
|
||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
||||
if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" {
|
||||
b.handleDownloadAvatar(message.UserID, channelName)
|
||||
}
|
||||
|
||||
b.Log.Debugf("== Receiving event %#v", message)
|
||||
|
||||
rmsg := &config.Message{
|
||||
Username: message.Username,
|
||||
UserID: message.UserID,
|
||||
Channel: channelName,
|
||||
Text: message.Text,
|
||||
ID: message.Post.Id,
|
||||
ParentID: message.Post.RootId, // ParentID is obsolete with mattermost
|
||||
Extra: make(map[string][]interface{}),
|
||||
}
|
||||
|
||||
// handle mattermost post properties (override username and attachments)
|
||||
b.handleProps6(rmsg, message)
|
||||
|
||||
// create a text for bridges that don't support native editing
|
||||
if message.Raw.EventType() == model6.WebsocketEventPostEdited && !b.GetBool("EditDisable") {
|
||||
rmsg.Text = message.Text + b.GetString("EditSuffix")
|
||||
}
|
||||
|
||||
if message.Raw.EventType() == model6.WebsocketEventPostDeleted {
|
||||
rmsg.Event = config.EventMsgDelete
|
||||
}
|
||||
|
||||
for _, id := range message.Post.FileIds {
|
||||
err := b.handleDownloadFile(rmsg, id)
|
||||
if err != nil {
|
||||
b.Log.Errorf("download failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Use nickname instead of username if defined
|
||||
if !b.GetBool("useusername") {
|
||||
if nick := b.mc6.GetNickName(rmsg.UserID); nick != "" {
|
||||
rmsg.Username = nick
|
||||
}
|
||||
}
|
||||
|
||||
messages <- rmsg
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
|
||||
for {
|
||||
message := b.mh.Receive()
|
||||
@@ -268,12 +167,7 @@ func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
// handleUploadFile handles native upload of files
|
||||
func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
|
||||
if b.mc6 != nil {
|
||||
return b.handleUploadFile6(msg)
|
||||
}
|
||||
|
||||
var err error
|
||||
var res, id string
|
||||
channelID := b.getChannelID(msg.Channel)
|
||||
@@ -292,53 +186,8 @@ func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// nolint:forcetypeassert,wrapcheck
|
||||
func (b *Bmattermost) handleUploadFile6(msg *config.Message) (string, error) {
|
||||
var err error
|
||||
var res, id string
|
||||
channelID := b.getChannelID(msg.Channel)
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
id, err = b.mc6.UploadFile(*fi.Data, channelID, fi.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg.Text = fi.Comment
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
msg.Text = msg.Username + msg.Text
|
||||
}
|
||||
res, err = b.mc6.PostMessageWithFiles(channelID, msg.Text, msg.ParentID, []string{id})
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
//nolint:forcetypeassert
|
||||
func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.Message) {
|
||||
props := message.Post.Props
|
||||
if props == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := props["override_username"].(string); ok {
|
||||
rmsg.Username = props["override_username"].(string)
|
||||
}
|
||||
if _, ok := props["attachments"].([]interface{}); ok {
|
||||
rmsg.Extra["attachments"] = props["attachments"].([]interface{})
|
||||
if rmsg.Text == "" {
|
||||
for _, attachment := range rmsg.Extra["attachments"] {
|
||||
attach := attachment.(map[string]interface{})
|
||||
if attach["text"].(string) != "" {
|
||||
rmsg.Text += attach["text"].(string)
|
||||
continue
|
||||
}
|
||||
if attach["fallback"].(string) != "" {
|
||||
rmsg.Text += attach["fallback"].(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:forcetypeassert
|
||||
func (b *Bmattermost) handleProps6(rmsg *config.Message, message *matterclient6.Message) {
|
||||
props := message.Post.Props
|
||||
if props == nil {
|
||||
return
|
||||
|
@@ -6,11 +6,9 @@ import (
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/42wim/matterbridge/matterclient"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
matterclient6 "github.com/matterbridge/matterclient"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
model6 "github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/matterbridge/matterclient"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func (b *Bmattermost) doConnectWebhookBind() error {
|
||||
@@ -24,33 +22,15 @@ func (b *Bmattermost) doConnectWebhookBind() error {
|
||||
})
|
||||
case b.GetString("Token") != "":
|
||||
b.Log.Info("Connecting using token (sending)")
|
||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||
|
||||
if b.v6 {
|
||||
err := b.apiLogin6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case b.GetString("Login") != "":
|
||||
b.Log.Info("Connecting using login/password (sending)")
|
||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||
|
||||
if b.v6 {
|
||||
err := b.apiLogin6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
||||
@@ -72,45 +52,28 @@ func (b *Bmattermost) doConnectWebhookURL() error {
|
||||
})
|
||||
if b.GetString("Token") != "" {
|
||||
b.Log.Info("Connecting using token (receiving)")
|
||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||
|
||||
if b.v6 {
|
||||
err := b.apiLogin6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if b.GetString("Login") != "" {
|
||||
b.Log.Info("Connecting using login/password (receiving)")
|
||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||
|
||||
if b.v6 {
|
||||
err := b.apiLogin6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:wrapcheck
|
||||
func (b *Bmattermost) apiLogin() error {
|
||||
password := b.GetString("Password")
|
||||
if b.GetString("Token") != "" {
|
||||
password = "token=" + b.GetString("Token")
|
||||
}
|
||||
|
||||
b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"))
|
||||
b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"), "")
|
||||
if b.GetBool("debug") {
|
||||
b.mc.SetLogLevel("debug")
|
||||
}
|
||||
@@ -118,39 +81,13 @@ func (b *Bmattermost) apiLogin() error {
|
||||
b.mc.SkipVersionCheck = b.GetBool("SkipVersionCheck")
|
||||
b.mc.NoTLS = b.GetBool("NoTLS")
|
||||
b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
|
||||
err := b.mc.Login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Log.Info("Connection succeeded")
|
||||
b.TeamID = b.mc.GetTeamId()
|
||||
go b.mc.WsReceiver()
|
||||
go b.mc.StatusLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint:wrapcheck
|
||||
func (b *Bmattermost) apiLogin6() error {
|
||||
password := b.GetString("Password")
|
||||
if b.GetString("Token") != "" {
|
||||
password = "token=" + b.GetString("Token")
|
||||
}
|
||||
|
||||
b.mc6 = matterclient6.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"), "")
|
||||
if b.GetBool("debug") {
|
||||
b.mc6.SetLogLevel("debug")
|
||||
}
|
||||
b.mc6.SkipTLSVerify = b.GetBool("SkipTLSVerify")
|
||||
b.mc6.SkipVersionCheck = b.GetBool("SkipVersionCheck")
|
||||
b.mc6.NoTLS = b.GetBool("NoTLS")
|
||||
b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
|
||||
|
||||
if err := b.mc6.Login(); err != nil {
|
||||
if err := b.mc.Login(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Log.Info("Connection succeeded")
|
||||
b.TeamID = b.mc6.GetTeamID()
|
||||
b.TeamID = b.mc.GetTeamID()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -234,6 +171,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
|
||||
}
|
||||
|
||||
// skipMessages returns true if this message should not be handled
|
||||
//nolint:gocyclo,cyclop
|
||||
func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
|
||||
// Handle join/leave
|
||||
if message.Type == "system_join_leave" ||
|
||||
@@ -260,76 +198,7 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
|
||||
}
|
||||
|
||||
// Handle edited messages
|
||||
if (message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED) && b.GetBool("EditDisable") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ignore non-post messages
|
||||
if message.Post == nil {
|
||||
b.Log.Debugf("ignoring nil message.Post: %#v", message)
|
||||
return true
|
||||
}
|
||||
|
||||
// Ignore messages sent from matterbridge
|
||||
if message.Post.Props != nil {
|
||||
if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok {
|
||||
b.Log.Debugf("sent by matterbridge, ignoring")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore messages sent from a user logged in as the bot
|
||||
if b.mc.User.Username == message.Username {
|
||||
return true
|
||||
}
|
||||
|
||||
// if the message has reactions don't repost it (for now, until we can correlate reaction with message)
|
||||
if message.Post.HasReactions {
|
||||
return true
|
||||
}
|
||||
|
||||
// ignore messages from other teams than ours
|
||||
if message.Raw.Data["team_id"].(string) != b.TeamID {
|
||||
return true
|
||||
}
|
||||
|
||||
// only handle posted, edited or deleted events
|
||||
if !(message.Raw.Event == "posted" || message.Raw.Event == model.WEBSOCKET_EVENT_POST_EDITED ||
|
||||
message.Raw.Event == model.WEBSOCKET_EVENT_POST_DELETED) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// skipMessages returns true if this message should not be handled
|
||||
// nolint:gocyclo,cyclop
|
||||
func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool {
|
||||
// Handle join/leave
|
||||
if message.Type == "system_join_leave" ||
|
||||
message.Type == "system_join_channel" ||
|
||||
message.Type == "system_leave_channel" {
|
||||
if b.GetBool("nosendjoinpart") {
|
||||
return true
|
||||
}
|
||||
|
||||
channelName := b.getChannelName(message.Post.ChannelId)
|
||||
if channelName == "" {
|
||||
channelName = message.Channel
|
||||
}
|
||||
|
||||
b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||
b.Remote <- config.Message{
|
||||
Username: "system",
|
||||
Text: message.Text,
|
||||
Channel: channelName,
|
||||
Account: b.Account,
|
||||
Event: config.EventJoinLeave,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle edited messages
|
||||
if (message.Raw.EventType() == model6.WebsocketEventPostEdited) && b.GetBool("EditDisable") {
|
||||
if (message.Raw.EventType() == model.WebsocketEventPostEdited) && b.GetBool("EditDisable") {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -348,7 +217,7 @@ func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool {
|
||||
}
|
||||
|
||||
// Ignore messages sent from a user logged in as the bot
|
||||
if b.mc6.User.Username == message.Username {
|
||||
if b.mc.User.Username == message.Username {
|
||||
b.Log.Debug("message from same user as bot, ignoring")
|
||||
return true
|
||||
}
|
||||
@@ -365,8 +234,8 @@ func (b *Bmattermost) skipMessage6(message *matterclient6.Message) bool {
|
||||
}
|
||||
|
||||
// only handle posted, edited or deleted events
|
||||
if !(message.Raw.EventType() == "posted" || message.Raw.EventType() == model6.WebsocketEventPostEdited ||
|
||||
message.Raw.EventType() == model6.WebsocketEventPostDeleted) {
|
||||
if !(message.Raw.EventType() == "posted" || message.Raw.EventType() == model.WebsocketEventPostEdited ||
|
||||
message.Raw.EventType() == model.WebsocketEventPostDeleted) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -396,11 +265,7 @@ func (b *Bmattermost) getChannelID(name string) string {
|
||||
return idcheck[1]
|
||||
}
|
||||
|
||||
if b.mc6 != nil {
|
||||
return b.mc6.GetChannelID(name, b.TeamID)
|
||||
}
|
||||
|
||||
return b.mc.GetChannelId(name, b.TeamID)
|
||||
return b.mc.GetChannelID(name, b.TeamID)
|
||||
}
|
||||
|
||||
func (b *Bmattermost) getChannelName(id string) string {
|
||||
|
@@ -9,16 +9,14 @@ import (
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/42wim/matterbridge/matterclient"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
matterclient6 "github.com/matterbridge/matterclient"
|
||||
"github.com/matterbridge/matterclient"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
type Bmattermost struct {
|
||||
mh *matterhook.Client
|
||||
mc *matterclient.MMClient
|
||||
mc6 *matterclient6.Client
|
||||
mc *matterclient.Client
|
||||
v6 bool
|
||||
uuid string
|
||||
TeamID string
|
||||
@@ -52,7 +50,7 @@ func (b *Bmattermost) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(b.getVersion(), "6.") {
|
||||
if strings.HasPrefix(b.getVersion(), "6.") || strings.HasPrefix(b.getVersion(), "7.") {
|
||||
if !b.v6 {
|
||||
b.v6 = true
|
||||
}
|
||||
@@ -74,34 +72,17 @@ func (b *Bmattermost) Connect() error {
|
||||
return nil
|
||||
case b.GetString("Token") != "":
|
||||
b.Log.Info("Connecting using token (sending and receiving)")
|
||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||
|
||||
if b.v6 {
|
||||
err := b.apiLogin6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go b.handleMatter()
|
||||
case b.GetString("Login") != "":
|
||||
b.Log.Info("Connecting using login/password (sending and receiving)")
|
||||
b.Log.Infof("Using mattermost v6 methods: %t", b.v6)
|
||||
|
||||
if b.v6 {
|
||||
err := b.apiLogin6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go b.handleMatter()
|
||||
}
|
||||
@@ -132,10 +113,6 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
|
||||
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
|
||||
}
|
||||
|
||||
if b.mc6 != nil {
|
||||
return b.mc6.JoinChannel(id) // nolint:wrapcheck
|
||||
}
|
||||
|
||||
return b.mc.JoinChannel(id)
|
||||
}
|
||||
|
||||
@@ -168,9 +145,6 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
}
|
||||
if b.mc6 != nil {
|
||||
return msg.ID, b.mc6.DeleteMessage(msg.ID) // nolint:wrapcheck
|
||||
}
|
||||
|
||||
return msg.ID, b.mc.DeleteMessage(msg.ID)
|
||||
}
|
||||
@@ -183,36 +157,20 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
||||
|
||||
// we only can reply to the root of the thread, not to a specific ID (like discord for example does)
|
||||
if msg.ParentID != "" {
|
||||
if b.mc6 != nil {
|
||||
post, _, err := b.mc6.Client.GetPost(msg.ParentID, "")
|
||||
if err != nil {
|
||||
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err)
|
||||
}
|
||||
if post.RootId != "" {
|
||||
msg.ParentID = post.RootId
|
||||
}
|
||||
} else {
|
||||
post, res := b.mc.Client.GetPost(msg.ParentID, "")
|
||||
if res.Error != nil {
|
||||
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, res.Error.DetailedError)
|
||||
}
|
||||
if post.RootId != "" {
|
||||
msg.ParentID = post.RootId
|
||||
}
|
||||
post, _, err := b.mc.Client.GetPost(msg.ParentID, "")
|
||||
if err != nil {
|
||||
b.Log.Errorf("getting post %s failed: %s", msg.ParentID, err)
|
||||
}
|
||||
if post.RootId != "" {
|
||||
msg.ParentID = post.RootId
|
||||
}
|
||||
}
|
||||
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
if b.mc6 != nil {
|
||||
if _, err := b.mc6.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
|
||||
b.Log.Errorf("PostMessage failed: %s", err)
|
||||
}
|
||||
} else {
|
||||
if _, err := b.mc.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
|
||||
b.Log.Errorf("PostMessage failed: %s", err)
|
||||
}
|
||||
if _, err := b.mc.PostMessage(b.getChannelID(rmsg.Channel), rmsg.Username+rmsg.Text, msg.ParentID); err != nil {
|
||||
b.Log.Errorf("PostMessage failed: %s", err)
|
||||
}
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
@@ -227,17 +185,9 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
||||
|
||||
// Edit message if we have an ID
|
||||
if msg.ID != "" {
|
||||
if b.mc6 != nil {
|
||||
return b.mc6.EditMessage(msg.ID, msg.Text) // nolint:wrapcheck
|
||||
}
|
||||
|
||||
return b.mc.EditMessage(msg.ID, msg.Text)
|
||||
}
|
||||
|
||||
// Post normal message
|
||||
if b.mc6 != nil {
|
||||
return b.mc6.PostMessage(b.getChannelID(msg.Channel), msg.Text, msg.ParentID) // nolint:wrapcheck
|
||||
}
|
||||
|
||||
return b.mc.PostMessage(b.getChannelID(msg.Channel), msg.Text, msg.ParentID)
|
||||
}
|
||||
|
@@ -78,19 +78,75 @@ func (b *Bmumble) handleConnect(event *gumble.ConnectEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bmumble) handleUserChange(event *gumble.UserChangeEvent) {
|
||||
// Only care about changes to self
|
||||
if event.User != event.Client.Self {
|
||||
func (b *Bmumble) handleJoinLeave(event *gumble.UserChangeEvent) {
|
||||
// Ignore events happening before setup is done
|
||||
if b.Channel == nil {
|
||||
return
|
||||
}
|
||||
// Someone attempted to move the user out of the configured channel; attempt to join back
|
||||
if b.Channel != nil {
|
||||
if b.GetBool("nosendjoinpart") {
|
||||
return
|
||||
}
|
||||
b.Log.Debugf("Received gumble user change event: %+v", event)
|
||||
|
||||
text := ""
|
||||
switch {
|
||||
case event.Type&gumble.UserChangeKicked > 0:
|
||||
text = " was kicked"
|
||||
case event.Type&gumble.UserChangeBanned > 0:
|
||||
text = " was banned"
|
||||
case event.Type&gumble.UserChangeDisconnected > 0:
|
||||
if event.User.Channel != nil && event.User.Channel.ID == *b.Channel {
|
||||
text = " left"
|
||||
}
|
||||
case event.Type&gumble.UserChangeConnected > 0:
|
||||
if event.User.Channel != nil && event.User.Channel.ID == *b.Channel {
|
||||
text = " joined"
|
||||
}
|
||||
case event.Type&gumble.UserChangeChannel > 0:
|
||||
// Treat Mumble channel changes the same as connects/disconnects; as far as matterbridge is concerned, they are identical
|
||||
if event.User.Channel != nil && event.User.Channel.ID == *b.Channel {
|
||||
text = " joined"
|
||||
} else {
|
||||
text = " left"
|
||||
}
|
||||
}
|
||||
|
||||
if text != "" {
|
||||
b.Remote <- config.Message{
|
||||
Username: "system",
|
||||
Text: event.User.Name + text,
|
||||
Channel: strconv.FormatUint(uint64(*b.Channel), 10),
|
||||
Account: b.Account,
|
||||
Event: config.EventJoinLeave,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bmumble) handleUserModified(event *gumble.UserChangeEvent) {
|
||||
// Ignore events happening before setup is done
|
||||
if b.Channel == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if event.Type&gumble.UserChangeChannel > 0 {
|
||||
// Someone attempted to move the user out of the configured channel; attempt to join back
|
||||
if err := b.doJoin(event.Client, *b.Channel); err != nil {
|
||||
b.Log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bmumble) handleUserChange(event *gumble.UserChangeEvent) {
|
||||
// The UserChangeEvent is used for both the gumble client itself as well as other clients
|
||||
if event.User != event.Client.Self {
|
||||
// other users
|
||||
b.handleJoinLeave(event)
|
||||
} else {
|
||||
// gumble user
|
||||
b.handleUserModified(event)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bmumble) handleDisconnect(event *gumble.DisconnectEvent) {
|
||||
b.connected <- *event
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ func (b *Bmumble) JoinChannel(channel config.ChannelInfo) error {
|
||||
func (b *Bmumble) Send(msg config.Message) (string, error) {
|
||||
// Only process text messages
|
||||
b.Log.Debugf("=> Received local message %#v", msg)
|
||||
if msg.Event != "" && msg.Event != config.EventUserAction {
|
||||
if msg.Event != "" && msg.Event != config.EventUserAction && msg.Event != config.EventJoinLeave {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
@@ -80,7 +80,7 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
||||
case *slack.FileDeletedEvent:
|
||||
rmsg, err := b.handleFileDeletedEvent(ev)
|
||||
if err != nil {
|
||||
b.Log.Errorf("%#v", err)
|
||||
b.Log.Printf("%#v", err)
|
||||
continue
|
||||
}
|
||||
messages <- rmsg
|
||||
|
@@ -176,7 +176,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
|
||||
if update.Message == nil && update.ChannelPost == nil &&
|
||||
update.EditedMessage == nil && update.EditedChannelPost == nil {
|
||||
b.Log.Error("Getting nil messages, this shouldn't happen.")
|
||||
b.Log.Info("Received event without messages, skipping.")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -451,6 +451,11 @@ func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64, parentID
|
||||
Name: fi.Name,
|
||||
Bytes: *fi.Data,
|
||||
}
|
||||
|
||||
if b.GetString("MessageFormat") == HTMLFormat {
|
||||
fi.Comment = makeHTML(html.EscapeString(fi.Comment))
|
||||
}
|
||||
|
||||
switch filepath.Ext(fi.Name) {
|
||||
case ".jpg", ".jpe", ".png":
|
||||
pc := tgbotapi.NewInputMediaPhoto(file)
|
||||
|
@@ -2,7 +2,6 @@ package btelegram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html"
|
||||
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
@@ -24,10 +23,16 @@ func (options *customHTML) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
func (options *customHTML) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
out.WriteString("<pre>")
|
||||
|
||||
out.WriteString(html.EscapeString(string(text)))
|
||||
out.WriteString(string(text))
|
||||
out.WriteString("</pre>\n")
|
||||
}
|
||||
|
||||
func (options *customHTML) CodeSpan(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("<code>")
|
||||
out.WriteString(string(text))
|
||||
out.WriteString("</code>")
|
||||
}
|
||||
|
||||
func (options *customHTML) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||
options.Paragraph(out, text)
|
||||
}
|
||||
@@ -42,6 +47,10 @@ func (options *customHTML) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *customHTML) LineBreak(out *bytes.Buffer) {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *customHTML) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||
options.Paragraph(out, text)
|
||||
}
|
||||
|
@@ -101,7 +101,7 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||
}
|
||||
|
||||
if b.GetString("MessageFormat") == HTMLFormat {
|
||||
msg.Text = makeHTML(msg.Text)
|
||||
msg.Text = makeHTML(html.EscapeString(msg.Text))
|
||||
}
|
||||
|
||||
// Delete message
|
||||
|
@@ -51,10 +51,7 @@ func (b *Bwhatsapp) handleTextMessage(messageInfo types.MessageInfo, msg *proto.
|
||||
senderJID := messageInfo.Sender
|
||||
channel := messageInfo.Chat
|
||||
|
||||
senderName := b.getSenderName(messageInfo.Sender)
|
||||
if senderName == "" {
|
||||
senderName = "Someone" // don't expose telephone number
|
||||
}
|
||||
senderName := b.getSenderName(messageInfo)
|
||||
|
||||
if msg.GetExtendedTextMessage() == nil && msg.GetConversation() == "" {
|
||||
b.Log.Debugf("message without text content? %#v", msg)
|
||||
@@ -82,9 +79,6 @@ func (b *Bwhatsapp) handleTextMessage(messageInfo types.MessageInfo, msg *proto.
|
||||
// mentions comes as telephone numbers and we don't want to expose it to other bridges
|
||||
// replace it with something more meaninful to others
|
||||
mention := b.getSenderNotify(types.NewJID(numberAndSuffix[0], types.DefaultUserServer))
|
||||
if mention == "" {
|
||||
mention = "someone"
|
||||
}
|
||||
|
||||
text = strings.Replace(text, "@"+numberAndSuffix[0], "@"+mention, 1)
|
||||
}
|
||||
@@ -118,7 +112,7 @@ func (b *Bwhatsapp) handleImageMessage(msg *events.Message) {
|
||||
imsg := msg.Message.GetImageMessage()
|
||||
|
||||
senderJID := msg.Info.Sender
|
||||
senderName := b.getSenderName(senderJID)
|
||||
senderName := b.getSenderName(msg.Info)
|
||||
ci := imsg.GetContextInfo()
|
||||
|
||||
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||
@@ -181,7 +175,7 @@ func (b *Bwhatsapp) handleVideoMessage(msg *events.Message) {
|
||||
imsg := msg.Message.GetVideoMessage()
|
||||
|
||||
senderJID := msg.Info.Sender
|
||||
senderName := b.getSenderName(senderJID)
|
||||
senderName := b.getSenderName(msg.Info)
|
||||
ci := imsg.GetContextInfo()
|
||||
|
||||
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||
@@ -238,7 +232,7 @@ func (b *Bwhatsapp) handleAudioMessage(msg *events.Message) {
|
||||
imsg := msg.Message.GetAudioMessage()
|
||||
|
||||
senderJID := msg.Info.Sender
|
||||
senderName := b.getSenderName(senderJID)
|
||||
senderName := b.getSenderName(msg.Info)
|
||||
ci := imsg.GetContextInfo()
|
||||
|
||||
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||
@@ -295,7 +289,7 @@ func (b *Bwhatsapp) handleDocumentMessage(msg *events.Message) {
|
||||
imsg := msg.Message.GetDocumentMessage()
|
||||
|
||||
senderJID := msg.Info.Sender
|
||||
senderName := b.getSenderName(senderJID)
|
||||
senderName := b.getSenderName(msg.Info)
|
||||
ci := imsg.GetContextInfo()
|
||||
|
||||
if senderJID == (types.JID{}) && ci.Participant != nil {
|
||||
@@ -335,7 +329,7 @@ func (b *Bwhatsapp) handleDocumentMessage(msg *events.Message) {
|
||||
}
|
||||
|
||||
// Move file to bridge storage
|
||||
helper.HandleDownloadData(b.Log, &rmsg, filename, "document", "", &data, b.General)
|
||||
helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &data, b.General)
|
||||
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
//go:build whatsappmulti
|
||||
// +build whatsappmulti
|
||||
|
||||
package bwhatsapp
|
||||
@@ -6,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.mau.fi/whatsmeow"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/store/sqlstore"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
@@ -17,24 +19,7 @@ type ProfilePicInfo struct {
|
||||
Status int16 `json:"status"`
|
||||
}
|
||||
|
||||
func (b *Bwhatsapp) getSenderName(senderJid types.JID) string {
|
||||
if sender, exists := b.contacts[senderJid]; exists {
|
||||
if sender.FullName != "" {
|
||||
return sender.FullName
|
||||
}
|
||||
// if user is not in phone contacts
|
||||
// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
|
||||
// users can change it in their WhatsApp settings -> profile -> click on Avatar
|
||||
if sender.PushName != "" {
|
||||
return sender.PushName
|
||||
}
|
||||
|
||||
if sender.FirstName != "" {
|
||||
return sender.FirstName
|
||||
}
|
||||
}
|
||||
|
||||
// try to reload this contact
|
||||
func (b *Bwhatsapp) reloadContacts() {
|
||||
if _, err := b.wc.Store.Contacts.GetAllContacts(); err != nil {
|
||||
b.Log.Errorf("error on update of contacts: %v", err)
|
||||
}
|
||||
@@ -47,37 +32,68 @@ func (b *Bwhatsapp) getSenderName(senderJid types.JID) string {
|
||||
if len(allcontacts) > 0 {
|
||||
b.contacts = allcontacts
|
||||
}
|
||||
}
|
||||
|
||||
if sender, exists := b.contacts[senderJid]; exists {
|
||||
if sender.FullName != "" {
|
||||
return sender.FullName
|
||||
}
|
||||
// if user is not in phone contacts
|
||||
// it is the most obvious scenario unless you sync your phone contacts with some remote updated source
|
||||
// users can change it in their WhatsApp settings -> profile -> click on Avatar
|
||||
if sender.PushName != "" {
|
||||
return sender.PushName
|
||||
}
|
||||
func (b *Bwhatsapp) getSenderName(info types.MessageInfo) string {
|
||||
// Parse AD JID
|
||||
var senderJid types.JID
|
||||
senderJid.User, senderJid.Server = info.Sender.User, info.Sender.Server
|
||||
|
||||
if sender.FirstName != "" {
|
||||
return sender.FirstName
|
||||
}
|
||||
sender, exists := b.contacts[senderJid]
|
||||
|
||||
if !exists || (sender.FullName == "" && sender.FirstName == "") {
|
||||
b.reloadContacts() // Contacts may need to be reloaded
|
||||
sender, exists = b.contacts[senderJid]
|
||||
}
|
||||
|
||||
if exists && sender.FullName != "" {
|
||||
return sender.FullName
|
||||
}
|
||||
|
||||
if info.PushName != "" {
|
||||
return info.PushName
|
||||
}
|
||||
|
||||
if exists && sender.FirstName != "" {
|
||||
return sender.FirstName
|
||||
}
|
||||
|
||||
return "Someone"
|
||||
}
|
||||
|
||||
func (b *Bwhatsapp) getSenderNotify(senderJid types.JID) string {
|
||||
if sender, exists := b.contacts[senderJid]; exists {
|
||||
sender, exists := b.contacts[senderJid]
|
||||
|
||||
if !exists || (sender.FullName == "" && sender.PushName == "" && sender.FirstName == "") {
|
||||
b.reloadContacts() // Contacts may need to be reloaded
|
||||
sender, exists = b.contacts[senderJid]
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return "someone"
|
||||
}
|
||||
|
||||
if exists && sender.FullName != "" {
|
||||
return sender.FullName
|
||||
}
|
||||
|
||||
if exists && sender.PushName != "" {
|
||||
return sender.PushName
|
||||
}
|
||||
|
||||
return ""
|
||||
if exists && sender.FirstName != "" {
|
||||
return sender.FirstName
|
||||
}
|
||||
|
||||
return "someone"
|
||||
}
|
||||
|
||||
func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*types.ProfilePictureInfo, error) {
|
||||
pjid, _ := types.ParseJID(jid)
|
||||
info, err := b.wc.GetProfilePictureInfo(pjid, true)
|
||||
|
||||
info, err := b.wc.GetProfilePictureInfo(pjid, &whatsmeow.GetProfilePictureParams{
|
||||
Preview: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get avatar: %v", err)
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
//go:build whatsappmulti
|
||||
// +build whatsappmulti
|
||||
|
||||
package bwhatsapp
|
||||
@@ -212,6 +213,8 @@ func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (st
|
||||
|
||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||
|
||||
caption := msg.Username + fi.Comment
|
||||
|
||||
resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaDocument)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -224,6 +227,7 @@ func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (st
|
||||
Title: &fi.Name,
|
||||
FileName: &fi.Name,
|
||||
Mimetype: &filetype,
|
||||
Caption: &caption,
|
||||
MediaKey: resp.MediaKey,
|
||||
FileEncSha256: resp.FileEncSHA256,
|
||||
FileSha256: resp.FileSHA256,
|
||||
@@ -231,10 +235,10 @@ func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (st
|
||||
Url: &resp.URL,
|
||||
}
|
||||
|
||||
b.Log.Debugf("=> Sending %#v", msg)
|
||||
b.Log.Debugf("=> Sending %#v as a document", msg)
|
||||
|
||||
ID := whatsmeow.GenerateMessageID()
|
||||
_, err = b.wc.SendMessage(groupJID, ID, &message)
|
||||
_, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
|
||||
|
||||
return ID, err
|
||||
}
|
||||
@@ -265,10 +269,80 @@ func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (strin
|
||||
Url: &resp.URL,
|
||||
}
|
||||
|
||||
b.Log.Debugf("=> Sending %#v", msg)
|
||||
b.Log.Debugf("=> Sending %#v as an image", msg)
|
||||
|
||||
ID := whatsmeow.GenerateMessageID()
|
||||
_, err = b.wc.SendMessage(groupJID, ID, &message)
|
||||
_, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
|
||||
|
||||
return ID, err
|
||||
}
|
||||
|
||||
// Post a video message from the bridge to WhatsApp
|
||||
func (b *Bwhatsapp) PostVideoMessage(msg config.Message, filetype string) (string, error) {
|
||||
groupJID, _ := types.ParseJID(msg.Channel)
|
||||
|
||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||
|
||||
caption := msg.Username + fi.Comment
|
||||
|
||||
resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaVideo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var message proto.Message
|
||||
|
||||
message.VideoMessage = &proto.VideoMessage{
|
||||
Mimetype: &filetype,
|
||||
Caption: &caption,
|
||||
MediaKey: resp.MediaKey,
|
||||
FileEncSha256: resp.FileEncSHA256,
|
||||
FileSha256: resp.FileSHA256,
|
||||
FileLength: goproto.Uint64(resp.FileLength),
|
||||
Url: &resp.URL,
|
||||
}
|
||||
|
||||
b.Log.Debugf("=> Sending %#v as a video", msg)
|
||||
|
||||
ID := whatsmeow.GenerateMessageID()
|
||||
_, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
|
||||
|
||||
return ID, err
|
||||
}
|
||||
|
||||
// Post audio inline
|
||||
func (b *Bwhatsapp) PostAudioMessage(msg config.Message, filetype string) (string, error) {
|
||||
groupJID, _ := types.ParseJID(msg.Channel)
|
||||
|
||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||
|
||||
resp, err := b.wc.Upload(context.Background(), *fi.Data, whatsmeow.MediaAudio)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var message proto.Message
|
||||
|
||||
message.AudioMessage = &proto.AudioMessage{
|
||||
Mimetype: &filetype,
|
||||
MediaKey: resp.MediaKey,
|
||||
FileEncSha256: resp.FileEncSHA256,
|
||||
FileSha256: resp.FileSHA256,
|
||||
FileLength: goproto.Uint64(resp.FileLength),
|
||||
Url: &resp.URL,
|
||||
}
|
||||
|
||||
b.Log.Debugf("=> Sending %#v as audio", msg)
|
||||
|
||||
ID := whatsmeow.GenerateMessageID()
|
||||
_, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
|
||||
|
||||
var captionMessage proto.Message
|
||||
caption := msg.Username + fi.Comment + "\u2B06" // the char on the end is upwards arrow emoji
|
||||
captionMessage.Conversation = &caption
|
||||
|
||||
captionID := whatsmeow.GenerateMessageID()
|
||||
_, err = b.wc.SendMessage(context.TODO(), groupJID, &captionMessage, whatsmeow.SendRequestExtra{ID: captionID})
|
||||
|
||||
return ID, err
|
||||
}
|
||||
@@ -315,6 +389,12 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
|
||||
switch filetype {
|
||||
case "image/jpeg", "image/png", "image/gif":
|
||||
return b.PostImageMessage(msg, filetype)
|
||||
case "video/mp4", "video/3gpp": // TODO: Check if codecs are supported by WA
|
||||
return b.PostVideoMessage(msg, filetype)
|
||||
case "audio/ogg":
|
||||
return b.PostAudioMessage(msg, "audio/ogg; codecs=opus") // TODO: Detect if it is actually OPUS
|
||||
case "audio/aac", "audio/mp4", "audio/amr", "audio/mpeg":
|
||||
return b.PostAudioMessage(msg, filetype)
|
||||
default:
|
||||
return b.PostDocumentMessage(msg, filetype)
|
||||
}
|
||||
@@ -327,7 +407,7 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
|
||||
message.Conversation = &text
|
||||
|
||||
ID := whatsmeow.GenerateMessageID()
|
||||
_, err := b.wc.SendMessage(groupJID, ID, &message)
|
||||
_, err := b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
|
||||
|
||||
return ID, err
|
||||
}
|
||||
|
43
changelog.md
43
changelog.md
@@ -1,3 +1,46 @@
|
||||
# v1.26.0
|
||||
|
||||
## New features
|
||||
|
||||
- irc: Allow substitution of bot's nick in RunCommands (irc) (#1890)
|
||||
- matrix: Add Matrix username spoofing (#1875)
|
||||
|
||||
## Enhancements
|
||||
|
||||
- general: Update dependencies (#1951)
|
||||
- mattermost: Remove mattermost 5 support (#1936)
|
||||
- mumble: Implement sending of EventJoinLeave both to and from Mumble (#1915)
|
||||
- whatsappmulti: Improve attachment handling (whatsapp) (#1928)
|
||||
- whatsappmulti: Handle incoming document captions from whatsapp (#1935)
|
||||
|
||||
## Bugfix
|
||||
|
||||
- irc: Fix empty messages on IRC (#1897)
|
||||
- telegram: Fix message html entities escaping when sending to Telegram (#1855)
|
||||
- telegram/slack: Fix error messages in telegram and slack bridges (#1862)
|
||||
- telegram: Fix telegram attachment comment formatting and escaping (#1920)
|
||||
- telegram: Make the cgo lottie a build tag (-tag cgolottie) (#1955)
|
||||
- whatsappmulti: Update dependencies and fix whatsmeow API changes (#1887)
|
||||
- whatsappmulti: Fix the "Someone" nickname problem (whatsapp) (#1931)
|
||||
|
||||
This release couldn't exist without the following contributors:
|
||||
@s3lph, @sas1024, @Glandos, @jx11r, @Lucki, @BuckarooBanzay, @ilmaisin, @Kufat
|
||||
|
||||
# v1.25.2
|
||||
|
||||
## Enhancements
|
||||
|
||||
- general: Update dependencies (#1851,#1841)
|
||||
- mattermost: Support mattermost v7.x (#1852)
|
||||
|
||||
## Bugfix
|
||||
|
||||
- discord: Fix Unwanted join notifications from one Discord server to another (#1612)
|
||||
- discord: Ignore events from other guilds, add nosendjoinpart support (#1846)
|
||||
|
||||
This release couldn't exist without the following contributors:
|
||||
@wlcx
|
||||
|
||||
# v1.25.1
|
||||
|
||||
## Enhancements
|
||||
|
125
go.mod
125
go.mod
@@ -6,60 +6,60 @@ require (
|
||||
github.com/Benau/tgsconverter v0.0.0-20210809170556-99f4a4f6337f
|
||||
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
|
||||
github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c
|
||||
github.com/SevereCloud/vksdk/v2 v2.14.0
|
||||
github.com/bwmarrin/discordgo v0.25.0
|
||||
github.com/d5/tengo/v2 v2.10.1
|
||||
github.com/SevereCloud/vksdk/v2 v2.15.0
|
||||
github.com/bwmarrin/discordgo v0.27.0
|
||||
github.com/d5/tengo/v2 v2.13.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fsnotify/fsnotify v1.5.4
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||
github.com/gomarkdown/markdown v0.0.0-20220509074759-a57bf950ab8c
|
||||
github.com/google/gops v0.3.23
|
||||
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c
|
||||
github.com/google/gops v0.3.26
|
||||
github.com/gorilla/schema v1.2.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/hashicorp/golang-lru v0.6.0
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/keybase/go-keybase-chat-bot v0.0.0-20220322223021-75d497527469
|
||||
github.com/kyokomi/emoji/v2 v2.2.9
|
||||
github.com/labstack/echo/v4 v4.7.2
|
||||
github.com/lrstanley/girc v0.0.0-20220507183218-96757fe3d2a2
|
||||
github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20
|
||||
github.com/kyokomi/emoji/v2 v2.2.11
|
||||
github.com/labstack/echo/v4 v4.10.0
|
||||
github.com/lrstanley/girc v0.0.0-20221222153823-a92667a5c9b4
|
||||
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696
|
||||
github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be
|
||||
github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27
|
||||
github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75
|
||||
github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
|
||||
github.com/matterbridge/matterclient v0.0.0-20220430213656-07aca2731bc9
|
||||
github.com/matterbridge/matterclient v0.0.0-20220624224459-272af20c7ddf
|
||||
github.com/mattermost/mattermost-server/v5 v5.39.3
|
||||
github.com/mattermost/mattermost-server/v6 v6.6.1
|
||||
github.com/mattermost/mattermost-server/v6 v6.7.2
|
||||
github.com/mattn/godown v0.0.1
|
||||
github.com/mdp/qrterminal v1.0.1
|
||||
github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
|
||||
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
|
||||
github.com/rs/xid v1.4.0
|
||||
github.com/russross/blackfriday v1.6.0
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
github.com/shazow/ssh-chat v1.10.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/slack-go/slack v0.10.3
|
||||
github.com/spf13/viper v1.11.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/slack-go/slack v0.12.1
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/vincent-petithory/dataurl v1.0.0
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
github.com/yaegashi/msgraph.go v0.1.4
|
||||
github.com/zfjagann/golang-ring v0.0.0-20220330170733-19bcea1b6289
|
||||
go.mau.fi/whatsmeow v0.0.0-20220504135614-f1f2a9d231fb
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
||||
golang.org/x/text v0.3.7
|
||||
go.mau.fi/whatsmeow v0.0.0-20230128195103-dcbc8dd31a22
|
||||
golang.org/x/image v0.3.0
|
||||
golang.org/x/oauth2 v0.4.0
|
||||
golang.org/x/text v0.6.0
|
||||
gomod.garykim.dev/nc-talk v0.3.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
|
||||
layeh.com/gumble v0.0.0-20200818122324-146f9205029b
|
||||
modernc.org/sqlite v1.17.2
|
||||
layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14
|
||||
modernc.org/sqlite v1.20.3
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/Benau/go_rlottie v0.0.0-20210807002906-98c1b2421989 // indirect
|
||||
github.com/Jeffail/gabs v1.4.0 // indirect
|
||||
github.com/apex/log v1.9.0 // indirect
|
||||
@@ -80,23 +80,23 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kettek/apng v0.0.0-20191108220231-414630eed80f // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.11 // indirect
|
||||
github.com/labstack/gommon v0.3.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/klauspost/compress v1.15.8 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
|
||||
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect
|
||||
github.com/mattermost/logr v1.0.13 // indirect
|
||||
github.com/mattermost/logr/v2 v2.0.15 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.23 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.24 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monaco-io/request v1.0.5 // indirect
|
||||
@@ -104,8 +104,8 @@ require (
|
||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pborman/uuid v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/philhofer/fwd v1.1.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -116,48 +116,47 @@ require (
|
||||
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect
|
||||
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 // indirect
|
||||
github.com/spf13/afero v1.8.2 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tinylib/msgp v1.1.6 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/wiggin77/cfg v1.0.2 // indirect
|
||||
github.com/wiggin77/merror v1.0.3 // indirect
|
||||
github.com/wiggin77/srslog v1.0.1 // indirect
|
||||
go.mau.fi/libsignal v0.0.0-20220425070825-c40c839ee6a0 // indirect
|
||||
go.mau.fi/libsignal v0.1.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.17.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.5.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/term v0.4.0 // indirect
|
||||
golang.org/x/time v0.2.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
modernc.org/libc v1.16.7 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.2 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
)
|
||||
|
||||
//replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
@@ -122,10 +122,11 @@ RejoinDelay=0
|
||||
#Only works in IRC right now.
|
||||
ColorNicks=false
|
||||
|
||||
#RunCommands allows you to send RAW irc commands after connection
|
||||
#RunCommands allows you to send RAW irc commands after connection.
|
||||
#The string {BOTNICK} (case sensitive) will be replaced with the bot's current nickname.
|
||||
#Array of strings
|
||||
#OPTIONAL (default empty)
|
||||
RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something"]
|
||||
RunCommands=["PRIVMSG user hello","PRIVMSG chanserv something", "MODE {BOTNICK} +B"]
|
||||
|
||||
#PingDelay specifies how long to wait to send a ping to the irc server.
|
||||
#You can use s for second, m for minute
|
||||
@@ -187,7 +188,7 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
@@ -197,7 +198,7 @@ ShowJoinPart=false
|
||||
VerboseJoinPart=false
|
||||
|
||||
#Do not send joins/parts to other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
NoSendJoinPart=false
|
||||
|
||||
@@ -324,7 +325,7 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
@@ -491,12 +492,12 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#Do not send joins/parts to other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
NoSendJoinPart=false
|
||||
|
||||
@@ -578,7 +579,7 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
@@ -686,7 +687,7 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
@@ -825,12 +826,12 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#Do not send joins/parts to other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
NoSendJoinPart=false
|
||||
|
||||
@@ -1137,7 +1138,7 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
@@ -1274,7 +1275,7 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
@@ -1386,10 +1387,15 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#Rename the bot in the current room to the username of the message
|
||||
#This will make an additional API request per message and will probably count towards rate limits
|
||||
#OPTIONAL (default false)
|
||||
SpoofUsername=false
|
||||
|
||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
|
||||
#It will strip other characters from the nick
|
||||
#OPTIONAL (default false)
|
||||
@@ -1478,7 +1484,7 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
@@ -1566,6 +1572,15 @@ SkipTLSVerify=false
|
||||
#Default "<clipped message>"
|
||||
MessageClipped="<clipped message>"
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#Do not send joins/parts to other bridges
|
||||
#OPTIONAL (default false)
|
||||
NoSendJoinPart=false
|
||||
|
||||
###################################################################
|
||||
#VK
|
||||
###################################################################
|
||||
@@ -1678,7 +1693,7 @@ Label=""
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack, discord
|
||||
#Currently works for messages from the following bridges: irc, mattermost, mumble, slack, discord
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#WARNING: as this file contains credentials, be sure to set correct file permissions
|
||||
[irc]
|
||||
[irc.freenode]
|
||||
Server="irc.freenode.net:6667"
|
||||
[irc.libera]
|
||||
Server="irc.libera.chat:6667"
|
||||
Nick="matterbot"
|
||||
|
||||
[mattermost]
|
||||
@@ -17,7 +17,7 @@
|
||||
name="gateway1"
|
||||
enable=true
|
||||
[[gateway.inout]]
|
||||
account="irc.freenode"
|
||||
account="irc.libera"
|
||||
channel="#testing"
|
||||
|
||||
[[gateway.inout]]
|
||||
@@ -29,6 +29,6 @@ enable=true
|
||||
#name="gateway2"
|
||||
#enable=true
|
||||
#inout = [
|
||||
# { account="irc.freenode", channel="#testing", options={key="channelkey"}},
|
||||
# { account="irc.libera", channel="#testing", options={key="channelkey"}},
|
||||
# { account="mattermost.work", channel="off-topic" },
|
||||
#]
|
||||
|
2
vendor/filippo.io/edwards25519/README.md
generated
vendored
2
vendor/filippo.io/edwards25519/README.md
generated
vendored
@@ -7,7 +7,7 @@ import "filippo.io/edwards25519"
|
||||
This library implements the edwards25519 elliptic curve, exposing the necessary APIs to build a wide array of higher-level primitives.
|
||||
Read the docs at [pkg.go.dev/filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519).
|
||||
|
||||
The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255.
|
||||
The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255, and was finally [merged back into the Go standard library](https://golang.org/cl/276272) as of Go 1.17. It now tracks the upstream codebase and extends it with additional functionality.
|
||||
|
||||
Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `golang.org/x/crypto/curve25519` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of `crypto/ed25519/internal/edwards25519` or `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative.
|
||||
|
||||
|
31
vendor/filippo.io/edwards25519/field/fe.go
generated
vendored
31
vendor/filippo.io/edwards25519/field/fe.go
generated
vendored
@@ -188,12 +188,13 @@ func (v *Element) Set(a *Element) *Element {
|
||||
}
|
||||
|
||||
// SetBytes sets v to x, where x is a 32-byte little-endian encoding. If x is
|
||||
// not of the right length, SetUniformBytes returns nil and an error, and the
|
||||
// not of the right length, SetBytes returns nil and an error, and the
|
||||
// receiver is unchanged.
|
||||
//
|
||||
// Consistent with RFC 7748, the most significant bit (the high bit of the
|
||||
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
|
||||
// are accepted. Note that this is laxer than specified by RFC 8032.
|
||||
// are accepted. Note that this is laxer than specified by RFC 8032, but
|
||||
// consistent with most Ed25519 implementations.
|
||||
func (v *Element) SetBytes(x []byte) (*Element, error) {
|
||||
if len(x) != 32 {
|
||||
return nil, errors.New("edwards25519: invalid field element input size")
|
||||
@@ -211,7 +212,7 @@ func (v *Element) SetBytes(x []byte) (*Element, error) {
|
||||
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
|
||||
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
|
||||
v.l3 &= maskLow51Bits
|
||||
// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51).
|
||||
// Bits 204:255 (bytes 24:32, bits 192:256, shift 12, mask 51).
|
||||
// Note: not bytes 25:33, shift 4, to avoid overread.
|
||||
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
|
||||
v.l4 &= maskLow51Bits
|
||||
@@ -394,26 +395,26 @@ var sqrtM1 = &Element{1718705420411056, 234908883556509,
|
||||
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
|
||||
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
|
||||
// and returns r and 0.
|
||||
func (r *Element) SqrtRatio(u, v *Element) (rr *Element, wasSquare int) {
|
||||
var a, b Element
|
||||
func (r *Element) SqrtRatio(u, v *Element) (R *Element, wasSquare int) {
|
||||
t0 := new(Element)
|
||||
|
||||
// r = (u * v3) * (u * v7)^((p-5)/8)
|
||||
v2 := a.Square(v)
|
||||
uv3 := b.Multiply(u, b.Multiply(v2, v))
|
||||
uv7 := a.Multiply(uv3, a.Square(v2))
|
||||
r.Multiply(uv3, r.Pow22523(uv7))
|
||||
v2 := new(Element).Square(v)
|
||||
uv3 := new(Element).Multiply(u, t0.Multiply(v2, v))
|
||||
uv7 := new(Element).Multiply(uv3, t0.Square(v2))
|
||||
rr := new(Element).Multiply(uv3, t0.Pow22523(uv7))
|
||||
|
||||
check := a.Multiply(v, a.Square(r)) // check = v * r^2
|
||||
check := new(Element).Multiply(v, t0.Square(rr)) // check = v * r^2
|
||||
|
||||
uNeg := b.Negate(u)
|
||||
uNeg := new(Element).Negate(u)
|
||||
correctSignSqrt := check.Equal(u)
|
||||
flippedSignSqrt := check.Equal(uNeg)
|
||||
flippedSignSqrtI := check.Equal(uNeg.Multiply(uNeg, sqrtM1))
|
||||
flippedSignSqrtI := check.Equal(t0.Multiply(uNeg, sqrtM1))
|
||||
|
||||
rPrime := b.Multiply(r, sqrtM1) // r_prime = SQRT_M1 * r
|
||||
rPrime := new(Element).Multiply(rr, sqrtM1) // r_prime = SQRT_M1 * r
|
||||
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
|
||||
r.Select(rPrime, r, flippedSignSqrt|flippedSignSqrtI)
|
||||
rr.Select(rPrime, rr, flippedSignSqrt|flippedSignSqrtI)
|
||||
|
||||
r.Absolute(r) // Choose the nonnegative square root.
|
||||
r.Absolute(rr) // Choose the nonnegative square root.
|
||||
return r, correctSignSqrt | flippedSignSqrt
|
||||
}
|
||||
|
50
vendor/filippo.io/edwards25519/field/fe_extra.go
generated
vendored
Normal file
50
vendor/filippo.io/edwards25519/field/fe_extra.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package field
|
||||
|
||||
import "errors"
|
||||
|
||||
// This file contains additional functionality that is not included in the
|
||||
// upstream crypto/ed25519/internal/edwards25519/field package.
|
||||
|
||||
// SetWideBytes sets v to x, where x is a 64-byte little-endian encoding, which
|
||||
// is reduced modulo the field order. If x is not of the right length,
|
||||
// SetWideBytes returns nil and an error, and the receiver is unchanged.
|
||||
//
|
||||
// SetWideBytes is not necessary to select a uniformly distributed value, and is
|
||||
// only provided for compatibility: SetBytes can be used instead as the chance
|
||||
// of bias is less than 2⁻²⁵⁰.
|
||||
func (v *Element) SetWideBytes(x []byte) (*Element, error) {
|
||||
if len(x) != 64 {
|
||||
return nil, errors.New("edwards25519: invalid SetWideBytes input size")
|
||||
}
|
||||
|
||||
// Split the 64 bytes into two elements, and extract the most significant
|
||||
// bit of each, which is ignored by SetBytes.
|
||||
lo, _ := new(Element).SetBytes(x[:32])
|
||||
loMSB := uint64(x[31] >> 7)
|
||||
hi, _ := new(Element).SetBytes(x[32:])
|
||||
hiMSB := uint64(x[63] >> 7)
|
||||
|
||||
// The output we want is
|
||||
//
|
||||
// v = lo + loMSB * 2²⁵⁵ + hi * 2²⁵⁶ + hiMSB * 2⁵¹¹
|
||||
//
|
||||
// which applying the reduction identity comes out to
|
||||
//
|
||||
// v = lo + loMSB * 19 + hi * 2 * 19 + hiMSB * 2 * 19²
|
||||
//
|
||||
// l0 will be the sum of a 52 bits value (lo.l0), plus a 5 bits value
|
||||
// (loMSB * 19), a 6 bits value (hi.l0 * 2 * 19), and a 10 bits value
|
||||
// (hiMSB * 2 * 19²), so it fits in a uint64.
|
||||
|
||||
v.l0 = lo.l0 + loMSB*19 + hi.l0*2*19 + hiMSB*2*19*19
|
||||
v.l1 = lo.l1 + hi.l1*2*19
|
||||
v.l2 = lo.l2 + hi.l2*2*19
|
||||
v.l3 = lo.l3 + hi.l3*2*19
|
||||
v.l4 = lo.l4 + hi.l4*2*19
|
||||
|
||||
return v.carryPropagate(), nil
|
||||
}
|
2
vendor/filippo.io/edwards25519/field/fe_generic.go
generated
vendored
2
vendor/filippo.io/edwards25519/field/fe_generic.go
generated
vendored
@@ -254,6 +254,8 @@ func (v *Element) carryPropagateGeneric() *Element {
|
||||
c3 := v.l3 >> 51
|
||||
c4 := v.l4 >> 51
|
||||
|
||||
// c4 is at most 64 - 51 = 13 bits, so c4*19 is at most 18 bits, and
|
||||
// the final l0 will be at most 52 bits. Similarly for the rest.
|
||||
v.l0 = v.l0&maskLow51Bits + c4*19
|
||||
v.l1 = v.l1&maskLow51Bits + c0
|
||||
v.l2 = v.l2&maskLow51Bits + c1
|
||||
|
11
vendor/filippo.io/edwards25519/scalar.go
generated
vendored
11
vendor/filippo.io/edwards25519/scalar.go
generated
vendored
@@ -22,7 +22,7 @@ import (
|
||||
// The zero value is a valid zero element.
|
||||
type Scalar struct {
|
||||
// s is the Scalar value in little-endian. The value is always reduced
|
||||
// between operations.
|
||||
// modulo l between operations.
|
||||
s [32]byte
|
||||
}
|
||||
|
||||
@@ -79,9 +79,12 @@ func (s *Scalar) Set(x *Scalar) *Scalar {
|
||||
return s
|
||||
}
|
||||
|
||||
// SetUniformBytes sets s to an uniformly distributed value given 64 uniformly
|
||||
// distributed random bytes. If x is not of the right length, SetUniformBytes
|
||||
// returns nil and an error, and the receiver is unchanged.
|
||||
// SetUniformBytes sets s = x mod l, where x is a 64-byte little-endian integer.
|
||||
// If x is not of the right length, SetUniformBytes returns nil and an error,
|
||||
// and the receiver is unchanged.
|
||||
//
|
||||
// SetUniformBytes can be used to set s to an uniformly distributed value given
|
||||
// 64 uniformly distributed random bytes.
|
||||
func (s *Scalar) SetUniformBytes(x []byte) (*Scalar, error) {
|
||||
if len(x) != 64 {
|
||||
return nil, errors.New("edwards25519: invalid SetUniformBytes input length")
|
||||
|
3
vendor/github.com/SevereCloud/vksdk/v2/.golangci.yml
generated
vendored
3
vendor/github.com/SevereCloud/vksdk/v2/.golangci.yml
generated
vendored
@@ -57,6 +57,8 @@ linters:
|
||||
- grouper
|
||||
- decorder
|
||||
- containedctx
|
||||
# - execinquery # FIXME: panic in 1.46.0
|
||||
- nosprintfhostport
|
||||
|
||||
# - wrapcheck # TODO: v3 Fix
|
||||
# - testpackage # TODO: Fix testpackage
|
||||
@@ -87,6 +89,7 @@ linters:
|
||||
# - varnamelen
|
||||
# - errchkjson
|
||||
# - maintidx
|
||||
# - nonamedreturns
|
||||
|
||||
# depricated
|
||||
# - maligned
|
||||
|
12
vendor/github.com/SevereCloud/vksdk/v2/api/api.go
generated
vendored
12
vendor/github.com/SevereCloud/vksdk/v2/api/api.go
generated
vendored
@@ -203,10 +203,13 @@ func buildQuery(sliceParams ...Params) (context.Context, url.Values) {
|
||||
|
||||
for _, params := range sliceParams {
|
||||
for key, value := range params {
|
||||
if key != ":context" {
|
||||
query.Set(key, FmtValue(value, 0))
|
||||
} else {
|
||||
switch key {
|
||||
case "access_token":
|
||||
continue
|
||||
case ":context":
|
||||
ctx = value.(context.Context)
|
||||
default:
|
||||
query.Set(key, FmtValue(value, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,6 +258,9 @@ func (vk *VK) DefaultHandler(method string, sliceParams ...Params) (Response, er
|
||||
acceptEncoding = "zstd"
|
||||
}
|
||||
|
||||
token := sliceParams[len(sliceParams)-1]["access_token"].(string)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
req.Header.Set("User-Agent", vk.UserAgent)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
|
48
vendor/github.com/SevereCloud/vksdk/v2/api/apps.go
generated
vendored
48
vendor/github.com/SevereCloud/vksdk/v2/api/apps.go
generated
vendored
@@ -4,6 +4,14 @@ import (
|
||||
"github.com/SevereCloud/vksdk/v2/object"
|
||||
)
|
||||
|
||||
// AppsAddUsersToTestingGroup method.
|
||||
//
|
||||
// https://vk.com/dev/apps.addUsersToTestingGroup
|
||||
func (vk *VK) AppsAddUsersToTestingGroup(params Params) (response int, err error) {
|
||||
err = vk.RequestUnmarshal("apps.addUsersToTestingGroup", &response, params)
|
||||
return
|
||||
}
|
||||
|
||||
// AppsDeleteAppRequests deletes all request notifications from the current app.
|
||||
//
|
||||
// https://vk.com/dev/apps.deleteAppRequests
|
||||
@@ -140,6 +148,33 @@ func (vk *VK) AppsGetScore(params Params) (response string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// AppsGetTestingGroupsResponse struct.
|
||||
type AppsGetTestingGroupsResponse []object.AppsTestingGroup
|
||||
|
||||
// AppsGetTestingGroups method.
|
||||
//
|
||||
// https://vk.com/dev/apps.getTestingGroups
|
||||
func (vk *VK) AppsGetTestingGroups(params Params) (response AppsGetTestingGroupsResponse, err error) {
|
||||
err = vk.RequestUnmarshal("apps.getTestingGroups", &response, params)
|
||||
return
|
||||
}
|
||||
|
||||
// AppsRemoveTestingGroup method.
|
||||
//
|
||||
// https://vk.com/dev/apps.removeTestingGroup
|
||||
func (vk *VK) AppsRemoveTestingGroup(params Params) (response int, err error) {
|
||||
err = vk.RequestUnmarshal("apps.removeTestingGroup", &response, params)
|
||||
return
|
||||
}
|
||||
|
||||
// AppsRemoveUsersFromTestingGroups method.
|
||||
//
|
||||
// https://vk.com/dev/apps.removeUsersFromTestingGroups
|
||||
func (vk *VK) AppsRemoveUsersFromTestingGroups(params Params) (response int, err error) {
|
||||
err = vk.RequestUnmarshal("apps.removeUsersFromTestingGroups", &response, params)
|
||||
return
|
||||
}
|
||||
|
||||
// AppsSendRequest sends a request to another user in an app that uses VK authorization.
|
||||
//
|
||||
// https://vk.com/dev/apps.sendRequest
|
||||
@@ -147,3 +182,16 @@ func (vk *VK) AppsSendRequest(params Params) (response int, err error) {
|
||||
err = vk.RequestUnmarshal("apps.sendRequest", &response, params)
|
||||
return
|
||||
}
|
||||
|
||||
// AppsUpdateMetaForTestingGroupResponse struct.
|
||||
type AppsUpdateMetaForTestingGroupResponse struct {
|
||||
GroupID int `json:"group_id"`
|
||||
}
|
||||
|
||||
// AppsUpdateMetaForTestingGroup method.
|
||||
//
|
||||
// https://vk.com/dev/apps.updateMetaForTestingGroup
|
||||
func (vk *VK) AppsUpdateMetaForTestingGroup(params Params) (response AppsUpdateMetaForTestingGroupResponse, err error) {
|
||||
err = vk.RequestUnmarshal("apps.updateMetaForTestingGroup", &response, params)
|
||||
return
|
||||
}
|
||||
|
6
vendor/github.com/SevereCloud/vksdk/v2/api/errors.go
generated
vendored
6
vendor/github.com/SevereCloud/vksdk/v2/api/errors.go
generated
vendored
@@ -622,6 +622,12 @@ const (
|
||||
// Anonymous token is invalid.
|
||||
ErrAnonymousTokenInvalid ErrorType = 1116
|
||||
|
||||
// Access token has expired.
|
||||
ErrAuthAccessTokenHasExpired ErrorType = 1117
|
||||
|
||||
// Anonymous token ip mismatch.
|
||||
ErrAuthAnonymousTokenIPMismatch ErrorType = 1118
|
||||
|
||||
// Invalid document id.
|
||||
ErrParamDocID ErrorType = 1150
|
||||
|
||||
|
22
vendor/github.com/SevereCloud/vksdk/v2/api/messages.go
generated
vendored
22
vendor/github.com/SevereCloud/vksdk/v2/api/messages.go
generated
vendored
@@ -116,6 +116,14 @@ func (vk *VK) MessagesEditChat(params Params) (response int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// MessagesForceCallFinish method.
|
||||
//
|
||||
// https://vk.com/dev/messages.forceCallFinish
|
||||
func (vk *VK) MessagesForceCallFinish(params Params) (response int, err error) {
|
||||
err = vk.RequestUnmarshal("messages.forceCallFinish", &response, params)
|
||||
return
|
||||
}
|
||||
|
||||
// MessagesGetByConversationMessageIDResponse struct.
|
||||
type MessagesGetByConversationMessageIDResponse struct {
|
||||
Count int `json:"count"`
|
||||
@@ -633,6 +641,20 @@ func (vk *VK) MessagesSetChatPhoto(params Params) (response MessagesSetChatPhoto
|
||||
return
|
||||
}
|
||||
|
||||
// MessagesStartCallResponse struct.
|
||||
type MessagesStartCallResponse struct {
|
||||
JoinLink string `json:"join_link"`
|
||||
CallID string `json:"call_id"`
|
||||
}
|
||||
|
||||
// MessagesStartCall method.
|
||||
//
|
||||
// https://vk.com/dev/messages.startCall
|
||||
func (vk *VK) MessagesStartCall(params Params) (response MessagesStartCallResponse, err error) {
|
||||
err = vk.RequestUnmarshal("messages.startCall", &response, params)
|
||||
return
|
||||
}
|
||||
|
||||
// MessagesUnpin messages.unpin.
|
||||
//
|
||||
// https://vk.com/dev/messages.unpin
|
||||
|
2
vendor/github.com/SevereCloud/vksdk/v2/doc.go
generated
vendored
2
vendor/github.com/SevereCloud/vksdk/v2/doc.go
generated
vendored
@@ -7,6 +7,6 @@ package vksdk
|
||||
|
||||
// Module constants.
|
||||
const (
|
||||
Version = "2.14.0"
|
||||
Version = "2.15.0"
|
||||
API = "5.131"
|
||||
)
|
||||
|
9
vendor/github.com/SevereCloud/vksdk/v2/object/apps.go
generated
vendored
9
vendor/github.com/SevereCloud/vksdk/v2/object/apps.go
generated
vendored
@@ -100,3 +100,12 @@ type AppsScope struct {
|
||||
Name string `json:"name"` // Scope name
|
||||
Title string `json:"title"` // Scope title
|
||||
}
|
||||
|
||||
// AppsTestingGroup testing group description.
|
||||
type AppsTestingGroup struct {
|
||||
GroupID int `json:"group_id"`
|
||||
UserIDs []int `json:"user_ids"`
|
||||
Name string `json:"name"`
|
||||
Webview string `json:"webview"`
|
||||
Platforms []string `json:"platforms"`
|
||||
}
|
||||
|
10
vendor/github.com/SevereCloud/vksdk/v2/object/groups.go
generated
vendored
10
vendor/github.com/SevereCloud/vksdk/v2/object/groups.go
generated
vendored
@@ -210,7 +210,7 @@ type GroupsGroup struct {
|
||||
MainSection int `json:"main_section,omitempty"`
|
||||
OnlineStatus GroupsOnlineStatus `json:"online_status,omitempty"` // Status of replies in community messages
|
||||
AgeLimits int `json:"age_limits,omitempty"` // Information whether age limit
|
||||
BanInfo GroupsGroupBanInfo `json:"ban_info,omitempty"` // User ban info
|
||||
BanInfo *GroupsGroupBanInfo `json:"ban_info,omitempty"` // User ban info
|
||||
Addresses GroupsAddressesInfo `json:"addresses,omitempty"` // Info about addresses in Groups
|
||||
LiveCovers GroupsLiveCovers `json:"live_covers,omitempty"`
|
||||
CropPhoto UsersCropPhoto `json:"crop_photo,omitempty"`
|
||||
@@ -963,10 +963,10 @@ type GroupsOnlineStatus struct {
|
||||
|
||||
// GroupsOwnerXtrBanInfo struct.
|
||||
type GroupsOwnerXtrBanInfo struct {
|
||||
BanInfo GroupsBanInfo `json:"ban_info"`
|
||||
Group GroupsGroup `json:"group"`
|
||||
Profile UsersUser `json:"profile"`
|
||||
Type string `json:"type"`
|
||||
BanInfo *GroupsBanInfo `json:"ban_info"`
|
||||
Group GroupsGroup `json:"group"`
|
||||
Profile UsersUser `json:"profile"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// GroupsSubjectItem struct.
|
||||
|
2
vendor/github.com/SevereCloud/vksdk/v2/object/video.go
generated
vendored
2
vendor/github.com/SevereCloud/vksdk/v2/object/video.go
generated
vendored
@@ -31,7 +31,7 @@ type VideoVideo struct {
|
||||
CanLike BaseBoolInt `json:"can_like"`
|
||||
|
||||
// Information whether current user can download the video.
|
||||
CanDownload BaseBoolInt `json:"can_download"`
|
||||
CanDownload int `json:"can_download"`
|
||||
|
||||
// Information whether current user can repost this video.
|
||||
CanRepost BaseBoolInt `json:"can_repost"`
|
||||
|
87
vendor/github.com/bwmarrin/discordgo/CONTRIBUTING.md
generated
vendored
Normal file
87
vendor/github.com/bwmarrin/discordgo/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
# Getting started
|
||||
|
||||
To start off you can check out existing Pull Requests and Issues to get a gasp of what problems we’re currently solving and what features you can implement.
|
||||
|
||||
## Issues
|
||||
|
||||
Our issues are mostly used for bugs, however we welcome refactoring and conceptual issues.
|
||||
|
||||
Any other conversation would belong and would be moved into “Discussions”.
|
||||
|
||||
## Discussions
|
||||
|
||||
We use discussions for ideas, polls, announcements and help questions.
|
||||
|
||||
Don’t hesitate to ask, we always would try to help.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
If you want to help us by improving existing or adding new features, you create what’s called a Pull Request (aka PR). It allows us to review your code, suggest changes and merge it.
|
||||
|
||||
Here are some tips on how to make a good first PR:
|
||||
|
||||
- When creating a PR, please consider a distinctive name and description for it, so the maintainers can understand what your PR changes / adds / removes.
|
||||
- It’s always a good idea to link documentation when implementing a new feature / endpoint
|
||||
- If you’re resolving an issue, don’t forget to [link it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) in the description.
|
||||
- Enable the checkbox to allow maintainers to edit your PR and make commits in the PR branch when necessary.
|
||||
- We may ask for changes, usually through suggestions or pull request comments. You can apply suggestions right in the UI. Any other change needs to be done manually.
|
||||
- Don’t forget to mark PR comments resolved when you’re done applying the changes.
|
||||
- Be patient and don’t close and reopen your PR when no one responds, sometimes it might be held for a while. There might be a lot of reasons: release preparation, the feature is not significant, maintainers are busy, etc.
|
||||
|
||||
|
||||
When your changes are still incomplete (i.e. in Work In Progress state), you can still create a PR, but consider making it a draft.
|
||||
To make a draft PR, you can change the type of PR by clicking to a triangle next to the “Create Pull Request” button.
|
||||
|
||||
Once you’re done, you can mark it as “Ready for review”, and we’ll get right on it.
|
||||
|
||||
|
||||
# Code style
|
||||
|
||||
To standardize and make things less messy we have a certain code style, that is persistent throughout the codebase.
|
||||
|
||||
## Naming
|
||||
|
||||
### REST methods
|
||||
|
||||
When naming a REST method, while it might seem counterintuitive, we specify the entity before the action verb (for GET endpoints we don’t specify one however). Here’s an example:
|
||||
|
||||
> Endpoint name: Get Channel Message
|
||||
>
|
||||
> Method name: `ChannelMessage`
|
||||
|
||||
> Endpoint name: Edit Channel Message
|
||||
>
|
||||
> Method name: `ChannelMessageEdit`
|
||||
|
||||
### Parameter structures
|
||||
|
||||
When making a complex REST endpoint, sometimes you might need to implement a `Param` structure. This structure contains parameters for certain endpoint/set of endpoints.
|
||||
|
||||
- If an endpoint/set of endpoints have mostly same parameters, it’s a good idea to use a single `Param` structure for them. Here’s an example:
|
||||
|
||||
> Endpoint: `GuildMemberEdit`
|
||||
>
|
||||
> `Param` structure: `GuildMemberParams`
|
||||
- If an endpoint/set of endpoints have differentiating parameters, `Param` structure can be named after the endpoint’s verb. Here’s an example:
|
||||
|
||||
> Endpoint: `ChannelMessageSendComplex`
|
||||
>
|
||||
> `Param` structure: `MessageSend`
|
||||
|
||||
> Endpoint: `ChannelMessageEditComplex`
|
||||
>
|
||||
> `Param` structure: `MessageEdit`
|
||||
|
||||
### Events
|
||||
|
||||
When naming an event, we follow gateway’s internal naming (which often matches with the official event name in the docs). Here’s an example:
|
||||
|
||||
> Event name: Interaction Create (`INTERACTION_CREATE`)
|
||||
>
|
||||
> Structure name: `InteractionCreate`
|
||||
|
||||
## Returns
|
||||
|
||||
In our REST functions we usually favor named returns instead of regular anonymous returns. This helps readability.
|
||||
|
||||
Additionally we try to avoid naked return statements for functions with a long body. Since it’s easier to loose track of the return result.
|
8
vendor/github.com/bwmarrin/discordgo/README.md
generated
vendored
8
vendor/github.com/bwmarrin/discordgo/README.md
generated
vendored
@@ -1,6 +1,6 @@
|
||||
# DiscordGo
|
||||
|
||||
[](https://pkg.go.dev/github.com/bwmarrin/discordgo) [](https://goreportcard.com/report/github.com/bwmarrin/discordgo) [](https://travis-ci.com/bwmarrin/discordgo) [](https://discord.gg/golang) [](https://discord.com/invite/discord-api)
|
||||
[](https://pkg.go.dev/github.com/bwmarrin/discordgo) [](https://goreportcard.com/report/github.com/bwmarrin/discordgo) [](https://github.com/bwmarrin/discordgo/actions/workflows/ci.yml) [](https://discord.gg/golang) [](https://discord.com/invite/discord-api)
|
||||
|
||||
<img align="right" alt="DiscordGo logo" src="docs/img/discordgo.svg" width="400">
|
||||
|
||||
@@ -61,11 +61,9 @@ See Documentation and Examples below for more detailed information.
|
||||
Because of that there may be major changes to library in the future.
|
||||
|
||||
The DiscordGo code is fairly well documented at this point and is currently
|
||||
the only documentation available. Both GoDoc and GoWalker (below) present
|
||||
that information in a nice format.
|
||||
the only documentation available. Go reference (below) presents that information in a nice format.
|
||||
|
||||
- [](https://pkg.go.dev/github.com/bwmarrin/discordgo)
|
||||
- [](https://gowalker.org/github.com/bwmarrin/discordgo)
|
||||
- [](https://pkg.go.dev/github.com/bwmarrin/discordgo)
|
||||
- Hand crafted documentation coming eventually.
|
||||
|
||||
|
||||
|
46
vendor/github.com/bwmarrin/discordgo/components.go
generated
vendored
46
vendor/github.com/bwmarrin/discordgo/components.go
generated
vendored
@@ -10,10 +10,14 @@ type ComponentType uint
|
||||
|
||||
// MessageComponent types.
|
||||
const (
|
||||
ActionsRowComponent ComponentType = 1
|
||||
ButtonComponent ComponentType = 2
|
||||
SelectMenuComponent ComponentType = 3
|
||||
TextInputComponent ComponentType = 4
|
||||
ActionsRowComponent ComponentType = 1
|
||||
ButtonComponent ComponentType = 2
|
||||
SelectMenuComponent ComponentType = 3
|
||||
TextInputComponent ComponentType = 4
|
||||
UserSelectMenuComponent ComponentType = 5
|
||||
RoleSelectMenuComponent ComponentType = 6
|
||||
MentionableSelectMenuComponent ComponentType = 7
|
||||
ChannelSelectMenuComponent ComponentType = 8
|
||||
)
|
||||
|
||||
// MessageComponent is a base interface for all message components.
|
||||
@@ -41,7 +45,8 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error {
|
||||
umc.MessageComponent = &ActionsRow{}
|
||||
case ButtonComponent:
|
||||
umc.MessageComponent = &Button{}
|
||||
case SelectMenuComponent:
|
||||
case SelectMenuComponent, ChannelSelectMenuComponent, UserSelectMenuComponent,
|
||||
RoleSelectMenuComponent, MentionableSelectMenuComponent:
|
||||
umc.MessageComponent = &SelectMenu{}
|
||||
case TextInputComponent:
|
||||
umc.MessageComponent = &TextInput{}
|
||||
@@ -169,8 +174,23 @@ type SelectMenuOption struct {
|
||||
Default bool `json:"default"`
|
||||
}
|
||||
|
||||
// SelectMenuType represents select menu type.
|
||||
type SelectMenuType ComponentType
|
||||
|
||||
// SelectMenu types.
|
||||
const (
|
||||
StringSelectMenu = SelectMenuType(SelectMenuComponent)
|
||||
UserSelectMenu = SelectMenuType(UserSelectMenuComponent)
|
||||
RoleSelectMenu = SelectMenuType(RoleSelectMenuComponent)
|
||||
MentionableSelectMenu = SelectMenuType(MentionableSelectMenuComponent)
|
||||
ChannelSelectMenu = SelectMenuType(ChannelSelectMenuComponent)
|
||||
)
|
||||
|
||||
// SelectMenu represents select menu component.
|
||||
type SelectMenu struct {
|
||||
// Type of the select menu.
|
||||
MenuType SelectMenuType `json:"type,omitempty"`
|
||||
// CustomID is a developer-defined identifier for the select menu.
|
||||
CustomID string `json:"custom_id,omitempty"`
|
||||
// The text which will be shown in the menu if there's no default options or all options was deselected and component was closed.
|
||||
Placeholder string `json:"placeholder"`
|
||||
@@ -179,25 +199,31 @@ type SelectMenu struct {
|
||||
// This value determines the maximal amount of selected items in the menu.
|
||||
// If MaxValues or MinValues are greater than one then the user can select multiple items in the component.
|
||||
MaxValues int `json:"max_values,omitempty"`
|
||||
Options []SelectMenuOption `json:"options"`
|
||||
Options []SelectMenuOption `json:"options,omitempty"`
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// NOTE: Can only be used in SelectMenu with Channel menu type.
|
||||
ChannelTypes []ChannelType `json:"channel_types,omitempty"`
|
||||
}
|
||||
|
||||
// Type is a method to get the type of a component.
|
||||
func (SelectMenu) Type() ComponentType {
|
||||
func (s SelectMenu) Type() ComponentType {
|
||||
if s.MenuType != 0 {
|
||||
return ComponentType(s.MenuType)
|
||||
}
|
||||
return SelectMenuComponent
|
||||
}
|
||||
|
||||
// MarshalJSON is a method for marshaling SelectMenu to a JSON object.
|
||||
func (m SelectMenu) MarshalJSON() ([]byte, error) {
|
||||
func (s SelectMenu) MarshalJSON() ([]byte, error) {
|
||||
type selectMenu SelectMenu
|
||||
|
||||
return Marshal(struct {
|
||||
selectMenu
|
||||
Type ComponentType `json:"type"`
|
||||
}{
|
||||
selectMenu: selectMenu(m),
|
||||
Type: m.Type(),
|
||||
selectMenu: selectMenu(s),
|
||||
Type: s.Type(),
|
||||
})
|
||||
}
|
||||
|
||||
|
7
vendor/github.com/bwmarrin/discordgo/discord.go
generated
vendored
7
vendor/github.com/bwmarrin/discordgo/discord.go
generated
vendored
@@ -17,10 +17,12 @@ import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
|
||||
const VERSION = "0.25.0"
|
||||
const VERSION = "0.27.0"
|
||||
|
||||
// New creates a new Discord session with provided token.
|
||||
// If the token is for a bot, it must be prefixed with "Bot "
|
||||
@@ -41,12 +43,13 @@ func New(token string) (s *Session, err error) {
|
||||
ShardCount: 1,
|
||||
MaxRestRetries: 3,
|
||||
Client: &http.Client{Timeout: (20 * time.Second)},
|
||||
Dialer: websocket.DefaultDialer,
|
||||
UserAgent: "DiscordBot (https://github.com/bwmarrin/discordgo, v" + VERSION + ")",
|
||||
sequence: new(int64),
|
||||
LastHeartbeatAck: time.Now().UTC(),
|
||||
}
|
||||
|
||||
// Initilize the Identify Package with defaults
|
||||
// Initialize the Identify Package with defaults
|
||||
// These can be modified prior to calling Open()
|
||||
s.Identify.Compress = true
|
||||
s.Identify.LargeThreshold = 250
|
||||
|
21
vendor/github.com/bwmarrin/discordgo/endpoints.go
generated
vendored
21
vendor/github.com/bwmarrin/discordgo/endpoints.go
generated
vendored
@@ -46,8 +46,6 @@ var (
|
||||
EndpointVoice = EndpointAPI + "/voice/"
|
||||
EndpointVoiceRegions = EndpointVoice + "regions"
|
||||
|
||||
// TODO: EndpointUserGuildMember
|
||||
|
||||
EndpointUser = func(uID string) string { return EndpointUsers + uID }
|
||||
EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" }
|
||||
EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" }
|
||||
@@ -62,12 +60,17 @@ var (
|
||||
return EndpointCDNBanners + uID + "/" + cID + ".gif"
|
||||
}
|
||||
|
||||
EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
|
||||
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
|
||||
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
|
||||
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
|
||||
EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
|
||||
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
|
||||
EndpointUserGuildMember = func(uID, gID string) string { return EndpointUserGuild(uID, gID) + "/member" }
|
||||
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
|
||||
EndpointUserApplicationRoleConnection = func(aID string) string { return EndpointUsers + "@me/applications/" + aID + "/role-connection" }
|
||||
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
|
||||
|
||||
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
|
||||
EndpointGuildAutoModeration = func(gID string) string { return EndpointGuild(gID) + "/auto-moderation" }
|
||||
EndpointGuildAutoModerationRules = func(gID string) string { return EndpointGuildAutoModeration(gID) + "/rules" }
|
||||
EndpointGuildAutoModerationRule = func(gID, rID string) string { return EndpointGuildAutoModerationRules(gID) + "/" + rID }
|
||||
EndpointGuildThreads = func(gID string) string { return EndpointGuild(gID) + "/threads" }
|
||||
EndpointGuildActiveThreads = func(gID string) string { return EndpointGuildThreads(gID) + "/active" }
|
||||
EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" }
|
||||
@@ -94,6 +97,7 @@ var (
|
||||
EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" }
|
||||
EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID }
|
||||
EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" }
|
||||
EndpointGuildBannerAnimated = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".gif" }
|
||||
EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" }
|
||||
EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID }
|
||||
EndpointStageInstance = func(cID string) string { return EndpointStageInstances + "/" + cID }
|
||||
@@ -195,8 +199,9 @@ var (
|
||||
EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" }
|
||||
EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" }
|
||||
|
||||
EndpointApplications = EndpointAPI + "applications"
|
||||
EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID }
|
||||
EndpointApplications = EndpointAPI + "applications"
|
||||
EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID }
|
||||
EndpointApplicationRoleConnectionMetadata = func(aID string) string { return EndpointApplication(aID) + "/role-connections/metadata" }
|
||||
|
||||
EndpointOAuth2 = EndpointAPI + "oauth2/"
|
||||
EndpointOAuth2Applications = EndpointOAuth2 + "applications"
|
||||
|
503
vendor/github.com/bwmarrin/discordgo/eventhandlers.go
generated
vendored
503
vendor/github.com/bwmarrin/discordgo/eventhandlers.go
generated
vendored
@@ -7,69 +7,168 @@ package discordgo
|
||||
// Event type values are used to match the events returned by Discord.
|
||||
// EventTypes surrounded by __ are synthetic and are internal to DiscordGo.
|
||||
const (
|
||||
channelCreateEventType = "CHANNEL_CREATE"
|
||||
channelDeleteEventType = "CHANNEL_DELETE"
|
||||
channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE"
|
||||
channelUpdateEventType = "CHANNEL_UPDATE"
|
||||
connectEventType = "__CONNECT__"
|
||||
disconnectEventType = "__DISCONNECT__"
|
||||
eventEventType = "__EVENT__"
|
||||
guildBanAddEventType = "GUILD_BAN_ADD"
|
||||
guildBanRemoveEventType = "GUILD_BAN_REMOVE"
|
||||
guildCreateEventType = "GUILD_CREATE"
|
||||
guildDeleteEventType = "GUILD_DELETE"
|
||||
guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE"
|
||||
guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE"
|
||||
guildMemberAddEventType = "GUILD_MEMBER_ADD"
|
||||
guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE"
|
||||
guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE"
|
||||
guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK"
|
||||
guildRoleCreateEventType = "GUILD_ROLE_CREATE"
|
||||
guildRoleDeleteEventType = "GUILD_ROLE_DELETE"
|
||||
guildRoleUpdateEventType = "GUILD_ROLE_UPDATE"
|
||||
guildStageInstanceCreateEventType = "STAGE_INSTANCE_CREATE"
|
||||
guildStageInstanceUpdateEventType = "STAGE_INSTANCE_UPDATE"
|
||||
guildStageInstanceDeleteEventType = "STAGE_INSTANCE_DELETE"
|
||||
guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE"
|
||||
guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE"
|
||||
guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE"
|
||||
guildScheduledEventUserAddEventType = "GUILD_SCHEDULED_EVENT_USER_ADD"
|
||||
guildScheduledEventUserRemoveEventType = "GUILD_SCHEDULED_EVENT_USER_REMOVE"
|
||||
guildUpdateEventType = "GUILD_UPDATE"
|
||||
interactionCreateEventType = "INTERACTION_CREATE"
|
||||
inviteCreateEventType = "INVITE_CREATE"
|
||||
inviteDeleteEventType = "INVITE_DELETE"
|
||||
messageAckEventType = "MESSAGE_ACK"
|
||||
messageCreateEventType = "MESSAGE_CREATE"
|
||||
messageDeleteEventType = "MESSAGE_DELETE"
|
||||
messageDeleteBulkEventType = "MESSAGE_DELETE_BULK"
|
||||
messageReactionAddEventType = "MESSAGE_REACTION_ADD"
|
||||
messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE"
|
||||
messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL"
|
||||
messageUpdateEventType = "MESSAGE_UPDATE"
|
||||
presenceUpdateEventType = "PRESENCE_UPDATE"
|
||||
presencesReplaceEventType = "PRESENCES_REPLACE"
|
||||
rateLimitEventType = "__RATE_LIMIT__"
|
||||
readyEventType = "READY"
|
||||
relationshipAddEventType = "RELATIONSHIP_ADD"
|
||||
relationshipRemoveEventType = "RELATIONSHIP_REMOVE"
|
||||
resumedEventType = "RESUMED"
|
||||
threadCreateEventType = "THREAD_CREATE"
|
||||
threadDeleteEventType = "THREAD_DELETE"
|
||||
threadListSyncEventType = "THREAD_LIST_SYNC"
|
||||
threadMemberUpdateEventType = "THREAD_MEMBER_UPDATE"
|
||||
threadMembersUpdateEventType = "THREAD_MEMBERS_UPDATE"
|
||||
threadUpdateEventType = "THREAD_UPDATE"
|
||||
typingStartEventType = "TYPING_START"
|
||||
userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE"
|
||||
userNoteUpdateEventType = "USER_NOTE_UPDATE"
|
||||
userSettingsUpdateEventType = "USER_SETTINGS_UPDATE"
|
||||
userUpdateEventType = "USER_UPDATE"
|
||||
voiceServerUpdateEventType = "VOICE_SERVER_UPDATE"
|
||||
voiceStateUpdateEventType = "VOICE_STATE_UPDATE"
|
||||
webhooksUpdateEventType = "WEBHOOKS_UPDATE"
|
||||
applicationCommandPermissionsUpdateEventType = "APPLICATION_COMMAND_PERMISSIONS_UPDATE"
|
||||
autoModerationActionExecutionEventType = "AUTO_MODERATION_ACTION_EXECUTION"
|
||||
autoModerationRuleCreateEventType = "AUTO_MODERATION_RULE_CREATE"
|
||||
autoModerationRuleDeleteEventType = "AUTO_MODERATION_RULE_DELETE"
|
||||
autoModerationRuleUpdateEventType = "AUTO_MODERATION_RULE_UPDATE"
|
||||
channelCreateEventType = "CHANNEL_CREATE"
|
||||
channelDeleteEventType = "CHANNEL_DELETE"
|
||||
channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE"
|
||||
channelUpdateEventType = "CHANNEL_UPDATE"
|
||||
connectEventType = "__CONNECT__"
|
||||
disconnectEventType = "__DISCONNECT__"
|
||||
eventEventType = "__EVENT__"
|
||||
guildBanAddEventType = "GUILD_BAN_ADD"
|
||||
guildBanRemoveEventType = "GUILD_BAN_REMOVE"
|
||||
guildCreateEventType = "GUILD_CREATE"
|
||||
guildDeleteEventType = "GUILD_DELETE"
|
||||
guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE"
|
||||
guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE"
|
||||
guildMemberAddEventType = "GUILD_MEMBER_ADD"
|
||||
guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE"
|
||||
guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE"
|
||||
guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK"
|
||||
guildRoleCreateEventType = "GUILD_ROLE_CREATE"
|
||||
guildRoleDeleteEventType = "GUILD_ROLE_DELETE"
|
||||
guildRoleUpdateEventType = "GUILD_ROLE_UPDATE"
|
||||
guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE"
|
||||
guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE"
|
||||
guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE"
|
||||
guildScheduledEventUserAddEventType = "GUILD_SCHEDULED_EVENT_USER_ADD"
|
||||
guildScheduledEventUserRemoveEventType = "GUILD_SCHEDULED_EVENT_USER_REMOVE"
|
||||
guildUpdateEventType = "GUILD_UPDATE"
|
||||
interactionCreateEventType = "INTERACTION_CREATE"
|
||||
inviteCreateEventType = "INVITE_CREATE"
|
||||
inviteDeleteEventType = "INVITE_DELETE"
|
||||
messageCreateEventType = "MESSAGE_CREATE"
|
||||
messageDeleteEventType = "MESSAGE_DELETE"
|
||||
messageDeleteBulkEventType = "MESSAGE_DELETE_BULK"
|
||||
messageReactionAddEventType = "MESSAGE_REACTION_ADD"
|
||||
messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE"
|
||||
messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL"
|
||||
messageUpdateEventType = "MESSAGE_UPDATE"
|
||||
presenceUpdateEventType = "PRESENCE_UPDATE"
|
||||
presencesReplaceEventType = "PRESENCES_REPLACE"
|
||||
rateLimitEventType = "__RATE_LIMIT__"
|
||||
readyEventType = "READY"
|
||||
resumedEventType = "RESUMED"
|
||||
stageInstanceEventCreateEventType = "STAGE_INSTANCE_EVENT_CREATE"
|
||||
stageInstanceEventDeleteEventType = "STAGE_INSTANCE_EVENT_DELETE"
|
||||
stageInstanceEventUpdateEventType = "STAGE_INSTANCE_EVENT_UPDATE"
|
||||
threadCreateEventType = "THREAD_CREATE"
|
||||
threadDeleteEventType = "THREAD_DELETE"
|
||||
threadListSyncEventType = "THREAD_LIST_SYNC"
|
||||
threadMemberUpdateEventType = "THREAD_MEMBER_UPDATE"
|
||||
threadMembersUpdateEventType = "THREAD_MEMBERS_UPDATE"
|
||||
threadUpdateEventType = "THREAD_UPDATE"
|
||||
typingStartEventType = "TYPING_START"
|
||||
userUpdateEventType = "USER_UPDATE"
|
||||
voiceServerUpdateEventType = "VOICE_SERVER_UPDATE"
|
||||
voiceStateUpdateEventType = "VOICE_STATE_UPDATE"
|
||||
webhooksUpdateEventType = "WEBHOOKS_UPDATE"
|
||||
)
|
||||
|
||||
// applicationCommandPermissionsUpdateEventHandler is an event handler for ApplicationCommandPermissionsUpdate events.
|
||||
type applicationCommandPermissionsUpdateEventHandler func(*Session, *ApplicationCommandPermissionsUpdate)
|
||||
|
||||
// Type returns the event type for ApplicationCommandPermissionsUpdate events.
|
||||
func (eh applicationCommandPermissionsUpdateEventHandler) Type() string {
|
||||
return applicationCommandPermissionsUpdateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of ApplicationCommandPermissionsUpdate.
|
||||
func (eh applicationCommandPermissionsUpdateEventHandler) New() interface{} {
|
||||
return &ApplicationCommandPermissionsUpdate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for ApplicationCommandPermissionsUpdate events.
|
||||
func (eh applicationCommandPermissionsUpdateEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*ApplicationCommandPermissionsUpdate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// autoModerationActionExecutionEventHandler is an event handler for AutoModerationActionExecution events.
|
||||
type autoModerationActionExecutionEventHandler func(*Session, *AutoModerationActionExecution)
|
||||
|
||||
// Type returns the event type for AutoModerationActionExecution events.
|
||||
func (eh autoModerationActionExecutionEventHandler) Type() string {
|
||||
return autoModerationActionExecutionEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of AutoModerationActionExecution.
|
||||
func (eh autoModerationActionExecutionEventHandler) New() interface{} {
|
||||
return &AutoModerationActionExecution{}
|
||||
}
|
||||
|
||||
// Handle is the handler for AutoModerationActionExecution events.
|
||||
func (eh autoModerationActionExecutionEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*AutoModerationActionExecution); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// autoModerationRuleCreateEventHandler is an event handler for AutoModerationRuleCreate events.
|
||||
type autoModerationRuleCreateEventHandler func(*Session, *AutoModerationRuleCreate)
|
||||
|
||||
// Type returns the event type for AutoModerationRuleCreate events.
|
||||
func (eh autoModerationRuleCreateEventHandler) Type() string {
|
||||
return autoModerationRuleCreateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of AutoModerationRuleCreate.
|
||||
func (eh autoModerationRuleCreateEventHandler) New() interface{} {
|
||||
return &AutoModerationRuleCreate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for AutoModerationRuleCreate events.
|
||||
func (eh autoModerationRuleCreateEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*AutoModerationRuleCreate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// autoModerationRuleDeleteEventHandler is an event handler for AutoModerationRuleDelete events.
|
||||
type autoModerationRuleDeleteEventHandler func(*Session, *AutoModerationRuleDelete)
|
||||
|
||||
// Type returns the event type for AutoModerationRuleDelete events.
|
||||
func (eh autoModerationRuleDeleteEventHandler) Type() string {
|
||||
return autoModerationRuleDeleteEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of AutoModerationRuleDelete.
|
||||
func (eh autoModerationRuleDeleteEventHandler) New() interface{} {
|
||||
return &AutoModerationRuleDelete{}
|
||||
}
|
||||
|
||||
// Handle is the handler for AutoModerationRuleDelete events.
|
||||
func (eh autoModerationRuleDeleteEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*AutoModerationRuleDelete); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// autoModerationRuleUpdateEventHandler is an event handler for AutoModerationRuleUpdate events.
|
||||
type autoModerationRuleUpdateEventHandler func(*Session, *AutoModerationRuleUpdate)
|
||||
|
||||
// Type returns the event type for AutoModerationRuleUpdate events.
|
||||
func (eh autoModerationRuleUpdateEventHandler) Type() string {
|
||||
return autoModerationRuleUpdateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of AutoModerationRuleUpdate.
|
||||
func (eh autoModerationRuleUpdateEventHandler) New() interface{} {
|
||||
return &AutoModerationRuleUpdate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for AutoModerationRuleUpdate events.
|
||||
func (eh autoModerationRuleUpdateEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*AutoModerationRuleUpdate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// channelCreateEventHandler is an event handler for ChannelCreate events.
|
||||
type channelCreateEventHandler func(*Session, *ChannelCreate)
|
||||
|
||||
@@ -455,66 +554,6 @@ func (eh guildRoleUpdateEventHandler) Handle(s *Session, i interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// guildStageInstanceEventCreateHandler is an event handler for StageInstanceEventCreate events.
|
||||
type guildStageInstanceEventCreateHandler func(*Session, *StageInstanceEventCreate)
|
||||
|
||||
// Type returns the event type for StageInstanceEventCreate events.
|
||||
func (eh guildStageInstanceEventCreateHandler) Type() string {
|
||||
return guildStageInstanceCreateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of StageInstanceEventCreate.
|
||||
func (eh guildStageInstanceEventCreateHandler) New() interface{} {
|
||||
return &StageInstanceEventCreate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for StageInstanceEventCreate events.
|
||||
func (eh guildStageInstanceEventCreateHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*StageInstanceEventCreate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// guildStageInstanceEventUpdateHandler is an event handler for StageInstanceEventUpdate events.
|
||||
type guildStageInstanceEventUpdateHandler func(*Session, *StageInstanceEventUpdate)
|
||||
|
||||
// Type returns the event type for StageInstanceEventUpdate events.
|
||||
func (eh guildStageInstanceEventUpdateHandler) Type() string {
|
||||
return guildStageInstanceCreateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of StageInstanceEventUpdate.
|
||||
func (eh guildStageInstanceEventUpdateHandler) New() interface{} {
|
||||
return &StageInstanceEventUpdate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for StageInstanceEventUpdate events.
|
||||
func (eh guildStageInstanceEventUpdateHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*StageInstanceEventUpdate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// guildStageInstanceEventDeleteHandler is an event handler for StageInstanceEventDelete events.
|
||||
type guildStageInstanceEventDeleteHandler func(*Session, *StageInstanceEventDelete)
|
||||
|
||||
// Type returns the event type for StageInstanceEventDelete events.
|
||||
func (eh guildStageInstanceEventDeleteHandler) Type() string {
|
||||
return guildStageInstanceCreateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of StageInstanceEventDelete.
|
||||
func (eh guildStageInstanceEventDeleteHandler) New() interface{} {
|
||||
return &StageInstanceEventDelete{}
|
||||
}
|
||||
|
||||
// Handle is the handler for StageInstanceEventDelete events.
|
||||
func (eh guildStageInstanceEventDeleteHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*StageInstanceEventDelete); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// guildScheduledEventCreateEventHandler is an event handler for GuildScheduledEventCreate events.
|
||||
type guildScheduledEventCreateEventHandler func(*Session, *GuildScheduledEventCreate)
|
||||
|
||||
@@ -695,26 +734,6 @@ func (eh inviteDeleteEventHandler) Handle(s *Session, i interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// messageAckEventHandler is an event handler for MessageAck events.
|
||||
type messageAckEventHandler func(*Session, *MessageAck)
|
||||
|
||||
// Type returns the event type for MessageAck events.
|
||||
func (eh messageAckEventHandler) Type() string {
|
||||
return messageAckEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of MessageAck.
|
||||
func (eh messageAckEventHandler) New() interface{} {
|
||||
return &MessageAck{}
|
||||
}
|
||||
|
||||
// Handle is the handler for MessageAck events.
|
||||
func (eh messageAckEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*MessageAck); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// messageCreateEventHandler is an event handler for MessageCreate events.
|
||||
type messageCreateEventHandler func(*Session, *MessageCreate)
|
||||
|
||||
@@ -930,46 +949,6 @@ func (eh readyEventHandler) Handle(s *Session, i interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// relationshipAddEventHandler is an event handler for RelationshipAdd events.
|
||||
type relationshipAddEventHandler func(*Session, *RelationshipAdd)
|
||||
|
||||
// Type returns the event type for RelationshipAdd events.
|
||||
func (eh relationshipAddEventHandler) Type() string {
|
||||
return relationshipAddEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of RelationshipAdd.
|
||||
func (eh relationshipAddEventHandler) New() interface{} {
|
||||
return &RelationshipAdd{}
|
||||
}
|
||||
|
||||
// Handle is the handler for RelationshipAdd events.
|
||||
func (eh relationshipAddEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*RelationshipAdd); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// relationshipRemoveEventHandler is an event handler for RelationshipRemove events.
|
||||
type relationshipRemoveEventHandler func(*Session, *RelationshipRemove)
|
||||
|
||||
// Type returns the event type for RelationshipRemove events.
|
||||
func (eh relationshipRemoveEventHandler) Type() string {
|
||||
return relationshipRemoveEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of RelationshipRemove.
|
||||
func (eh relationshipRemoveEventHandler) New() interface{} {
|
||||
return &RelationshipRemove{}
|
||||
}
|
||||
|
||||
// Handle is the handler for RelationshipRemove events.
|
||||
func (eh relationshipRemoveEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*RelationshipRemove); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// resumedEventHandler is an event handler for Resumed events.
|
||||
type resumedEventHandler func(*Session, *Resumed)
|
||||
|
||||
@@ -990,6 +969,66 @@ func (eh resumedEventHandler) Handle(s *Session, i interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// stageInstanceEventCreateEventHandler is an event handler for StageInstanceEventCreate events.
|
||||
type stageInstanceEventCreateEventHandler func(*Session, *StageInstanceEventCreate)
|
||||
|
||||
// Type returns the event type for StageInstanceEventCreate events.
|
||||
func (eh stageInstanceEventCreateEventHandler) Type() string {
|
||||
return stageInstanceEventCreateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of StageInstanceEventCreate.
|
||||
func (eh stageInstanceEventCreateEventHandler) New() interface{} {
|
||||
return &StageInstanceEventCreate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for StageInstanceEventCreate events.
|
||||
func (eh stageInstanceEventCreateEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*StageInstanceEventCreate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// stageInstanceEventDeleteEventHandler is an event handler for StageInstanceEventDelete events.
|
||||
type stageInstanceEventDeleteEventHandler func(*Session, *StageInstanceEventDelete)
|
||||
|
||||
// Type returns the event type for StageInstanceEventDelete events.
|
||||
func (eh stageInstanceEventDeleteEventHandler) Type() string {
|
||||
return stageInstanceEventDeleteEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of StageInstanceEventDelete.
|
||||
func (eh stageInstanceEventDeleteEventHandler) New() interface{} {
|
||||
return &StageInstanceEventDelete{}
|
||||
}
|
||||
|
||||
// Handle is the handler for StageInstanceEventDelete events.
|
||||
func (eh stageInstanceEventDeleteEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*StageInstanceEventDelete); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// stageInstanceEventUpdateEventHandler is an event handler for StageInstanceEventUpdate events.
|
||||
type stageInstanceEventUpdateEventHandler func(*Session, *StageInstanceEventUpdate)
|
||||
|
||||
// Type returns the event type for StageInstanceEventUpdate events.
|
||||
func (eh stageInstanceEventUpdateEventHandler) Type() string {
|
||||
return stageInstanceEventUpdateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of StageInstanceEventUpdate.
|
||||
func (eh stageInstanceEventUpdateEventHandler) New() interface{} {
|
||||
return &StageInstanceEventUpdate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for StageInstanceEventUpdate events.
|
||||
func (eh stageInstanceEventUpdateEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*StageInstanceEventUpdate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// threadCreateEventHandler is an event handler for ThreadCreate events.
|
||||
type threadCreateEventHandler func(*Session, *ThreadCreate)
|
||||
|
||||
@@ -1130,66 +1169,6 @@ func (eh typingStartEventHandler) Handle(s *Session, i interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// userGuildSettingsUpdateEventHandler is an event handler for UserGuildSettingsUpdate events.
|
||||
type userGuildSettingsUpdateEventHandler func(*Session, *UserGuildSettingsUpdate)
|
||||
|
||||
// Type returns the event type for UserGuildSettingsUpdate events.
|
||||
func (eh userGuildSettingsUpdateEventHandler) Type() string {
|
||||
return userGuildSettingsUpdateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of UserGuildSettingsUpdate.
|
||||
func (eh userGuildSettingsUpdateEventHandler) New() interface{} {
|
||||
return &UserGuildSettingsUpdate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for UserGuildSettingsUpdate events.
|
||||
func (eh userGuildSettingsUpdateEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*UserGuildSettingsUpdate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// userNoteUpdateEventHandler is an event handler for UserNoteUpdate events.
|
||||
type userNoteUpdateEventHandler func(*Session, *UserNoteUpdate)
|
||||
|
||||
// Type returns the event type for UserNoteUpdate events.
|
||||
func (eh userNoteUpdateEventHandler) Type() string {
|
||||
return userNoteUpdateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of UserNoteUpdate.
|
||||
func (eh userNoteUpdateEventHandler) New() interface{} {
|
||||
return &UserNoteUpdate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for UserNoteUpdate events.
|
||||
func (eh userNoteUpdateEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*UserNoteUpdate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// userSettingsUpdateEventHandler is an event handler for UserSettingsUpdate events.
|
||||
type userSettingsUpdateEventHandler func(*Session, *UserSettingsUpdate)
|
||||
|
||||
// Type returns the event type for UserSettingsUpdate events.
|
||||
func (eh userSettingsUpdateEventHandler) Type() string {
|
||||
return userSettingsUpdateEventType
|
||||
}
|
||||
|
||||
// New returns a new instance of UserSettingsUpdate.
|
||||
func (eh userSettingsUpdateEventHandler) New() interface{} {
|
||||
return &UserSettingsUpdate{}
|
||||
}
|
||||
|
||||
// Handle is the handler for UserSettingsUpdate events.
|
||||
func (eh userSettingsUpdateEventHandler) Handle(s *Session, i interface{}) {
|
||||
if t, ok := i.(*UserSettingsUpdate); ok {
|
||||
eh(s, t)
|
||||
}
|
||||
}
|
||||
|
||||
// userUpdateEventHandler is an event handler for UserUpdate events.
|
||||
type userUpdateEventHandler func(*Session, *UserUpdate)
|
||||
|
||||
@@ -1274,6 +1253,16 @@ func handlerForInterface(handler interface{}) EventHandler {
|
||||
switch v := handler.(type) {
|
||||
case func(*Session, interface{}):
|
||||
return interfaceEventHandler(v)
|
||||
case func(*Session, *ApplicationCommandPermissionsUpdate):
|
||||
return applicationCommandPermissionsUpdateEventHandler(v)
|
||||
case func(*Session, *AutoModerationActionExecution):
|
||||
return autoModerationActionExecutionEventHandler(v)
|
||||
case func(*Session, *AutoModerationRuleCreate):
|
||||
return autoModerationRuleCreateEventHandler(v)
|
||||
case func(*Session, *AutoModerationRuleDelete):
|
||||
return autoModerationRuleDeleteEventHandler(v)
|
||||
case func(*Session, *AutoModerationRuleUpdate):
|
||||
return autoModerationRuleUpdateEventHandler(v)
|
||||
case func(*Session, *ChannelCreate):
|
||||
return channelCreateEventHandler(v)
|
||||
case func(*Session, *ChannelDelete):
|
||||
@@ -1332,8 +1321,6 @@ func handlerForInterface(handler interface{}) EventHandler {
|
||||
return inviteCreateEventHandler(v)
|
||||
case func(*Session, *InviteDelete):
|
||||
return inviteDeleteEventHandler(v)
|
||||
case func(*Session, *MessageAck):
|
||||
return messageAckEventHandler(v)
|
||||
case func(*Session, *MessageCreate):
|
||||
return messageCreateEventHandler(v)
|
||||
case func(*Session, *MessageDelete):
|
||||
@@ -1356,12 +1343,14 @@ func handlerForInterface(handler interface{}) EventHandler {
|
||||
return rateLimitEventHandler(v)
|
||||
case func(*Session, *Ready):
|
||||
return readyEventHandler(v)
|
||||
case func(*Session, *RelationshipAdd):
|
||||
return relationshipAddEventHandler(v)
|
||||
case func(*Session, *RelationshipRemove):
|
||||
return relationshipRemoveEventHandler(v)
|
||||
case func(*Session, *Resumed):
|
||||
return resumedEventHandler(v)
|
||||
case func(*Session, *StageInstanceEventCreate):
|
||||
return stageInstanceEventCreateEventHandler(v)
|
||||
case func(*Session, *StageInstanceEventDelete):
|
||||
return stageInstanceEventDeleteEventHandler(v)
|
||||
case func(*Session, *StageInstanceEventUpdate):
|
||||
return stageInstanceEventUpdateEventHandler(v)
|
||||
case func(*Session, *ThreadCreate):
|
||||
return threadCreateEventHandler(v)
|
||||
case func(*Session, *ThreadDelete):
|
||||
@@ -1376,12 +1365,6 @@ func handlerForInterface(handler interface{}) EventHandler {
|
||||
return threadUpdateEventHandler(v)
|
||||
case func(*Session, *TypingStart):
|
||||
return typingStartEventHandler(v)
|
||||
case func(*Session, *UserGuildSettingsUpdate):
|
||||
return userGuildSettingsUpdateEventHandler(v)
|
||||
case func(*Session, *UserNoteUpdate):
|
||||
return userNoteUpdateEventHandler(v)
|
||||
case func(*Session, *UserSettingsUpdate):
|
||||
return userSettingsUpdateEventHandler(v)
|
||||
case func(*Session, *UserUpdate):
|
||||
return userUpdateEventHandler(v)
|
||||
case func(*Session, *VoiceServerUpdate):
|
||||
@@ -1396,6 +1379,11 @@ func handlerForInterface(handler interface{}) EventHandler {
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerInterfaceProvider(applicationCommandPermissionsUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(autoModerationActionExecutionEventHandler(nil))
|
||||
registerInterfaceProvider(autoModerationRuleCreateEventHandler(nil))
|
||||
registerInterfaceProvider(autoModerationRuleDeleteEventHandler(nil))
|
||||
registerInterfaceProvider(autoModerationRuleUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(channelCreateEventHandler(nil))
|
||||
registerInterfaceProvider(channelDeleteEventHandler(nil))
|
||||
registerInterfaceProvider(channelPinsUpdateEventHandler(nil))
|
||||
@@ -1422,7 +1410,6 @@ func init() {
|
||||
registerInterfaceProvider(interactionCreateEventHandler(nil))
|
||||
registerInterfaceProvider(inviteCreateEventHandler(nil))
|
||||
registerInterfaceProvider(inviteDeleteEventHandler(nil))
|
||||
registerInterfaceProvider(messageAckEventHandler(nil))
|
||||
registerInterfaceProvider(messageCreateEventHandler(nil))
|
||||
registerInterfaceProvider(messageDeleteEventHandler(nil))
|
||||
registerInterfaceProvider(messageDeleteBulkEventHandler(nil))
|
||||
@@ -1433,9 +1420,10 @@ func init() {
|
||||
registerInterfaceProvider(presenceUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(presencesReplaceEventHandler(nil))
|
||||
registerInterfaceProvider(readyEventHandler(nil))
|
||||
registerInterfaceProvider(relationshipAddEventHandler(nil))
|
||||
registerInterfaceProvider(relationshipRemoveEventHandler(nil))
|
||||
registerInterfaceProvider(resumedEventHandler(nil))
|
||||
registerInterfaceProvider(stageInstanceEventCreateEventHandler(nil))
|
||||
registerInterfaceProvider(stageInstanceEventDeleteEventHandler(nil))
|
||||
registerInterfaceProvider(stageInstanceEventUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(threadCreateEventHandler(nil))
|
||||
registerInterfaceProvider(threadDeleteEventHandler(nil))
|
||||
registerInterfaceProvider(threadListSyncEventHandler(nil))
|
||||
@@ -1443,9 +1431,6 @@ func init() {
|
||||
registerInterfaceProvider(threadMembersUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(threadUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(typingStartEventHandler(nil))
|
||||
registerInterfaceProvider(userGuildSettingsUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(userNoteUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(userSettingsUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(userUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(voiceServerUpdateEventHandler(nil))
|
||||
registerInterfaceProvider(voiceStateUpdateEventHandler(nil))
|
||||
|
78
vendor/github.com/bwmarrin/discordgo/events.go
generated
vendored
78
vendor/github.com/bwmarrin/discordgo/events.go
generated
vendored
@@ -39,16 +39,10 @@ type Ready struct {
|
||||
Version int `json:"v"`
|
||||
SessionID string `json:"session_id"`
|
||||
User *User `json:"user"`
|
||||
ReadState []*ReadState `json:"read_state"`
|
||||
PrivateChannels []*Channel `json:"private_channels"`
|
||||
Shard *[2]int `json:"shard"`
|
||||
Application *Application `json:"application"`
|
||||
Guilds []*Guild `json:"guilds"`
|
||||
|
||||
// Undocumented fields
|
||||
Settings *Settings `json:"user_settings"`
|
||||
UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"`
|
||||
Relationships []*Relationship `json:"relationships"`
|
||||
Presences []*Presence `json:"presences"`
|
||||
Notes map[string]string `json:"notes"`
|
||||
PrivateChannels []*Channel `json:"private_channels"`
|
||||
}
|
||||
|
||||
// ChannelCreate is the data for a ChannelCreate event.
|
||||
@@ -156,6 +150,7 @@ type GuildMemberAdd struct {
|
||||
// GuildMemberUpdate is the data for a GuildMemberUpdate event.
|
||||
type GuildMemberUpdate struct {
|
||||
*Member
|
||||
BeforeUpdate *Member `json:"-"`
|
||||
}
|
||||
|
||||
// GuildMemberRemove is the data for a GuildMemberRemove event.
|
||||
@@ -245,12 +240,6 @@ type GuildScheduledEventUserRemove struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
}
|
||||
|
||||
// MessageAck is the data for a MessageAck event.
|
||||
type MessageAck struct {
|
||||
MessageID string `json:"message_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
}
|
||||
|
||||
// MessageCreate is the data for a MessageCreate event.
|
||||
type MessageCreate struct {
|
||||
*Message
|
||||
@@ -314,16 +303,6 @@ type Resumed struct {
|
||||
Trace []string `json:"_trace"`
|
||||
}
|
||||
|
||||
// RelationshipAdd is the data for a RelationshipAdd event.
|
||||
type RelationshipAdd struct {
|
||||
*Relationship
|
||||
}
|
||||
|
||||
// RelationshipRemove is the data for a RelationshipRemove event.
|
||||
type RelationshipRemove struct {
|
||||
*Relationship
|
||||
}
|
||||
|
||||
// TypingStart is the data for a TypingStart event.
|
||||
type TypingStart struct {
|
||||
UserID string `json:"user_id"`
|
||||
@@ -337,20 +316,6 @@ type UserUpdate struct {
|
||||
*User
|
||||
}
|
||||
|
||||
// UserSettingsUpdate is the data for a UserSettingsUpdate event.
|
||||
type UserSettingsUpdate map[string]interface{}
|
||||
|
||||
// UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event.
|
||||
type UserGuildSettingsUpdate struct {
|
||||
*UserGuildSettings
|
||||
}
|
||||
|
||||
// UserNoteUpdate is the data for a UserNoteUpdate event.
|
||||
type UserNoteUpdate struct {
|
||||
ID string `json:"id"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
// VoiceServerUpdate is the data for a VoiceServerUpdate event.
|
||||
type VoiceServerUpdate struct {
|
||||
Token string `json:"token"`
|
||||
@@ -401,3 +366,38 @@ type InviteDelete struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// ApplicationCommandPermissionsUpdate is the data for an ApplicationCommandPermissionsUpdate event
|
||||
type ApplicationCommandPermissionsUpdate struct {
|
||||
*GuildApplicationCommandPermissions
|
||||
}
|
||||
|
||||
// AutoModerationRuleCreate is the data for an AutoModerationRuleCreate event.
|
||||
type AutoModerationRuleCreate struct {
|
||||
*AutoModerationRule
|
||||
}
|
||||
|
||||
// AutoModerationRuleUpdate is the data for an AutoModerationRuleUpdate event.
|
||||
type AutoModerationRuleUpdate struct {
|
||||
*AutoModerationRule
|
||||
}
|
||||
|
||||
// AutoModerationRuleDelete is the data for an AutoModerationRuleDelete event.
|
||||
type AutoModerationRuleDelete struct {
|
||||
*AutoModerationRule
|
||||
}
|
||||
|
||||
// AutoModerationActionExecution is the data for an AutoModerationActionExecution event.
|
||||
type AutoModerationActionExecution struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
Action AutoModerationAction `json:"action"`
|
||||
RuleID string `json:"rule_id"`
|
||||
RuleTriggerType AutoModerationRuleTriggerType `json:"rule_trigger_type"`
|
||||
UserID string `json:"user_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
MessageID string `json:"message_id"`
|
||||
AlertSystemMessageID string `json:"alert_system_message_id"`
|
||||
Content string `json:"content"`
|
||||
MatchedKeyword string `json:"matched_keyword"`
|
||||
MatchedContent string `json:"matched_content"`
|
||||
}
|
||||
|
51
vendor/github.com/bwmarrin/discordgo/interactions.go
generated
vendored
51
vendor/github.com/bwmarrin/discordgo/interactions.go
generated
vendored
@@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -32,11 +33,16 @@ const (
|
||||
type ApplicationCommand struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
ApplicationID string `json:"application_id,omitempty"`
|
||||
GuildID string `json:"guild_id,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Type ApplicationCommandType `json:"type,omitempty"`
|
||||
Name string `json:"name"`
|
||||
NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"`
|
||||
DefaultPermission *bool `json:"default_permission,omitempty"`
|
||||
// NOTE: DefaultPermission will be soon deprecated. Use DefaultMemberPermissions and DMPermission instead.
|
||||
DefaultPermission *bool `json:"default_permission,omitempty"`
|
||||
DefaultMemberPermissions *int64 `json:"default_member_permissions,string,omitempty"`
|
||||
DMPermission *bool `json:"dm_permission,omitempty"`
|
||||
NSFW *bool `json:"nsfw,omitempty"`
|
||||
|
||||
// NOTE: Chat commands only. Otherwise it mustn't be set.
|
||||
|
||||
@@ -113,6 +119,10 @@ type ApplicationCommandOption struct {
|
||||
MinValue *float64 `json:"min_value,omitempty"`
|
||||
// Maximum value of number/integer option.
|
||||
MaxValue float64 `json:"max_value,omitempty"`
|
||||
// Minimum length of string option.
|
||||
MinLength *int `json:"min_length,omitempty"`
|
||||
// Maximum length of string option.
|
||||
MaxLength int `json:"max_length,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationCommandOptionChoice represents a slash command option choice.
|
||||
@@ -129,6 +139,18 @@ type ApplicationCommandPermissions struct {
|
||||
Permission bool `json:"permission"`
|
||||
}
|
||||
|
||||
// GuildAllChannelsID is a helper function which returns guild_id-1.
|
||||
// It is used in ApplicationCommandPermissions to target all the channels within a guild.
|
||||
func GuildAllChannelsID(guild string) (id string, err error) {
|
||||
var v uint64
|
||||
v, err = strconv.ParseUint(guild, 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return strconv.FormatUint(v-1, 10), nil
|
||||
}
|
||||
|
||||
// ApplicationCommandPermissionsList represents a list of ApplicationCommandPermissions, needed for serializing to JSON.
|
||||
type ApplicationCommandPermissionsList struct {
|
||||
Permissions []*ApplicationCommandPermissions `json:"permissions"`
|
||||
@@ -147,8 +169,9 @@ type ApplicationCommandPermissionType uint8
|
||||
|
||||
// Application command permission types.
|
||||
const (
|
||||
ApplicationCommandPermissionTypeRole ApplicationCommandPermissionType = 1
|
||||
ApplicationCommandPermissionTypeUser ApplicationCommandPermissionType = 2
|
||||
ApplicationCommandPermissionTypeRole ApplicationCommandPermissionType = 1
|
||||
ApplicationCommandPermissionTypeUser ApplicationCommandPermissionType = 2
|
||||
ApplicationCommandPermissionTypeChannel ApplicationCommandPermissionType = 3
|
||||
)
|
||||
|
||||
// InteractionType indicates the type of an interaction event.
|
||||
@@ -190,6 +213,9 @@ type Interaction struct {
|
||||
// NOTE: this field is only filled when a button click triggered the interaction. Otherwise it will be nil.
|
||||
Message *Message `json:"message"`
|
||||
|
||||
// Bitwise set of permissions the app or bot has within the channel the interaction was sent from
|
||||
AppPermissions int64 `json:"app_permissions,string"`
|
||||
|
||||
// The member who invoked this interaction.
|
||||
// NOTE: this field is only filled when the slash command was invoked in a guild;
|
||||
// if it was invoked in a DM, the `User` field will be filled instead.
|
||||
@@ -318,13 +344,22 @@ func (ApplicationCommandInteractionData) Type() InteractionType {
|
||||
|
||||
// MessageComponentInteractionData contains the data of message component interaction.
|
||||
type MessageComponentInteractionData struct {
|
||||
CustomID string `json:"custom_id"`
|
||||
ComponentType ComponentType `json:"component_type"`
|
||||
CustomID string `json:"custom_id"`
|
||||
ComponentType ComponentType `json:"component_type"`
|
||||
Resolved MessageComponentInteractionDataResolved `json:"resolved"`
|
||||
|
||||
// NOTE: Only filled when ComponentType is SelectMenuComponent (3). Otherwise is nil.
|
||||
Values []string `json:"values"`
|
||||
}
|
||||
|
||||
// MessageComponentInteractionDataResolved contains the resolved data of selected option.
|
||||
type MessageComponentInteractionDataResolved struct {
|
||||
Users map[string]*User `json:"users"`
|
||||
Members map[string]*Member `json:"members"`
|
||||
Roles map[string]*Role `json:"roles"`
|
||||
Channels map[string]*Channel `json:"channels"`
|
||||
}
|
||||
|
||||
// Type returns the type of interaction data.
|
||||
func (MessageComponentInteractionData) Type() InteractionType {
|
||||
return InteractionMessageComponent
|
||||
@@ -447,7 +482,7 @@ func (o ApplicationCommandInteractionDataOption) RoleValue(s *Session, gID strin
|
||||
return &Role{ID: roleID}
|
||||
}
|
||||
|
||||
r, err := s.State.Role(roleID, gID)
|
||||
r, err := s.State.Role(gID, roleID)
|
||||
if err != nil {
|
||||
roles, err := s.GuildRoles(gID)
|
||||
if err == nil {
|
||||
@@ -517,9 +552,11 @@ type InteractionResponseData struct {
|
||||
Components []MessageComponent `json:"components"`
|
||||
Embeds []*MessageEmbed `json:"embeds"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
Flags uint64 `json:"flags,omitempty"`
|
||||
Files []*File `json:"-"`
|
||||
|
||||
// NOTE: only MessageFlagsSuppressEmbeds and MessageFlagsEphemeral can be set.
|
||||
Flags MessageFlags `json:"flags,omitempty"`
|
||||
|
||||
// NOTE: autocomplete interaction only.
|
||||
Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"`
|
||||
|
||||
|
2
vendor/github.com/bwmarrin/discordgo/logging.go
generated
vendored
2
vendor/github.com/bwmarrin/discordgo/logging.go
generated
vendored
@@ -90,7 +90,7 @@ func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) {
|
||||
msglog(msgL, 2, format, a...)
|
||||
}
|
||||
|
||||
// printJSON is a helper function to display JSON data in a easy to read format.
|
||||
// printJSON is a helper function to display JSON data in an easy to read format.
|
||||
/* NOT USED ATM
|
||||
func printJSON(body []byte) {
|
||||
var prettyJSON bytes.Buffer
|
||||
|
13
vendor/github.com/bwmarrin/discordgo/message.go
generated
vendored
13
vendor/github.com/bwmarrin/discordgo/message.go
generated
vendored
@@ -249,6 +249,10 @@ type MessageEdit struct {
|
||||
Embeds []*MessageEmbed `json:"embeds"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
Flags MessageFlags `json:"flags,omitempty"`
|
||||
// Files to append to the message
|
||||
Files []*File `json:"-"`
|
||||
// Overwrite existing attachments
|
||||
Attachments *[]*MessageAttachment `json:"attachments,omitempty"`
|
||||
|
||||
ID string
|
||||
Channel string
|
||||
@@ -321,6 +325,9 @@ type MessageAllowedMentions struct {
|
||||
// A list of user IDs to allow. This cannot be used when specifying
|
||||
// AllowedMentionTypeUsers in the Parse slice.
|
||||
Users []string `json:"users,omitempty"`
|
||||
|
||||
// For replies, whether to mention the author of the message being replied to
|
||||
RepliedUser bool `json:"replied_user"`
|
||||
}
|
||||
|
||||
// A MessageAttachment stores data for message attachments.
|
||||
@@ -382,8 +389,8 @@ type MessageEmbedAuthor struct {
|
||||
|
||||
// MessageEmbedField is a part of a MessageEmbed struct.
|
||||
type MessageEmbedField struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Inline bool `json:"inline,omitempty"`
|
||||
}
|
||||
|
||||
@@ -454,7 +461,7 @@ type MessageApplication struct {
|
||||
// MessageReference contains reference data sent with crossposted messages
|
||||
type MessageReference struct {
|
||||
MessageID string `json:"message_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
ChannelID string `json:"channel_id,omitempty"`
|
||||
GuildID string `json:"guild_id,omitempty"`
|
||||
}
|
||||
|
||||
|
1289
vendor/github.com/bwmarrin/discordgo/restapi.go
generated
vendored
1289
vendor/github.com/bwmarrin/discordgo/restapi.go
generated
vendored
File diff suppressed because it is too large
Load Diff
47
vendor/github.com/bwmarrin/discordgo/state.go
generated
vendored
47
vendor/github.com/bwmarrin/discordgo/state.go
generated
vendored
@@ -7,7 +7,7 @@
|
||||
|
||||
// This file contains code related to state tracking. If enabled, state
|
||||
// tracking will capture the initial READY packet and many other websocket
|
||||
// events and maintain an in-memory state of of guilds, channels, users, and
|
||||
// events and maintain an in-memory state of guilds, channels, users, and
|
||||
// so forth. This information can be accessed through the Session.State struct.
|
||||
|
||||
package discordgo
|
||||
@@ -207,6 +207,15 @@ func (s *State) presenceAdd(guildID string, presence *Presence) error {
|
||||
if presence.Status != "" {
|
||||
guild.Presences[i].Status = presence.Status
|
||||
}
|
||||
if presence.ClientStatus.Desktop != "" {
|
||||
guild.Presences[i].ClientStatus.Desktop = presence.ClientStatus.Desktop
|
||||
}
|
||||
if presence.ClientStatus.Mobile != "" {
|
||||
guild.Presences[i].ClientStatus.Mobile = presence.ClientStatus.Mobile
|
||||
}
|
||||
if presence.ClientStatus.Web != "" {
|
||||
guild.Presences[i].ClientStatus.Web = presence.ClientStatus.Web
|
||||
}
|
||||
|
||||
//Update the optionally sent user information
|
||||
//ID Is a mandatory field so you should not need to check if it is empty
|
||||
@@ -661,18 +670,6 @@ func (s *State) ThreadMemberUpdate(mu *ThreadMemberUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GuildChannel gets a channel by ID from a guild.
|
||||
// This method is Deprecated, use Channel(channelID)
|
||||
func (s *State) GuildChannel(guildID, channelID string) (*Channel, error) {
|
||||
return s.Channel(channelID)
|
||||
}
|
||||
|
||||
// PrivateChannel gets a private channel by ID.
|
||||
// This method is Deprecated, use Channel(channelID)
|
||||
func (s *State) PrivateChannel(channelID string) (*Channel, error) {
|
||||
return s.Channel(channelID)
|
||||
}
|
||||
|
||||
// Channel gets a channel by ID, it will look in all guilds and private channels.
|
||||
func (s *State) Channel(channelID string) (*Channel, error) {
|
||||
if s == nil {
|
||||
@@ -921,9 +918,11 @@ func (s *State) onReady(se *Session, r *Ready) (err error) {
|
||||
// if state is disabled, store the bare essentials.
|
||||
if !se.StateEnabled {
|
||||
ready := Ready{
|
||||
Version: r.Version,
|
||||
SessionID: r.SessionID,
|
||||
User: r.User,
|
||||
Version: r.Version,
|
||||
SessionID: r.SessionID,
|
||||
User: r.User,
|
||||
Shard: r.Shard,
|
||||
Application: r.Application,
|
||||
}
|
||||
|
||||
s.Ready = ready
|
||||
@@ -993,6 +992,13 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
|
||||
}
|
||||
case *GuildMemberUpdate:
|
||||
if s.TrackMembers {
|
||||
var old *Member
|
||||
old, err = s.Member(t.GuildID, t.User.ID)
|
||||
if err == nil {
|
||||
oldCopy := *old
|
||||
t.BeforeUpdate = &oldCopy
|
||||
}
|
||||
|
||||
err = s.MemberAdd(t.Member)
|
||||
}
|
||||
case *GuildMemberRemove:
|
||||
@@ -1035,7 +1041,14 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
|
||||
}
|
||||
case *GuildEmojisUpdate:
|
||||
if s.TrackEmojis {
|
||||
err = s.EmojisAdd(t.GuildID, t.Emojis)
|
||||
var guild *Guild
|
||||
guild, err = s.Guild(t.GuildID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
guild.Emojis = t.Emojis
|
||||
}
|
||||
case *ChannelCreate:
|
||||
if s.TrackChannels {
|
||||
|
699
vendor/github.com/bwmarrin/discordgo/structs.go
generated
vendored
699
vendor/github.com/bwmarrin/discordgo/structs.go
generated
vendored
@@ -17,7 +17,6 @@ import (
|
||||
"math"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -59,12 +58,12 @@ type Session struct {
|
||||
ShardCount int
|
||||
|
||||
// Should state tracking be enabled.
|
||||
// State tracking is the best way for getting the the users
|
||||
// State tracking is the best way for getting the users
|
||||
// active guilds and the members of the guilds.
|
||||
StateEnabled bool
|
||||
|
||||
// Whether or not to call event handlers synchronously.
|
||||
// e.g false = launch event handlers in their own goroutines.
|
||||
// e.g. false = launch event handlers in their own goroutines.
|
||||
SyncEvents bool
|
||||
|
||||
// Exposed but should not be modified by User.
|
||||
@@ -75,7 +74,7 @@ type Session struct {
|
||||
// Max number of REST API retries
|
||||
MaxRestRetries int
|
||||
|
||||
// Status stores the currect status of the websocket connection
|
||||
// Status stores the current status of the websocket connection
|
||||
// this is being tested, may stay, may go away.
|
||||
status int32
|
||||
|
||||
@@ -95,6 +94,9 @@ type Session struct {
|
||||
// The http client used for REST requests
|
||||
Client *http.Client
|
||||
|
||||
// The dialer used for WebSocket connection
|
||||
Dialer *websocket.Dialer
|
||||
|
||||
// The user agent used for REST APIs
|
||||
UserAgent string
|
||||
|
||||
@@ -153,6 +155,38 @@ type Application struct {
|
||||
Flags int `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationRoleConnectionMetadataType represents the type of application role connection metadata.
|
||||
type ApplicationRoleConnectionMetadataType int
|
||||
|
||||
// Application role connection metadata types.
|
||||
const (
|
||||
ApplicationRoleConnectionMetadataIntegerLessThanOrEqual ApplicationRoleConnectionMetadataType = 1
|
||||
ApplicationRoleConnectionMetadataIntegerGreaterThanOrEqual ApplicationRoleConnectionMetadataType = 2
|
||||
ApplicationRoleConnectionMetadataIntegerEqual ApplicationRoleConnectionMetadataType = 3
|
||||
ApplicationRoleConnectionMetadataIntegerNotEqual ApplicationRoleConnectionMetadataType = 4
|
||||
ApplicationRoleConnectionMetadataDatetimeLessThanOrEqual ApplicationRoleConnectionMetadataType = 5
|
||||
ApplicationRoleConnectionMetadataDatetimeGreaterThanOrEqual ApplicationRoleConnectionMetadataType = 6
|
||||
ApplicationRoleConnectionMetadataBooleanEqual ApplicationRoleConnectionMetadataType = 7
|
||||
ApplicationRoleConnectionMetadataBooleanNotEqual ApplicationRoleConnectionMetadataType = 8
|
||||
)
|
||||
|
||||
// ApplicationRoleConnectionMetadata stores application role connection metadata.
|
||||
type ApplicationRoleConnectionMetadata struct {
|
||||
Type ApplicationRoleConnectionMetadataType `json:"type"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
NameLocalizations map[Locale]string `json:"name_localizations"`
|
||||
Description string `json:"description"`
|
||||
DescriptionLocalizations map[Locale]string `json:"description_localizations"`
|
||||
}
|
||||
|
||||
// ApplicationRoleConnection represents the role connection that an application has attached to a user.
|
||||
type ApplicationRoleConnection struct {
|
||||
PlatformName string `json:"platform_name"`
|
||||
PlatformUsername string `json:"platform_username"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
|
||||
// UserConnection is a Connection returned from the UserConnections endpoint
|
||||
type UserConnection struct {
|
||||
ID string `json:"id"`
|
||||
@@ -197,23 +231,8 @@ type IntegrationAccount struct {
|
||||
|
||||
// A VoiceRegion stores data for a specific voice region server.
|
||||
type VoiceRegion struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"sample_hostname"`
|
||||
Port int `json:"sample_port"`
|
||||
}
|
||||
|
||||
// A VoiceICE stores data for voice ICE servers.
|
||||
type VoiceICE struct {
|
||||
TTL string `json:"ttl"`
|
||||
Servers []*ICEServer `json:"servers"`
|
||||
}
|
||||
|
||||
// A ICEServer stores data for a specific voice ICE server.
|
||||
type ICEServer struct {
|
||||
URL string `json:"url"`
|
||||
Username string `json:"username"`
|
||||
Credential string `json:"credential"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// InviteTargetType indicates the type of target of an invite
|
||||
@@ -222,8 +241,8 @@ type InviteTargetType uint8
|
||||
|
||||
// Invite target types
|
||||
const (
|
||||
InviteTargetStream InviteTargetType = 1
|
||||
InviteTargetEmbeddedAppliction InviteTargetType = 2
|
||||
InviteTargetStream InviteTargetType = 1
|
||||
InviteTargetEmbeddedApplication InviteTargetType = 2
|
||||
)
|
||||
|
||||
// A Invite stores all data related to a specific Discord Guild or Channel invite.
|
||||
@@ -246,6 +265,8 @@ type Invite struct {
|
||||
// will only be filled when using InviteWithCounts
|
||||
ApproximatePresenceCount int `json:"approximate_presence_count"`
|
||||
ApproximateMemberCount int `json:"approximate_member_count"`
|
||||
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// ChannelType is the type of a Channel
|
||||
@@ -264,6 +285,42 @@ const (
|
||||
ChannelTypeGuildPublicThread ChannelType = 11
|
||||
ChannelTypeGuildPrivateThread ChannelType = 12
|
||||
ChannelTypeGuildStageVoice ChannelType = 13
|
||||
ChannelTypeGuildForum ChannelType = 15
|
||||
)
|
||||
|
||||
// ChannelFlags represent flags of a channel/thread.
|
||||
type ChannelFlags int
|
||||
|
||||
// Block containing known ChannelFlags values.
|
||||
const (
|
||||
// ChannelFlagPinned indicates whether the thread is pinned in the forum channel.
|
||||
// NOTE: forum threads only.
|
||||
ChannelFlagPinned ChannelFlags = 1 << 1
|
||||
// ChannelFlagRequireTag indicates whether a tag is required to be specified when creating a thread.
|
||||
// NOTE: forum channels only.
|
||||
ChannelFlagRequireTag ChannelFlags = 1 << 4
|
||||
)
|
||||
|
||||
// ForumSortOrderType represents sort order of a forum channel.
|
||||
type ForumSortOrderType int
|
||||
|
||||
const (
|
||||
// ForumSortOrderLatestActivity sorts posts by activity.
|
||||
ForumSortOrderLatestActivity ForumSortOrderType = 0
|
||||
// ForumSortOrderCreationDate sorts posts by creation time (from most recent to oldest).
|
||||
ForumSortOrderCreationDate ForumSortOrderType = 1
|
||||
)
|
||||
|
||||
// ForumLayout represents layout of a forum channel.
|
||||
type ForumLayout int
|
||||
|
||||
const (
|
||||
// ForumLayoutNotSet represents no default layout.
|
||||
ForumLayoutNotSet ForumLayout = 0
|
||||
// ForumLayoutListView displays forum posts as a list.
|
||||
ForumLayoutListView ForumLayout = 1
|
||||
// ForumLayoutGalleryView displays forum posts as a collection of tiles.
|
||||
ForumLayoutGalleryView ForumLayout = 2
|
||||
)
|
||||
|
||||
// A Channel holds all data related to an individual Discord channel.
|
||||
@@ -342,6 +399,30 @@ type Channel struct {
|
||||
|
||||
// All thread members. State channels only.
|
||||
Members []*ThreadMember `json:"-"`
|
||||
|
||||
// Channel flags.
|
||||
Flags ChannelFlags `json:"flags"`
|
||||
|
||||
// The set of tags that can be used in a forum channel.
|
||||
AvailableTags []ForumTag `json:"available_tags"`
|
||||
|
||||
// The IDs of the set of tags that have been applied to a thread in a forum channel.
|
||||
AppliedTags []string `json:"applied_tags"`
|
||||
|
||||
// Emoji to use as the default reaction to a forum post.
|
||||
DefaultReactionEmoji ForumDefaultReaction `json:"default_reaction_emoji"`
|
||||
|
||||
// The initial RateLimitPerUser to set on newly created threads in a channel.
|
||||
// This field is copied to the thread at creation time and does not live update.
|
||||
DefaultThreadRateLimitPerUser int `json:"default_thread_rate_limit_per_user"`
|
||||
|
||||
// The default sort order type used to order posts in forum channels.
|
||||
// Defaults to null, which indicates a preferred sort order hasn't been set by a channel admin.
|
||||
DefaultSortOrder *ForumSortOrderType `json:"default_sort_order"`
|
||||
|
||||
// The default forum layout view used to display posts in forum channels.
|
||||
// Defaults to ForumLayoutNotSet, which indicates a layout view has not been set by a channel admin.
|
||||
DefaultForumLayout ForumLayout `json:"default_forum_layout"`
|
||||
}
|
||||
|
||||
// Mention returns a string which mentions the channel
|
||||
@@ -356,22 +437,34 @@ func (c *Channel) IsThread() bool {
|
||||
|
||||
// A ChannelEdit holds Channel Field data for a channel edit.
|
||||
type ChannelEdit struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Topic string `json:"topic,omitempty"`
|
||||
NSFW bool `json:"nsfw,omitempty"`
|
||||
Position int `json:"position"`
|
||||
Bitrate int `json:"bitrate,omitempty"`
|
||||
UserLimit int `json:"user_limit,omitempty"`
|
||||
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"`
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Topic string `json:"topic,omitempty"`
|
||||
NSFW *bool `json:"nsfw,omitempty"`
|
||||
Position int `json:"position"`
|
||||
Bitrate int `json:"bitrate,omitempty"`
|
||||
UserLimit int `json:"user_limit,omitempty"`
|
||||
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"`
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"`
|
||||
Flags *ChannelFlags `json:"flags,omitempty"`
|
||||
DefaultThreadRateLimitPerUser *int `json:"default_thread_rate_limit_per_user,omitempty"`
|
||||
|
||||
// NOTE: threads only
|
||||
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
AutoArchiveDuration int `json:"auto_archive_duration,omitempty"`
|
||||
Locked bool `json:"locked,bool"`
|
||||
Invitable bool `json:"invitable,omitempty"`
|
||||
Archived *bool `json:"archived,omitempty"`
|
||||
AutoArchiveDuration int `json:"auto_archive_duration,omitempty"`
|
||||
Locked *bool `json:"locked,omitempty"`
|
||||
Invitable *bool `json:"invitable,omitempty"`
|
||||
|
||||
// NOTE: forum channels only
|
||||
|
||||
AvailableTags *[]ForumTag `json:"available_tags,omitempty"`
|
||||
DefaultReactionEmoji *ForumDefaultReaction `json:"default_reaction_emoji,omitempty"`
|
||||
DefaultSortOrder *ForumSortOrderType `json:"default_sort_order,omitempty"` // TODO: null
|
||||
DefaultForumLayout *ForumLayout `json:"default_forum_layout,omitempty"`
|
||||
|
||||
// NOTE: forum threads only
|
||||
AppliedTags *[]string `json:"applied_tags,omitempty"`
|
||||
}
|
||||
|
||||
// A ChannelFollow holds data returned after following a news channel
|
||||
@@ -405,6 +498,9 @@ type ThreadStart struct {
|
||||
Type ChannelType `json:"type,omitempty"`
|
||||
Invitable bool `json:"invitable"`
|
||||
RateLimitPerUser int `json:"rate_limit_per_user,omitempty"`
|
||||
|
||||
// NOTE: forum threads only
|
||||
AppliedTags []string `json:"applied_tags,omitempty"`
|
||||
}
|
||||
|
||||
// ThreadMetadata contains a number of thread-specific channel fields that are not needed by other channel types.
|
||||
@@ -448,6 +544,24 @@ type AddedThreadMember struct {
|
||||
Presence *Presence `json:"presence"`
|
||||
}
|
||||
|
||||
// ForumDefaultReaction specifies emoji to use as the default reaction to a forum post.
|
||||
// NOTE: Exactly one of EmojiID and EmojiName must be set.
|
||||
type ForumDefaultReaction struct {
|
||||
// The id of a guild's custom emoji.
|
||||
EmojiID string `json:"emoji_id,omitempty"`
|
||||
// The unicode character of the emoji.
|
||||
EmojiName string `json:"emoji_name,omitempty"`
|
||||
}
|
||||
|
||||
// ForumTag represents a tag that is able to be applied to a thread in a forum channel.
|
||||
type ForumTag struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Moderated bool `json:"moderated"`
|
||||
EmojiID string `json:"emoji_id,omitempty"`
|
||||
EmojiName string `json:"emoji_name,omitempty"`
|
||||
}
|
||||
|
||||
// Emoji struct holds data related to Emoji's
|
||||
type Emoji struct {
|
||||
ID string `json:"id"`
|
||||
@@ -462,7 +576,7 @@ type Emoji struct {
|
||||
|
||||
// EmojiRegex is the regex used to find and identify emojis in messages
|
||||
var (
|
||||
EmojiRegex = regexp.MustCompile(`<(a|):[A-z0-9_~]+:[0-9]{18}>`)
|
||||
EmojiRegex = regexp.MustCompile(`<(a|):[A-z0-9_~]+:[0-9]{18,20}>`)
|
||||
)
|
||||
|
||||
// MessageFormat returns a correctly formatted Emoji for use in Message content and embeds
|
||||
@@ -489,6 +603,17 @@ func (e *Emoji) APIName() string {
|
||||
return e.ID
|
||||
}
|
||||
|
||||
// EmojiParams represents parameters needed to create or update an Emoji.
|
||||
type EmojiParams struct {
|
||||
// Name of the emoji
|
||||
Name string `json:"name,omitempty"`
|
||||
// A base64 encoded emoji image, has to be smaller than 256KB.
|
||||
// NOTE: can be only set on creation.
|
||||
Image string `json:"image,omitempty"`
|
||||
// Roles for which this emoji will be available.
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
// StickerFormat is the file format of the Sticker.
|
||||
type StickerFormat int
|
||||
|
||||
@@ -694,7 +819,7 @@ type Guild struct {
|
||||
NSFWLevel GuildNSFWLevel `json:"nsfw_level"`
|
||||
|
||||
// The list of enabled guild features
|
||||
Features []string `json:"features"`
|
||||
Features []GuildFeature `json:"features"`
|
||||
|
||||
// Required MFA level for the guild
|
||||
MfaLevel MfaLevel `json:"mfa_level"`
|
||||
@@ -791,16 +916,11 @@ type GuildPreview struct {
|
||||
}
|
||||
|
||||
// IconURL returns a URL to the guild's icon.
|
||||
func (g *GuildPreview) IconURL() string {
|
||||
if g.Icon == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if strings.HasPrefix(g.Icon, "a_") {
|
||||
return EndpointGuildIconAnimated(g.ID, g.Icon)
|
||||
}
|
||||
|
||||
return EndpointGuildIcon(g.ID, g.Icon)
|
||||
//
|
||||
// size: The size of the desired icon image as a power of two
|
||||
// Image size can be any power of two between 16 and 4096.
|
||||
func (g *GuildPreview) IconURL(size string) string {
|
||||
return iconURL(g.Icon, EndpointGuildIcon(g.ID, g.Icon), EndpointGuildIconAnimated(g.ID, g.Icon), size)
|
||||
}
|
||||
|
||||
// GuildScheduledEvent is a representation of a scheduled event in a guild. Only for retrieval of the data.
|
||||
@@ -916,13 +1036,13 @@ type GuildScheduledEventStatus int
|
||||
|
||||
const (
|
||||
// GuildScheduledEventStatusScheduled represents the current event is in scheduled state
|
||||
GuildScheduledEventStatusScheduled = 1
|
||||
GuildScheduledEventStatusScheduled GuildScheduledEventStatus = 1
|
||||
// GuildScheduledEventStatusActive represents the current event is in active state
|
||||
GuildScheduledEventStatusActive = 2
|
||||
GuildScheduledEventStatusActive GuildScheduledEventStatus = 2
|
||||
// GuildScheduledEventStatusCompleted represents the current event is in completed state
|
||||
GuildScheduledEventStatusCompleted = 3
|
||||
GuildScheduledEventStatusCompleted GuildScheduledEventStatus = 3
|
||||
// GuildScheduledEventStatusCanceled represents the current event is in canceled state
|
||||
GuildScheduledEventStatusCanceled = 4
|
||||
GuildScheduledEventStatusCanceled GuildScheduledEventStatus = 4
|
||||
)
|
||||
|
||||
// GuildScheduledEventEntityType is the type of entity associated with a guild scheduled event.
|
||||
@@ -931,11 +1051,11 @@ type GuildScheduledEventEntityType int
|
||||
|
||||
const (
|
||||
// GuildScheduledEventEntityTypeStageInstance represents a stage channel
|
||||
GuildScheduledEventEntityTypeStageInstance = 1
|
||||
GuildScheduledEventEntityTypeStageInstance GuildScheduledEventEntityType = 1
|
||||
// GuildScheduledEventEntityTypeVoice represents a voice channel
|
||||
GuildScheduledEventEntityTypeVoice = 2
|
||||
GuildScheduledEventEntityTypeVoice GuildScheduledEventEntityType = 2
|
||||
// GuildScheduledEventEntityTypeExternal represents an external event
|
||||
GuildScheduledEventEntityTypeExternal = 3
|
||||
GuildScheduledEventEntityTypeExternal GuildScheduledEventEntityType = 3
|
||||
)
|
||||
|
||||
// GuildScheduledEventUser is a user subscribed to a scheduled event.
|
||||
@@ -946,19 +1066,19 @@ type GuildScheduledEventUser struct {
|
||||
Member *Member `json:"member"`
|
||||
}
|
||||
|
||||
// A GuildTemplate represents
|
||||
// A GuildTemplate represents a replicable template for guild creation
|
||||
type GuildTemplate struct {
|
||||
// The unique code for the guild template
|
||||
Code string `json:"code"`
|
||||
|
||||
// The name of the template
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// The description for the template
|
||||
Description string `json:"description"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
|
||||
// The number of times this template has been used
|
||||
UsageCount string `json:"usage_count"`
|
||||
UsageCount int `json:"usage_count"`
|
||||
|
||||
// The ID of the user who created the template
|
||||
CreatorID string `json:"creator_id"`
|
||||
@@ -982,6 +1102,14 @@ type GuildTemplate struct {
|
||||
IsDirty bool `json:"is_dirty"`
|
||||
}
|
||||
|
||||
// GuildTemplateParams stores the data needed to create or update a GuildTemplate.
|
||||
type GuildTemplateParams struct {
|
||||
// The name of the template (1-100 characters)
|
||||
Name string `json:"name,omitempty"`
|
||||
// The description of the template (0-120 characters)
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// MessageNotifications is the notification level for a guild
|
||||
// https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level
|
||||
type MessageNotifications int
|
||||
@@ -998,52 +1126,88 @@ type SystemChannelFlag int
|
||||
|
||||
// Block containing known SystemChannelFlag values
|
||||
const (
|
||||
SystemChannelFlagsSuppressJoin SystemChannelFlag = 1 << 0
|
||||
SystemChannelFlagsSuppressPremium SystemChannelFlag = 1 << 1
|
||||
SystemChannelFlagsSuppressJoinNotifications SystemChannelFlag = 1 << 0
|
||||
SystemChannelFlagsSuppressPremium SystemChannelFlag = 1 << 1
|
||||
SystemChannelFlagsSuppressGuildReminderNotifications SystemChannelFlag = 1 << 2
|
||||
SystemChannelFlagsSuppressJoinNotificationReplies SystemChannelFlag = 1 << 3
|
||||
)
|
||||
|
||||
// IconURL returns a URL to the guild's icon.
|
||||
func (g *Guild) IconURL() string {
|
||||
if g.Icon == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if strings.HasPrefix(g.Icon, "a_") {
|
||||
return EndpointGuildIconAnimated(g.ID, g.Icon)
|
||||
}
|
||||
|
||||
return EndpointGuildIcon(g.ID, g.Icon)
|
||||
//
|
||||
// size: The size of the desired icon image as a power of two
|
||||
// Image size can be any power of two between 16 and 4096.
|
||||
func (g *Guild) IconURL(size string) string {
|
||||
return iconURL(g.Icon, EndpointGuildIcon(g.ID, g.Icon), EndpointGuildIconAnimated(g.ID, g.Icon), size)
|
||||
}
|
||||
|
||||
// BannerURL returns a URL to the guild's banner.
|
||||
func (g *Guild) BannerURL() string {
|
||||
if g.Banner == "" {
|
||||
return ""
|
||||
}
|
||||
return EndpointGuildBanner(g.ID, g.Banner)
|
||||
//
|
||||
// size: The size of the desired banner image as a power of two
|
||||
// Image size can be any power of two between 16 and 4096.
|
||||
func (g *Guild) BannerURL(size string) string {
|
||||
return bannerURL(g.Banner, EndpointGuildBanner(g.ID, g.Banner), EndpointGuildBannerAnimated(g.ID, g.Banner), size)
|
||||
}
|
||||
|
||||
// A UserGuild holds a brief version of a Guild
|
||||
type UserGuild struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Owner bool `json:"owner"`
|
||||
Permissions int64 `json:"permissions,string"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Owner bool `json:"owner"`
|
||||
Permissions int64 `json:"permissions,string"`
|
||||
Features []GuildFeature `json:"features"`
|
||||
}
|
||||
|
||||
// GuildFeature indicates the presence of a feature in a guild
|
||||
type GuildFeature string
|
||||
|
||||
// Constants for GuildFeature
|
||||
const (
|
||||
GuildFeatureAnimatedBanner GuildFeature = "ANIMATED_BANNER"
|
||||
GuildFeatureAnimatedIcon GuildFeature = "ANIMATED_ICON"
|
||||
GuildFeatureAutoModeration GuildFeature = "AUTO_MODERATION"
|
||||
GuildFeatureBanner GuildFeature = "BANNER"
|
||||
GuildFeatureCommunity GuildFeature = "COMMUNITY"
|
||||
GuildFeatureDiscoverable GuildFeature = "DISCOVERABLE"
|
||||
GuildFeatureFeaturable GuildFeature = "FEATURABLE"
|
||||
GuildFeatureInviteSplash GuildFeature = "INVITE_SPLASH"
|
||||
GuildFeatureMemberVerificationGateEnabled GuildFeature = "MEMBER_VERIFICATION_GATE_ENABLED"
|
||||
GuildFeatureMonetizationEnabled GuildFeature = "MONETIZATION_ENABLED"
|
||||
GuildFeatureMoreStickers GuildFeature = "MORE_STICKERS"
|
||||
GuildFeatureNews GuildFeature = "NEWS"
|
||||
GuildFeaturePartnered GuildFeature = "PARTNERED"
|
||||
GuildFeaturePreviewEnabled GuildFeature = "PREVIEW_ENABLED"
|
||||
GuildFeaturePrivateThreads GuildFeature = "PRIVATE_THREADS"
|
||||
GuildFeatureRoleIcons GuildFeature = "ROLE_ICONS"
|
||||
GuildFeatureTicketedEventsEnabled GuildFeature = "TICKETED_EVENTS_ENABLED"
|
||||
GuildFeatureVanityURL GuildFeature = "VANITY_URL"
|
||||
GuildFeatureVerified GuildFeature = "VERIFIED"
|
||||
GuildFeatureVipRegions GuildFeature = "VIP_REGIONS"
|
||||
GuildFeatureWelcomeScreenEnabled GuildFeature = "WELCOME_SCREEN_ENABLED"
|
||||
)
|
||||
|
||||
// A GuildParams stores all the data needed to update discord guild settings
|
||||
type GuildParams struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
VerificationLevel *VerificationLevel `json:"verification_level,omitempty"`
|
||||
DefaultMessageNotifications int `json:"default_message_notifications,omitempty"` // TODO: Separate type?
|
||||
ExplicitContentFilter int `json:"explicit_content_filter,omitempty"`
|
||||
AfkChannelID string `json:"afk_channel_id,omitempty"`
|
||||
AfkTimeout int `json:"afk_timeout,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
OwnerID string `json:"owner_id,omitempty"`
|
||||
Splash string `json:"splash,omitempty"`
|
||||
DiscoverySplash string `json:"discovery_splash,omitempty"`
|
||||
Banner string `json:"banner,omitempty"`
|
||||
SystemChannelID string `json:"system_channel_id,omitempty"`
|
||||
SystemChannelFlags SystemChannelFlag `json:"system_channel_flags,omitempty"`
|
||||
RulesChannelID string `json:"rules_channel_id,omitempty"`
|
||||
PublicUpdatesChannelID string `json:"public_updates_channel_id,omitempty"`
|
||||
PreferredLocale Locale `json:"preferred_locale,omitempty"`
|
||||
Features []GuildFeature `json:"features,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
PremiumProgressBarEnabled *bool `json:"premium_progress_bar_enabled,omitempty"`
|
||||
}
|
||||
|
||||
// A Role stores information about Discord guild member roles.
|
||||
@@ -1081,6 +1245,20 @@ func (r *Role) Mention() string {
|
||||
return fmt.Sprintf("<@&%s>", r.ID)
|
||||
}
|
||||
|
||||
// RoleParams represents the parameters needed to create or update a Role
|
||||
type RoleParams struct {
|
||||
// The role's name
|
||||
Name string `json:"name,omitempty"`
|
||||
// The color the role should have (as a decimal, not hex)
|
||||
Color *int `json:"color,omitempty"`
|
||||
// Whether to display the role's users separately
|
||||
Hoist *bool `json:"hoist,omitempty"`
|
||||
// The overall permissions number of the role
|
||||
Permissions *int64 `json:"permissions,omitempty,string"`
|
||||
// Whether this role is mentionable
|
||||
Mentionable *bool `json:"mentionable,omitempty"`
|
||||
}
|
||||
|
||||
// Roles are a collection of Role
|
||||
type Roles []*Role
|
||||
|
||||
@@ -1098,23 +1276,28 @@ func (r Roles) Swap(i, j int) {
|
||||
|
||||
// A VoiceState stores the voice states of Guilds
|
||||
type VoiceState struct {
|
||||
UserID string `json:"user_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
GuildID string `json:"guild_id"`
|
||||
Suppress bool `json:"suppress"`
|
||||
SelfMute bool `json:"self_mute"`
|
||||
SelfDeaf bool `json:"self_deaf"`
|
||||
Mute bool `json:"mute"`
|
||||
Deaf bool `json:"deaf"`
|
||||
GuildID string `json:"guild_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Member *Member `json:"member"`
|
||||
SessionID string `json:"session_id"`
|
||||
Deaf bool `json:"deaf"`
|
||||
Mute bool `json:"mute"`
|
||||
SelfDeaf bool `json:"self_deaf"`
|
||||
SelfMute bool `json:"self_mute"`
|
||||
SelfStream bool `json:"self_stream"`
|
||||
SelfVideo bool `json:"self_video"`
|
||||
Suppress bool `json:"suppress"`
|
||||
RequestToSpeakTimestamp *time.Time `json:"request_to_speak_timestamp"`
|
||||
}
|
||||
|
||||
// A Presence stores the online, offline, or idle and game status of Guild members.
|
||||
type Presence struct {
|
||||
User *User `json:"user"`
|
||||
Status Status `json:"status"`
|
||||
Activities []*Activity `json:"activities"`
|
||||
Since *int `json:"since"`
|
||||
User *User `json:"user"`
|
||||
Status Status `json:"status"`
|
||||
Activities []*Activity `json:"activities"`
|
||||
Since *int `json:"since"`
|
||||
ClientStatus ClientStatus `json:"client_status"`
|
||||
}
|
||||
|
||||
// A TimeStamps struct contains start and end times used in the rich presence "playing .." Game
|
||||
@@ -1193,9 +1376,10 @@ func (m *Member) Mention() string {
|
||||
}
|
||||
|
||||
// AvatarURL returns the URL of the member's avatar
|
||||
// size: The size of the user's avatar as a power of two
|
||||
// if size is an empty string, no size parameter will
|
||||
// be added to the URL.
|
||||
//
|
||||
// size: The size of the user's avatar as a power of two
|
||||
// if size is an empty string, no size parameter will
|
||||
// be added to the URL.
|
||||
func (m *Member) AvatarURL(size string) string {
|
||||
if m.Avatar == "" {
|
||||
return m.User.AvatarURL(size)
|
||||
@@ -1206,23 +1390,11 @@ func (m *Member) AvatarURL(size string) string {
|
||||
|
||||
}
|
||||
|
||||
// A Settings stores data for a specific users Discord client settings.
|
||||
type Settings struct {
|
||||
RenderEmbeds bool `json:"render_embeds"`
|
||||
InlineEmbedMedia bool `json:"inline_embed_media"`
|
||||
InlineAttachmentMedia bool `json:"inline_attachment_media"`
|
||||
EnableTTSCommand bool `json:"enable_tts_command"`
|
||||
MessageDisplayCompact bool `json:"message_display_compact"`
|
||||
ShowCurrentGame bool `json:"show_current_game"`
|
||||
ConvertEmoticons bool `json:"convert_emoticons"`
|
||||
Locale string `json:"locale"`
|
||||
Theme string `json:"theme"`
|
||||
GuildPositions []string `json:"guild_positions"`
|
||||
RestrictedGuilds []string `json:"restricted_guilds"`
|
||||
FriendSourceFlags *FriendSourceFlags `json:"friend_source_flags"`
|
||||
Status Status `json:"status"`
|
||||
DetectPlatformAccounts bool `json:"detect_platform_accounts"`
|
||||
DeveloperMode bool `json:"developer_mode"`
|
||||
// ClientStatus stores the online, offline, idle, or dnd status of each device of a Guild member.
|
||||
type ClientStatus struct {
|
||||
Desktop Status `json:"desktop"`
|
||||
Mobile Status `json:"mobile"`
|
||||
Web Status `json:"web"`
|
||||
}
|
||||
|
||||
// Status type definition
|
||||
@@ -1237,20 +1409,6 @@ const (
|
||||
StatusOffline Status = "offline"
|
||||
)
|
||||
|
||||
// FriendSourceFlags stores ... TODO :)
|
||||
type FriendSourceFlags struct {
|
||||
All bool `json:"all"`
|
||||
MutualGuilds bool `json:"mutual_guilds"`
|
||||
MutualFriends bool `json:"mutual_friends"`
|
||||
}
|
||||
|
||||
// A Relationship between the logged in user and Relationship.User
|
||||
type Relationship struct {
|
||||
User *User `json:"user"`
|
||||
Type int `json:"type"` // 1 = friend, 2 = blocked, 3 = incoming friend req, 4 = sent friend req
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// A TooManyRequests struct holds information received from Discord
|
||||
// when receiving a HTTP 429 response.
|
||||
type TooManyRequests struct {
|
||||
@@ -1286,11 +1444,6 @@ type ReadState struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// An Ack is used to ack messages
|
||||
type Ack struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// A GuildRole stores data for guild roles.
|
||||
type GuildRole struct {
|
||||
Role *Role `json:"role"`
|
||||
@@ -1303,10 +1456,104 @@ type GuildBan struct {
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
// AutoModerationRule stores data for an auto moderation rule.
|
||||
type AutoModerationRule struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
GuildID string `json:"guild_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
CreatorID string `json:"creator_id,omitempty"`
|
||||
EventType AutoModerationRuleEventType `json:"event_type,omitempty"`
|
||||
TriggerType AutoModerationRuleTriggerType `json:"trigger_type,omitempty"`
|
||||
TriggerMetadata *AutoModerationTriggerMetadata `json:"trigger_metadata,omitempty"`
|
||||
Actions []AutoModerationAction `json:"actions,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
ExemptRoles *[]string `json:"exempt_roles,omitempty"`
|
||||
ExemptChannels *[]string `json:"exempt_channels,omitempty"`
|
||||
}
|
||||
|
||||
// AutoModerationRuleEventType indicates in what event context a rule should be checked.
|
||||
type AutoModerationRuleEventType int
|
||||
|
||||
// Auto moderation rule event types.
|
||||
const (
|
||||
// AutoModerationEventMessageSend is checked when a member sends or edits a message in the guild
|
||||
AutoModerationEventMessageSend AutoModerationRuleEventType = 1
|
||||
)
|
||||
|
||||
// AutoModerationRuleTriggerType represents the type of content which can trigger the rule.
|
||||
type AutoModerationRuleTriggerType int
|
||||
|
||||
// Auto moderation rule trigger types.
|
||||
const (
|
||||
AutoModerationEventTriggerKeyword AutoModerationRuleTriggerType = 1
|
||||
AutoModerationEventTriggerHarmfulLink AutoModerationRuleTriggerType = 2
|
||||
AutoModerationEventTriggerSpam AutoModerationRuleTriggerType = 3
|
||||
AutoModerationEventTriggerKeywordPreset AutoModerationRuleTriggerType = 4
|
||||
)
|
||||
|
||||
// AutoModerationKeywordPreset represents an internally pre-defined wordset.
|
||||
type AutoModerationKeywordPreset uint
|
||||
|
||||
// Auto moderation keyword presets.
|
||||
const (
|
||||
AutoModerationKeywordPresetProfanity AutoModerationKeywordPreset = 1
|
||||
AutoModerationKeywordPresetSexualContent AutoModerationKeywordPreset = 2
|
||||
AutoModerationKeywordPresetSlurs AutoModerationKeywordPreset = 3
|
||||
)
|
||||
|
||||
// AutoModerationTriggerMetadata represents additional metadata used to determine whether rule should be triggered.
|
||||
type AutoModerationTriggerMetadata struct {
|
||||
// Substrings which will be searched for in content.
|
||||
// NOTE: should be only used with keyword trigger type.
|
||||
KeywordFilter []string `json:"keyword_filter,omitempty"`
|
||||
// Regular expression patterns which will be matched against content (maximum of 10).
|
||||
// NOTE: should be only used with keyword trigger type.
|
||||
RegexPatterns []string `json:"regex_patterns,omitempty"`
|
||||
|
||||
// Internally pre-defined wordsets which will be searched for in content.
|
||||
// NOTE: should be only used with keyword preset trigger type.
|
||||
Presets []AutoModerationKeywordPreset `json:"presets,omitempty"`
|
||||
|
||||
// Substrings which should not trigger the rule.
|
||||
// NOTE: should be only used with keyword or keyword preset trigger type.
|
||||
AllowList *[]string `json:"allow_list,omitempty"`
|
||||
|
||||
// Total number of unique role and user mentions allowed per message.
|
||||
// NOTE: should be only used with mention spam trigger type.
|
||||
MentionTotalLimit int `json:"mention_total_limit,omitempty"`
|
||||
}
|
||||
|
||||
// AutoModerationActionType represents an action which will execute whenever a rule is triggered.
|
||||
type AutoModerationActionType int
|
||||
|
||||
// Auto moderation actions types.
|
||||
const (
|
||||
AutoModerationRuleActionBlockMessage AutoModerationActionType = 1
|
||||
AutoModerationRuleActionSendAlertMessage AutoModerationActionType = 2
|
||||
AutoModerationRuleActionTimeout AutoModerationActionType = 3
|
||||
)
|
||||
|
||||
// AutoModerationActionMetadata represents additional metadata needed during execution for a specific action type.
|
||||
type AutoModerationActionMetadata struct {
|
||||
// Channel to which user content should be logged.
|
||||
// NOTE: should be only used with send alert message action type.
|
||||
ChannelID string `json:"channel_id,omitempty"`
|
||||
|
||||
// Timeout duration in seconds (maximum of 2419200 - 4 weeks).
|
||||
// NOTE: should be only used with timeout action type.
|
||||
Duration int `json:"duration_seconds,omitempty"`
|
||||
}
|
||||
|
||||
// AutoModerationAction stores data for an auto moderation action.
|
||||
type AutoModerationAction struct {
|
||||
Type AutoModerationActionType `json:"type"`
|
||||
Metadata *AutoModerationActionMetadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// A GuildEmbed stores data for a guild embed.
|
||||
type GuildEmbed struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
ChannelID string `json:"channel_id,omitempty"`
|
||||
}
|
||||
|
||||
// A GuildAuditLog stores data for a guild audit log.
|
||||
@@ -1574,41 +1821,79 @@ const (
|
||||
AuditLogActionThreadCreate AuditLogAction = 110
|
||||
AuditLogActionThreadUpdate AuditLogAction = 111
|
||||
AuditLogActionThreadDelete AuditLogAction = 112
|
||||
|
||||
AuditLogActionApplicationCommandPermissionUpdate AuditLogAction = 121
|
||||
)
|
||||
|
||||
// A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings.
|
||||
type UserGuildSettingsChannelOverride struct {
|
||||
Muted bool `json:"muted"`
|
||||
MessageNotifications int `json:"message_notifications"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
}
|
||||
|
||||
// A UserGuildSettings stores data for a users guild settings.
|
||||
type UserGuildSettings struct {
|
||||
SupressEveryone bool `json:"suppress_everyone"`
|
||||
Muted bool `json:"muted"`
|
||||
MobilePush bool `json:"mobile_push"`
|
||||
MessageNotifications int `json:"message_notifications"`
|
||||
GuildID string `json:"guild_id"`
|
||||
ChannelOverrides []*UserGuildSettingsChannelOverride `json:"channel_overrides"`
|
||||
}
|
||||
|
||||
// A UserGuildSettingsEdit stores data for editing UserGuildSettings
|
||||
type UserGuildSettingsEdit struct {
|
||||
SupressEveryone bool `json:"suppress_everyone"`
|
||||
Muted bool `json:"muted"`
|
||||
MobilePush bool `json:"mobile_push"`
|
||||
MessageNotifications int `json:"message_notifications"`
|
||||
ChannelOverrides map[string]*UserGuildSettingsChannelOverride `json:"channel_overrides"`
|
||||
}
|
||||
|
||||
// GuildMemberParams stores data needed to update a member
|
||||
// https://discord.com/developers/docs/resources/guild#modify-guild-member
|
||||
type GuildMemberParams struct {
|
||||
// Value to set user's nickname to
|
||||
// Value to set user's nickname to.
|
||||
Nick string `json:"nick,omitempty"`
|
||||
// Array of role ids the member is assigned
|
||||
// Array of role ids the member is assigned.
|
||||
Roles *[]string `json:"roles,omitempty"`
|
||||
// ID of channel to move user to (if they are connected to voice).
|
||||
// Set to "" to remove user from a voice channel.
|
||||
ChannelID *string `json:"channel_id,omitempty"`
|
||||
// Whether the user is muted in voice channels.
|
||||
Mute *bool `json:"mute,omitempty"`
|
||||
// Whether the user is deafened in voice channels.
|
||||
Deaf *bool `json:"deaf,omitempty"`
|
||||
// When the user's timeout will expire and the user will be able
|
||||
// to communicate in the guild again (up to 28 days in the future).
|
||||
// Set to time.Time{} to remove timeout.
|
||||
CommunicationDisabledUntil *time.Time `json:"communication_disabled_until,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON is a helper function to marshal GuildMemberParams.
|
||||
func (p GuildMemberParams) MarshalJSON() (res []byte, err error) {
|
||||
type guildMemberParams GuildMemberParams
|
||||
v := struct {
|
||||
guildMemberParams
|
||||
ChannelID json.RawMessage `json:"channel_id,omitempty"`
|
||||
CommunicationDisabledUntil json.RawMessage `json:"communication_disabled_until,omitempty"`
|
||||
}{guildMemberParams: guildMemberParams(p)}
|
||||
|
||||
if p.ChannelID != nil {
|
||||
if *p.ChannelID == "" {
|
||||
v.ChannelID = json.RawMessage(`null`)
|
||||
} else {
|
||||
res, err = json.Marshal(p.ChannelID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
v.ChannelID = res
|
||||
}
|
||||
}
|
||||
|
||||
if p.CommunicationDisabledUntil != nil {
|
||||
if p.CommunicationDisabledUntil.IsZero() {
|
||||
v.CommunicationDisabledUntil = json.RawMessage(`null`)
|
||||
} else {
|
||||
res, err = json.Marshal(p.CommunicationDisabledUntil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
v.CommunicationDisabledUntil = res
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// GuildMemberAddParams stores data needed to add a user to a guild.
|
||||
// NOTE: All fields are optional, except AccessToken.
|
||||
type GuildMemberAddParams struct {
|
||||
// Valid access_token for the user.
|
||||
AccessToken string `json:"access_token"`
|
||||
// Value to set users nickname to.
|
||||
Nick string `json:"nick,omitempty"`
|
||||
// A list of role ID's to set on the member.
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
// Whether the user is muted.
|
||||
Mute bool `json:"mute,omitempty"`
|
||||
// Whether the user is deafened.
|
||||
Deaf bool `json:"deaf,omitempty"`
|
||||
}
|
||||
|
||||
// An APIErrorMessage is an api error message returned from discord
|
||||
@@ -1936,6 +2221,7 @@ const (
|
||||
ErrCodeUnknownGuildWelcomeScreen = 10069
|
||||
ErrCodeUnknownGuildScheduledEvent = 10070
|
||||
ErrCodeUnknownGuildScheduledEventUser = 10071
|
||||
ErrUnknownTag = 10087
|
||||
|
||||
ErrCodeBotsCannotUseEndpoint = 20001
|
||||
ErrCodeOnlyBotsCanUseEndpoint = 20002
|
||||
@@ -1949,28 +2235,30 @@ const (
|
||||
ErrCodeStageTopicContainsNotAllowedWordsForPublicStages = 20031
|
||||
ErrCodeGuildPremiumSubscriptionLevelTooLow = 20035
|
||||
|
||||
ErrCodeMaximumGuildsReached = 30001
|
||||
ErrCodeMaximumPinsReached = 30003
|
||||
ErrCodeMaximumNumberOfRecipientsReached = 30004
|
||||
ErrCodeMaximumGuildRolesReached = 30005
|
||||
ErrCodeMaximumNumberOfWebhooksReached = 30007
|
||||
ErrCodeMaximumNumberOfEmojisReached = 30008
|
||||
ErrCodeTooManyReactions = 30010
|
||||
ErrCodeMaximumNumberOfGuildChannelsReached = 30013
|
||||
ErrCodeMaximumNumberOfAttachmentsInAMessageReached = 30015
|
||||
ErrCodeMaximumNumberOfInvitesReached = 30016
|
||||
ErrCodeMaximumNumberOfAnimatedEmojisReached = 30018
|
||||
ErrCodeMaximumNumberOfServerMembersReached = 30019
|
||||
ErrCodeMaximumNumberOfGuildDiscoverySubcategoriesReached = 30030
|
||||
ErrCodeGuildAlreadyHasATemplate = 30031
|
||||
ErrCodeMaximumNumberOfThreadParticipantsReached = 30033
|
||||
ErrCodeMaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035
|
||||
ErrCodeMaximumNumberOfBansFetchesHasBeenReached = 30037
|
||||
ErrCodeMaximumNumberOfUncompletedGuildScheduledEventsReached = 30038
|
||||
ErrCodeMaximumNumberOfStickersReached = 30039
|
||||
ErrCodeMaximumNumberOfPruneRequestsHasBeenReached = 30040
|
||||
ErrCodeMaximumNumberOfGuildWidgetSettingsUpdatesHasBeenReached = 30042
|
||||
ErrCodeMaximumNumberOfEditsToMessagesOlderThanOneHourReached = 30046
|
||||
ErrCodeMaximumGuildsReached = 30001
|
||||
ErrCodeMaximumPinsReached = 30003
|
||||
ErrCodeMaximumNumberOfRecipientsReached = 30004
|
||||
ErrCodeMaximumGuildRolesReached = 30005
|
||||
ErrCodeMaximumNumberOfWebhooksReached = 30007
|
||||
ErrCodeMaximumNumberOfEmojisReached = 30008
|
||||
ErrCodeTooManyReactions = 30010
|
||||
ErrCodeMaximumNumberOfGuildChannelsReached = 30013
|
||||
ErrCodeMaximumNumberOfAttachmentsInAMessageReached = 30015
|
||||
ErrCodeMaximumNumberOfInvitesReached = 30016
|
||||
ErrCodeMaximumNumberOfAnimatedEmojisReached = 30018
|
||||
ErrCodeMaximumNumberOfServerMembersReached = 30019
|
||||
ErrCodeMaximumNumberOfGuildDiscoverySubcategoriesReached = 30030
|
||||
ErrCodeGuildAlreadyHasATemplate = 30031
|
||||
ErrCodeMaximumNumberOfThreadParticipantsReached = 30033
|
||||
ErrCodeMaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035
|
||||
ErrCodeMaximumNumberOfBansFetchesHasBeenReached = 30037
|
||||
ErrCodeMaximumNumberOfUncompletedGuildScheduledEventsReached = 30038
|
||||
ErrCodeMaximumNumberOfStickersReached = 30039
|
||||
ErrCodeMaximumNumberOfPruneRequestsHasBeenReached = 30040
|
||||
ErrCodeMaximumNumberOfGuildWidgetSettingsUpdatesHasBeenReached = 30042
|
||||
ErrCodeMaximumNumberOfEditsToMessagesOlderThanOneHourReached = 30046
|
||||
ErrCodeMaximumNumberOfPinnedThreadsInForumChannelHasBeenReached = 30047
|
||||
ErrCodeMaximumNumberOfTagsInForumChannelHasBeenReached = 30048
|
||||
|
||||
ErrCodeUnauthorized = 40001
|
||||
ErrCodeActionRequiredVerifiedAccount = 40002
|
||||
@@ -1983,6 +2271,7 @@ const (
|
||||
ErrCodeMessageAlreadyCrossposted = 40033
|
||||
ErrCodeAnApplicationWithThatNameAlreadyExists = 40041
|
||||
ErrCodeInteractionHasAlreadyBeenAcknowledged = 40060
|
||||
ErrCodeTagNamesMustBeUnique = 40061
|
||||
|
||||
ErrCodeMissingAccess = 50001
|
||||
ErrCodeInvalidAccountType = 50002
|
||||
@@ -2059,23 +2348,25 @@ type Intent int
|
||||
|
||||
// Constants for the different bit offsets of intents
|
||||
const (
|
||||
IntentGuilds Intent = 1 << 0
|
||||
IntentGuildMembers Intent = 1 << 1
|
||||
IntentGuildBans Intent = 1 << 2
|
||||
IntentGuildEmojis Intent = 1 << 3
|
||||
IntentGuildIntegrations Intent = 1 << 4
|
||||
IntentGuildWebhooks Intent = 1 << 5
|
||||
IntentGuildInvites Intent = 1 << 6
|
||||
IntentGuildVoiceStates Intent = 1 << 7
|
||||
IntentGuildPresences Intent = 1 << 8
|
||||
IntentGuildMessages Intent = 1 << 9
|
||||
IntentGuildMessageReactions Intent = 1 << 10
|
||||
IntentGuildMessageTyping Intent = 1 << 11
|
||||
IntentDirectMessages Intent = 1 << 12
|
||||
IntentDirectMessageReactions Intent = 1 << 13
|
||||
IntentDirectMessageTyping Intent = 1 << 14
|
||||
IntentMessageContent Intent = 1 << 15
|
||||
IntentGuildScheduledEvents Intent = 1 << 16
|
||||
IntentGuilds Intent = 1 << 0
|
||||
IntentGuildMembers Intent = 1 << 1
|
||||
IntentGuildBans Intent = 1 << 2
|
||||
IntentGuildEmojis Intent = 1 << 3
|
||||
IntentGuildIntegrations Intent = 1 << 4
|
||||
IntentGuildWebhooks Intent = 1 << 5
|
||||
IntentGuildInvites Intent = 1 << 6
|
||||
IntentGuildVoiceStates Intent = 1 << 7
|
||||
IntentGuildPresences Intent = 1 << 8
|
||||
IntentGuildMessages Intent = 1 << 9
|
||||
IntentGuildMessageReactions Intent = 1 << 10
|
||||
IntentGuildMessageTyping Intent = 1 << 11
|
||||
IntentDirectMessages Intent = 1 << 12
|
||||
IntentDirectMessageReactions Intent = 1 << 13
|
||||
IntentDirectMessageTyping Intent = 1 << 14
|
||||
IntentMessageContent Intent = 1 << 15
|
||||
IntentGuildScheduledEvents Intent = 1 << 16
|
||||
IntentAutoModerationConfiguration Intent = 1 << 20
|
||||
IntentAutoModerationExecution Intent = 1 << 21
|
||||
|
||||
// TODO: remove when compatibility is not needed
|
||||
|
||||
@@ -2110,7 +2401,9 @@ const (
|
||||
IntentDirectMessages |
|
||||
IntentDirectMessageReactions |
|
||||
IntentDirectMessageTyping |
|
||||
IntentGuildScheduledEvents
|
||||
IntentGuildScheduledEvents |
|
||||
IntentAutoModerationConfiguration |
|
||||
IntentAutoModerationExecution
|
||||
|
||||
IntentsAll = IntentsAllWithoutPrivileged |
|
||||
IntentGuildMembers |
|
||||
|
18
vendor/github.com/bwmarrin/discordgo/util.go
generated
vendored
18
vendor/github.com/bwmarrin/discordgo/util.go
generated
vendored
@@ -51,7 +51,7 @@ func MultipartBodyWithJSON(data interface{}, files []*File) (requestContentType
|
||||
|
||||
for i, file := range files {
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file%d"; filename="%s"`, i, quoteEscaper.Replace(file.Name)))
|
||||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="files[%d]"; filename="%s"`, i, quoteEscaper.Replace(file.Name)))
|
||||
contentType := file.ContentType
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
@@ -107,3 +107,19 @@ func bannerURL(bannerHash, staticBannerURL, animatedBannerURL, size string) stri
|
||||
}
|
||||
return URL
|
||||
}
|
||||
|
||||
func iconURL(iconHash, staticIconURL, animatedIconURL, size string) string {
|
||||
var URL string
|
||||
if iconHash == "" {
|
||||
return ""
|
||||
} else if strings.HasPrefix(iconHash, "a_") {
|
||||
URL = animatedIconURL
|
||||
} else {
|
||||
URL = staticIconURL
|
||||
}
|
||||
|
||||
if size != "" {
|
||||
return URL + "?size=" + size
|
||||
}
|
||||
return URL
|
||||
}
|
||||
|
40
vendor/github.com/bwmarrin/discordgo/voice.go
generated
vendored
40
vendor/github.com/bwmarrin/discordgo/voice.go
generated
vendored
@@ -120,9 +120,9 @@ func (v *VoiceConnection) ChangeChannel(channelID string, mute, deaf bool) (err
|
||||
v.log(LogInformational, "called")
|
||||
|
||||
data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, mute, deaf}}
|
||||
v.wsMutex.Lock()
|
||||
v.session.wsMutex.Lock()
|
||||
err = v.session.wsConn.WriteJSON(data)
|
||||
v.wsMutex.Unlock()
|
||||
v.session.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -304,7 +304,7 @@ func (v *VoiceConnection) open() (err error) {
|
||||
// Connect to VoiceConnection Websocket
|
||||
vg := "wss://" + strings.TrimSuffix(v.endpoint, ":80")
|
||||
v.log(LogInformational, "connecting to voice endpoint %s", vg)
|
||||
v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil)
|
||||
v.wsConn, _, err = v.session.Dialer.Dial(vg, nil)
|
||||
if err != nil {
|
||||
v.log(LogWarning, "error connecting to voice endpoint %s, %s", vg, err)
|
||||
v.log(LogDebug, "voice struct: %#v\n", v)
|
||||
@@ -323,7 +323,9 @@ func (v *VoiceConnection) open() (err error) {
|
||||
}
|
||||
data := voiceHandshakeOp{0, voiceHandshakeData{v.GuildID, v.UserID, v.sessionID, v.token}}
|
||||
|
||||
v.wsMutex.Lock()
|
||||
err = v.wsConn.WriteJSON(data)
|
||||
v.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
v.log(LogWarning, "error sending init packet, %s", err)
|
||||
return
|
||||
@@ -358,6 +360,25 @@ func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}
|
||||
v.wsConn = nil
|
||||
v.Unlock()
|
||||
|
||||
// Wait for VOICE_SERVER_UPDATE.
|
||||
// When the bot is moved by the user to another voice channel,
|
||||
// VOICE_SERVER_UPDATE is received after the code 4014.
|
||||
for i := 0; i < 5; i++ { // TODO: temp, wait for VoiceServerUpdate.
|
||||
<-time.After(1 * time.Second)
|
||||
|
||||
v.RLock()
|
||||
reconnected := v.wsConn != nil
|
||||
v.RUnlock()
|
||||
if !reconnected {
|
||||
continue
|
||||
}
|
||||
v.log(LogInformational, "successfully reconnected after 4014 manual disconnection")
|
||||
return
|
||||
}
|
||||
|
||||
// When VOICE_SERVER_UPDATE is not received, disconnect as usual.
|
||||
v.log(LogInformational, "disconnect due to 4014 manual disconnection")
|
||||
|
||||
v.session.Lock()
|
||||
delete(v.session.VoiceConnections, v.GuildID)
|
||||
v.session.Unlock()
|
||||
@@ -829,7 +850,12 @@ func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct
|
||||
p.SSRC = binary.BigEndian.Uint32(recvbuf[8:12])
|
||||
// decrypt opus data
|
||||
copy(nonce[:], recvbuf[0:12])
|
||||
p.Opus, _ = secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey)
|
||||
|
||||
if opus, ok := secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey); ok {
|
||||
p.Opus = opus
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
// extension bit set, and not a RTCP packet
|
||||
if ((recvbuf[0] & 0x10) == 0x10) && ((recvbuf[1] & 0x80) == 0) {
|
||||
@@ -870,7 +896,11 @@ func (v *VoiceConnection) reconnect() {
|
||||
v.reconnecting = true
|
||||
v.Unlock()
|
||||
|
||||
defer func() { v.reconnecting = false }()
|
||||
defer func() {
|
||||
v.Lock()
|
||||
v.reconnecting = false
|
||||
v.Unlock()
|
||||
}()
|
||||
|
||||
// Close any currently open connections
|
||||
v.Close()
|
||||
|
11
vendor/github.com/bwmarrin/discordgo/webhook.go
generated
vendored
11
vendor/github.com/bwmarrin/discordgo/webhook.go
generated
vendored
@@ -35,15 +35,16 @@ type WebhookParams struct {
|
||||
Components []MessageComponent `json:"components"`
|
||||
Embeds []*MessageEmbed `json:"embeds,omitempty"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
// NOTE: Works only for followup messages.
|
||||
Flags uint64 `json:"flags,omitempty"`
|
||||
// Only MessageFlagsSuppressEmbeds and MessageFlagsEphemeral can be set.
|
||||
// MessageFlagsEphemeral can only be set when using Followup Message Create endpoint.
|
||||
Flags MessageFlags `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
// WebhookEdit stores data for editing of a webhook message.
|
||||
type WebhookEdit struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
Components []MessageComponent `json:"components"`
|
||||
Embeds []*MessageEmbed `json:"embeds,omitempty"`
|
||||
Content *string `json:"content,omitempty"`
|
||||
Components *[]MessageComponent `json:"components,omitempty"`
|
||||
Embeds *[]*MessageEmbed `json:"embeds,omitempty"`
|
||||
Files []*File `json:"-"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
}
|
||||
|
12
vendor/github.com/bwmarrin/discordgo/wsapi.go
generated
vendored
12
vendor/github.com/bwmarrin/discordgo/wsapi.go
generated
vendored
@@ -77,7 +77,7 @@ func (s *Session) Open() error {
|
||||
s.log(LogInformational, "connecting to gateway %s", s.gateway)
|
||||
header := http.Header{}
|
||||
header.Add("accept-encoding", "zlib")
|
||||
s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header)
|
||||
s.wsConn, _, err = s.Dialer.Dial(s.gateway, header)
|
||||
if err != nil {
|
||||
s.log(LogError, "error connecting to gateway %s, %s", s.gateway, err)
|
||||
s.gateway = "" // clear cached gateway
|
||||
@@ -320,7 +320,7 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateStatusData ia provided to UpdateStatusComplex()
|
||||
// UpdateStatusData is provided to UpdateStatusComplex()
|
||||
type UpdateStatusData struct {
|
||||
IdleSince *int `json:"since"`
|
||||
Activities []*Activity `json:"activities"`
|
||||
@@ -361,6 +361,14 @@ func (s *Session) UpdateGameStatus(idle int, name string) (err error) {
|
||||
return s.UpdateStatusComplex(*newUpdateStatusData(idle, ActivityTypeGame, name, ""))
|
||||
}
|
||||
|
||||
// UpdateWatchStatus is used to update the user's watch status.
|
||||
// If idle>0 then set status to idle.
|
||||
// If name!="" then set movie/stream.
|
||||
// if otherwise, set status to active, and no activity.
|
||||
func (s *Session) UpdateWatchStatus(idle int, name string) (err error) {
|
||||
return s.UpdateStatusComplex(*newUpdateStatusData(idle, ActivityTypeWatching, name, ""))
|
||||
}
|
||||
|
||||
// UpdateStreamingStatus is used to update the user's streaming status.
|
||||
// If idle>0 then set status to idle.
|
||||
// If name!="" then set game.
|
||||
|
2
vendor/github.com/d5/tengo/v2/README.md
generated
vendored
2
vendor/github.com/d5/tengo/v2/README.md
generated
vendored
@@ -144,7 +144,7 @@ fmt.Println(res) // "success"
|
||||
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
|
||||
- [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md)
|
||||
- [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md)
|
||||
- Syntax Highlighters: [VSCode](https://github.com/lissein/vscode-tengo), [Atom](https://github.com/d5/tengo-atom)
|
||||
- Syntax Highlighters: [VSCode](https://github.com/lissein/vscode-tengo), [Atom](https://github.com/d5/tengo-atom), [Vim](https://github.com/geseq/tengo-vim)
|
||||
- **Why the name Tengo?** It's from [1Q84](https://en.wikipedia.org/wiki/1Q84).
|
||||
|
||||
|
||||
|
40
vendor/github.com/d5/tengo/v2/compiler.go
generated
vendored
40
vendor/github.com/d5/tengo/v2/compiler.go
generated
vendored
@@ -141,25 +141,7 @@ func (c *Compiler) Compile(node parser.Node) error {
|
||||
if node.Token == token.LAnd || node.Token == token.LOr {
|
||||
return c.compileLogical(node)
|
||||
}
|
||||
if node.Token == token.Less {
|
||||
if err := c.Compile(node.RHS); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Compile(node.LHS); err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(node, parser.OpBinaryOp, int(token.Greater))
|
||||
return nil
|
||||
} else if node.Token == token.LessEq {
|
||||
if err := c.Compile(node.RHS); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Compile(node.LHS); err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(node, parser.OpBinaryOp, int(token.GreaterEq))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.Compile(node.LHS); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -182,6 +164,10 @@ func (c *Compiler) Compile(node parser.Node) error {
|
||||
c.emit(node, parser.OpBinaryOp, int(token.Greater))
|
||||
case token.GreaterEq:
|
||||
c.emit(node, parser.OpBinaryOp, int(token.GreaterEq))
|
||||
case token.Less:
|
||||
c.emit(node, parser.OpBinaryOp, int(token.Less))
|
||||
case token.LessEq:
|
||||
c.emit(node, parser.OpBinaryOp, int(token.LessEq))
|
||||
case token.Equal:
|
||||
c.emit(node, parser.OpEqual)
|
||||
case token.NotEqual:
|
||||
@@ -692,12 +678,15 @@ func (c *Compiler) compileAssign(
|
||||
return c.errorf(node, "operator ':=' not allowed with selector")
|
||||
}
|
||||
|
||||
_, isFunc := rhs[0].(*parser.FuncLit)
|
||||
symbol, depth, exists := c.symbolTable.Resolve(ident, false)
|
||||
if op == token.Define {
|
||||
if depth == 0 && exists {
|
||||
return c.errorf(node, "'%s' redeclared in this block", ident)
|
||||
}
|
||||
symbol = c.symbolTable.Define(ident)
|
||||
if isFunc {
|
||||
symbol = c.symbolTable.Define(ident)
|
||||
}
|
||||
} else {
|
||||
if !exists {
|
||||
return c.errorf(node, "unresolved reference '%s'", ident)
|
||||
@@ -718,6 +707,10 @@ func (c *Compiler) compileAssign(
|
||||
}
|
||||
}
|
||||
|
||||
if op == token.Define && !isFunc {
|
||||
symbol = c.symbolTable.Define(ident)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case token.AddAssign:
|
||||
c.emit(node, parser.OpBinaryOp, int(token.Add))
|
||||
@@ -1220,14 +1213,14 @@ func (c *Compiler) optimizeFunc(node parser.Node) {
|
||||
iterateInstructions(c.scopes[c.scopeIndex].Instructions,
|
||||
func(pos int, opcode parser.Opcode, operands []int) bool {
|
||||
switch {
|
||||
case dsts[pos]:
|
||||
dstIdx++
|
||||
deadCode = false
|
||||
case opcode == parser.OpReturn:
|
||||
if deadCode {
|
||||
return true
|
||||
}
|
||||
deadCode = true
|
||||
case dsts[pos]:
|
||||
dstIdx++
|
||||
deadCode = false
|
||||
case deadCode:
|
||||
return true
|
||||
}
|
||||
@@ -1242,6 +1235,7 @@ func (c *Compiler) optimizeFunc(node parser.Node) {
|
||||
var appendReturn bool
|
||||
endPos := len(c.scopes[c.scopeIndex].Instructions)
|
||||
newEndPost := len(newInsts)
|
||||
|
||||
iterateInstructions(newInsts,
|
||||
func(pos int, opcode parser.Opcode, operands []int) bool {
|
||||
switch opcode {
|
||||
|
18
vendor/github.com/d5/tengo/v2/parser/parser.go
generated
vendored
18
vendor/github.com/d5/tengo/v2/parser/parser.go
generated
vendored
@@ -375,7 +375,12 @@ func (p *Parser) parseOperand() Expr {
|
||||
case token.Ident:
|
||||
return p.parseIdent()
|
||||
case token.Int:
|
||||
v, _ := strconv.ParseInt(p.tokenLit, 10, 64)
|
||||
v, err := strconv.ParseInt(p.tokenLit, 0, 64)
|
||||
if err == strconv.ErrRange {
|
||||
p.error(p.pos, "number out of range")
|
||||
} else if err != nil {
|
||||
p.error(p.pos, "invalid integer")
|
||||
}
|
||||
x := &IntLit{
|
||||
Value: v,
|
||||
ValuePos: p.pos,
|
||||
@@ -383,8 +388,14 @@ func (p *Parser) parseOperand() Expr {
|
||||
}
|
||||
p.next()
|
||||
return x
|
||||
|
||||
case token.Float:
|
||||
v, _ := strconv.ParseFloat(p.tokenLit, 64)
|
||||
v, err := strconv.ParseFloat(p.tokenLit, 64)
|
||||
if err == strconv.ErrRange {
|
||||
p.error(p.pos, "number out of range")
|
||||
} else if err != nil {
|
||||
p.error(p.pos, "invalid float")
|
||||
}
|
||||
x := &FloatLit{
|
||||
Value: v,
|
||||
ValuePos: p.pos,
|
||||
@@ -447,10 +458,11 @@ func (p *Parser) parseOperand() Expr {
|
||||
return p.parseErrorExpr()
|
||||
case token.Immutable: // immutable expression
|
||||
return p.parseImmutableExpr()
|
||||
default:
|
||||
p.errorExpected(p.pos, "operand")
|
||||
}
|
||||
|
||||
pos := p.pos
|
||||
p.errorExpected(pos, "operand")
|
||||
p.advance(stmtStart)
|
||||
return &BadExpr{From: pos, To: p.pos}
|
||||
}
|
||||
|
113
vendor/github.com/d5/tengo/v2/parser/scanner.go
generated
vendored
113
vendor/github.com/d5/tengo/v2/parser/scanner.go
generated
vendored
@@ -93,9 +93,9 @@ func (s *Scanner) Scan() (
|
||||
token.Export, token.True, token.False, token.Undefined:
|
||||
insertSemi = true
|
||||
}
|
||||
case '0' <= ch && ch <= '9':
|
||||
case ('0' <= ch && ch <= '9') || (ch == '.' && '0' <= s.peek() && s.peek() <= '9'):
|
||||
insertSemi = true
|
||||
tok, literal = s.scanNumber(false)
|
||||
tok, literal = s.scanNumber()
|
||||
default:
|
||||
s.next() // always make progress
|
||||
|
||||
@@ -125,16 +125,11 @@ func (s *Scanner) Scan() (
|
||||
case ':':
|
||||
tok = s.switch2(token.Colon, token.Define)
|
||||
case '.':
|
||||
if '0' <= s.ch && s.ch <= '9' {
|
||||
insertSemi = true
|
||||
tok, literal = s.scanNumber(true)
|
||||
} else {
|
||||
tok = token.Period
|
||||
if s.ch == '.' && s.peek() == '.' {
|
||||
s.next()
|
||||
s.next() // consume last '.'
|
||||
tok = token.Ellipsis
|
||||
}
|
||||
tok = token.Period
|
||||
if s.ch == '.' && s.peek() == '.' {
|
||||
s.next()
|
||||
s.next() // consume last '.'
|
||||
tok = token.Ellipsis
|
||||
}
|
||||
case ',':
|
||||
tok = token.Comma
|
||||
@@ -379,86 +374,58 @@ func (s *Scanner) scanIdentifier() string {
|
||||
return string(s.src[offs:s.offset])
|
||||
}
|
||||
|
||||
func (s *Scanner) scanMantissa(base int) {
|
||||
for digitVal(s.ch) < base {
|
||||
func (s *Scanner) scanDigits(base int) {
|
||||
for s.ch == '_' || digitVal(s.ch) < base {
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) scanNumber(
|
||||
seenDecimalPoint bool,
|
||||
) (tok token.Token, lit string) {
|
||||
// digitVal(s.ch) < 10
|
||||
func (s *Scanner) scanNumber() (token.Token, string) {
|
||||
offs := s.offset
|
||||
tok = token.Int
|
||||
tok := token.Int
|
||||
base := 10
|
||||
|
||||
defer func() {
|
||||
lit = string(s.src[offs:s.offset])
|
||||
}()
|
||||
|
||||
if seenDecimalPoint {
|
||||
offs--
|
||||
tok = token.Float
|
||||
s.scanMantissa(10)
|
||||
goto exponent
|
||||
}
|
||||
|
||||
if s.ch == '0' {
|
||||
// int or float
|
||||
offs := s.offset
|
||||
// Determine base
|
||||
switch {
|
||||
case s.ch == '0' && lower(s.peek()) == 'b':
|
||||
base = 2
|
||||
s.next()
|
||||
s.next()
|
||||
case s.ch == '0' && lower(s.peek()) == 'o':
|
||||
base = 8
|
||||
s.next()
|
||||
s.next()
|
||||
case s.ch == '0' && lower(s.peek()) == 'x':
|
||||
base = 16
|
||||
s.next()
|
||||
s.next()
|
||||
if s.ch == 'x' || s.ch == 'X' {
|
||||
// hexadecimal int
|
||||
s.next()
|
||||
s.scanMantissa(16)
|
||||
if s.offset-offs <= 2 {
|
||||
// only scanned "0x" or "0X"
|
||||
s.error(offs, "illegal hexadecimal number")
|
||||
}
|
||||
} else {
|
||||
// octal int or float
|
||||
seenDecimalDigit := false
|
||||
s.scanMantissa(8)
|
||||
if s.ch == '8' || s.ch == '9' {
|
||||
// illegal octal int or float
|
||||
seenDecimalDigit = true
|
||||
s.scanMantissa(10)
|
||||
}
|
||||
if s.ch == '.' || s.ch == 'e' || s.ch == 'E' || s.ch == 'i' {
|
||||
goto fraction
|
||||
}
|
||||
// octal int
|
||||
if seenDecimalDigit {
|
||||
s.error(offs, "illegal octal number")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// decimal int or float
|
||||
s.scanMantissa(10)
|
||||
// Scan whole number
|
||||
s.scanDigits(base)
|
||||
|
||||
fraction:
|
||||
if s.ch == '.' {
|
||||
// Scan fractional part
|
||||
if s.ch == '.' && (base == 10 || base == 16) {
|
||||
tok = token.Float
|
||||
s.next()
|
||||
s.scanMantissa(10)
|
||||
s.scanDigits(base)
|
||||
}
|
||||
|
||||
exponent:
|
||||
if s.ch == 'e' || s.ch == 'E' {
|
||||
// Scan exponent
|
||||
if s.ch == 'e' || s.ch == 'E' || s.ch == 'p' || s.ch == 'P' {
|
||||
tok = token.Float
|
||||
s.next()
|
||||
if s.ch == '-' || s.ch == '+' {
|
||||
s.next()
|
||||
}
|
||||
if digitVal(s.ch) < 10 {
|
||||
s.scanMantissa(10)
|
||||
} else {
|
||||
s.error(offs, "illegal floating-point exponent")
|
||||
offs := s.offset
|
||||
s.scanDigits(10)
|
||||
if offs == s.offset {
|
||||
s.error(offs, "exponent has no digits")
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
return tok, string(s.src[offs:s.offset])
|
||||
}
|
||||
|
||||
func (s *Scanner) scanEscape(quote rune) bool {
|
||||
@@ -687,3 +654,7 @@ func digitVal(ch rune) int {
|
||||
}
|
||||
return 16 // larger than any legal digit val
|
||||
}
|
||||
|
||||
func lower(c byte) byte {
|
||||
return c | ('x' - 'X')
|
||||
}
|
||||
|
2
vendor/github.com/d5/tengo/v2/script.go
generated
vendored
2
vendor/github.com/d5/tengo/v2/script.go
generated
vendored
@@ -259,7 +259,7 @@ func (c *Compiled) Clone() *Compiled {
|
||||
// copy global objects
|
||||
for idx, g := range c.globals {
|
||||
if g != nil {
|
||||
clone.globals[idx] = g
|
||||
clone.globals[idx] = g.Copy()
|
||||
}
|
||||
}
|
||||
return clone
|
||||
|
21
vendor/github.com/d5/tengo/v2/vm.go
generated
vendored
21
vendor/github.com/d5/tengo/v2/vm.go
generated
vendored
@@ -376,8 +376,8 @@ func (v *VM) run() {
|
||||
|
||||
var lowIdx int64
|
||||
if low != UndefinedValue {
|
||||
if low, ok := low.(*Int); ok {
|
||||
lowIdx = low.Value
|
||||
if lowInt, ok := low.(*Int); ok {
|
||||
lowIdx = lowInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
low.TypeName())
|
||||
@@ -391,8 +391,8 @@ func (v *VM) run() {
|
||||
var highIdx int64
|
||||
if high == UndefinedValue {
|
||||
highIdx = numElements
|
||||
} else if high, ok := high.(*Int); ok {
|
||||
highIdx = high.Value
|
||||
} else if highInt, ok := high.(*Int); ok {
|
||||
highIdx = highInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
high.TypeName())
|
||||
@@ -428,8 +428,8 @@ func (v *VM) run() {
|
||||
var highIdx int64
|
||||
if high == UndefinedValue {
|
||||
highIdx = numElements
|
||||
} else if high, ok := high.(*Int); ok {
|
||||
highIdx = high.Value
|
||||
} else if highInt, ok := high.(*Int); ok {
|
||||
highIdx = highInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
high.TypeName())
|
||||
@@ -465,8 +465,8 @@ func (v *VM) run() {
|
||||
var highIdx int64
|
||||
if high == UndefinedValue {
|
||||
highIdx = numElements
|
||||
} else if high, ok := high.(*Int); ok {
|
||||
highIdx = high.Value
|
||||
} else if highInt, ok := high.(*Int); ok {
|
||||
highIdx = highInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
high.TypeName())
|
||||
@@ -502,8 +502,8 @@ func (v *VM) run() {
|
||||
var highIdx int64
|
||||
if high == UndefinedValue {
|
||||
highIdx = numElements
|
||||
} else if high, ok := high.(*Int); ok {
|
||||
highIdx = high.Value
|
||||
} else if highInt, ok := high.(*Int); ok {
|
||||
highIdx = highInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
high.TypeName())
|
||||
@@ -767,6 +767,7 @@ func (v *VM) run() {
|
||||
NumLocals: fn.NumLocals,
|
||||
NumParameters: fn.NumParameters,
|
||||
VarArgs: fn.VarArgs,
|
||||
SourceMap: fn.SourceMap,
|
||||
Free: free,
|
||||
}
|
||||
v.allocs--
|
||||
|
10
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
10
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
@@ -1,6 +1,6 @@
|
||||
# Setup a Global .gitignore for OS and editor generated files:
|
||||
# https://help.github.com/articles/ignoring-files
|
||||
# git config --global core.excludesfile ~/.gitignore_global
|
||||
# go test -c output
|
||||
*.test
|
||||
*.test.exe
|
||||
|
||||
.vagrant
|
||||
*.sublime-project
|
||||
# Output of go build ./cmd/fsnotify
|
||||
/fsnotify
|
||||
|
62
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
62
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
@@ -1,62 +0,0 @@
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# You can update this list using the following command:
|
||||
#
|
||||
# $ (head -n10 AUTHORS && git shortlog -se | sed -E 's/^\s+[0-9]+\t//') | tee AUTHORS
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Aaron L <aaron@bettercoder.net>
|
||||
Adrien Bustany <adrien@bustany.org>
|
||||
Alexey Kazakov <alkazako@redhat.com>
|
||||
Amit Krishnan <amit.krishnan@oracle.com>
|
||||
Anmol Sethi <me@anmol.io>
|
||||
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Bruno Bigras <bigras.bruno@gmail.com>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Case Nelson <case@teammating.com>
|
||||
Chris Howey <howeyc@gmail.com>
|
||||
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||
Daniel Wagner-Hall <dawagner@gmail.com>
|
||||
Dave Cheney <dave@cheney.net>
|
||||
Eric Lin <linxiulei@gmail.com>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Gautam Dey <gautam.dey77@gmail.com>
|
||||
Hari haran <hariharan.uno@gmail.com>
|
||||
Ichinose Shogo <shogo82148@gmail.com>
|
||||
Johannes Ebke <johannes@ebke.org>
|
||||
John C Barstow <jbowtie@amathaine.com>
|
||||
Kelvin Fo <vmirage@gmail.com>
|
||||
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
||||
Matt Layher <mdlayher@gmail.com>
|
||||
Matthias Stone <matthias@bellstone.ca>
|
||||
Nathan Youngman <git@nathany.com>
|
||||
Nickolai Zeldovich <nickolai@csail.mit.edu>
|
||||
Oliver Bristow <evilumbrella+github@gmail.com>
|
||||
Patrick <patrick@dropbox.com>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Pawel Knap <pawelknap88@gmail.com>
|
||||
Pieter Droogendijk <pieter@binky.org.uk>
|
||||
Pratik Shinde <pratikshinde320@gmail.com>
|
||||
Pursuit92 <JoshChase@techpursuit.net>
|
||||
Riku Voipio <riku.voipio@linaro.org>
|
||||
Rob Figueiredo <robfig@gmail.com>
|
||||
Rodrigo Chiossi <rodrigochiossi@gmail.com>
|
||||
Slawek Ligus <root@ooz.ie>
|
||||
Soge Zhang <zhssoge@gmail.com>
|
||||
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
||||
Tilak Sharma <tilaks@google.com>
|
||||
Tobias Klauser <tobias.klauser@gmail.com>
|
||||
Tom Payne <twpayne@gmail.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Tudor Golubenco <tudor.g@gmail.com>
|
||||
Vahe Khachikyan <vahe@live.ca>
|
||||
Yukang <moorekang@gmail.com>
|
||||
bronze1man <bronze1man@gmail.com>
|
||||
debrando <denis.brandolini@gmail.com>
|
||||
henrikedwards <henrik.edwards@gmail.com>
|
||||
铁哥 <guotie.9@gmail.com>
|
113
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
113
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
@@ -7,6 +7,95 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
Nothing yet.
|
||||
|
||||
## [1.6.0] - 2022-10-13
|
||||
|
||||
This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
|
||||
but not documented). It also increases the minimum Linux version to 2.6.32.
|
||||
|
||||
### Additions
|
||||
|
||||
- all: add `Event.Has()` and `Op.Has()` ([#477])
|
||||
|
||||
This makes checking events a lot easier; for example:
|
||||
|
||||
if event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||
}
|
||||
|
||||
Becomes:
|
||||
|
||||
if event.Has(Write) && !event.Has(Remove) {
|
||||
}
|
||||
|
||||
- all: add cmd/fsnotify ([#463])
|
||||
|
||||
A command-line utility for testing and some examples.
|
||||
|
||||
### Changes and fixes
|
||||
|
||||
- inotify: don't ignore events for files that don't exist ([#260], [#470])
|
||||
|
||||
Previously the inotify watcher would call `os.Lstat()` to check if a file
|
||||
still exists before emitting events.
|
||||
|
||||
This was inconsistent with other platforms and resulted in inconsistent event
|
||||
reporting (e.g. when a file is quickly removed and re-created), and generally
|
||||
a source of confusion. It was added in 2013 to fix a memory leak that no
|
||||
longer exists.
|
||||
|
||||
- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's
|
||||
not watched ([#460])
|
||||
|
||||
- inotify: replace epoll() with non-blocking inotify ([#434])
|
||||
|
||||
Non-blocking inotify was not generally available at the time this library was
|
||||
written in 2014, but now it is. As a result, the minimum Linux version is
|
||||
bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster.
|
||||
|
||||
- kqueue: don't check for events every 100ms ([#480])
|
||||
|
||||
The watcher would wake up every 100ms, even when there was nothing to do. Now
|
||||
it waits until there is something to do.
|
||||
|
||||
- macos: retry opening files on EINTR ([#475])
|
||||
|
||||
- kqueue: skip unreadable files ([#479])
|
||||
|
||||
kqueue requires a file descriptor for every file in a directory; this would
|
||||
fail if a file was unreadable by the current user. Now these files are simply
|
||||
skipped.
|
||||
|
||||
- windows: fix renaming a watched directory if the parent is also watched ([#370])
|
||||
|
||||
- windows: increase buffer size from 4K to 64K ([#485])
|
||||
|
||||
- windows: close file handle on Remove() ([#288])
|
||||
|
||||
- kqueue: put pathname in the error if watching a file fails ([#471])
|
||||
|
||||
- inotify, windows: calling Close() more than once could race ([#465])
|
||||
|
||||
- kqueue: improve Close() performance ([#233])
|
||||
|
||||
- all: various documentation additions and clarifications.
|
||||
|
||||
[#233]: https://github.com/fsnotify/fsnotify/pull/233
|
||||
[#260]: https://github.com/fsnotify/fsnotify/pull/260
|
||||
[#288]: https://github.com/fsnotify/fsnotify/pull/288
|
||||
[#370]: https://github.com/fsnotify/fsnotify/pull/370
|
||||
[#434]: https://github.com/fsnotify/fsnotify/pull/434
|
||||
[#460]: https://github.com/fsnotify/fsnotify/pull/460
|
||||
[#463]: https://github.com/fsnotify/fsnotify/pull/463
|
||||
[#465]: https://github.com/fsnotify/fsnotify/pull/465
|
||||
[#470]: https://github.com/fsnotify/fsnotify/pull/470
|
||||
[#471]: https://github.com/fsnotify/fsnotify/pull/471
|
||||
[#475]: https://github.com/fsnotify/fsnotify/pull/475
|
||||
[#477]: https://github.com/fsnotify/fsnotify/pull/477
|
||||
[#479]: https://github.com/fsnotify/fsnotify/pull/479
|
||||
[#480]: https://github.com/fsnotify/fsnotify/pull/480
|
||||
[#485]: https://github.com/fsnotify/fsnotify/pull/485
|
||||
|
||||
## [1.5.4] - 2022-04-25
|
||||
|
||||
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
||||
@@ -40,6 +129,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
||||
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
||||
|
||||
## [1.4.9] - 2020-03-11
|
||||
|
||||
* Move example usage to the readme #329. This may resolve #328.
|
||||
|
||||
## [1.4.8] - 2020-03-10
|
||||
|
||||
* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216)
|
||||
* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265)
|
||||
* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266)
|
||||
* CI: Less verbosity (@nathany #267)
|
||||
* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267)
|
||||
* Tests: Check if channels are closed in the example (@alexeykazakov #244)
|
||||
* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284)
|
||||
* CI: Add windows to travis matrix (@cpuguy83 #284)
|
||||
* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93)
|
||||
* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219)
|
||||
* Linux: open files with close-on-exec (@linxiulei #273)
|
||||
* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 )
|
||||
* Project: Add go.mod (@nathany #309)
|
||||
* Project: Revise editor config (@nathany #309)
|
||||
* Project: Update copyright for 2019 (@nathany #309)
|
||||
* CI: Drop go1.8 from CI matrix (@nathany #309)
|
||||
* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e )
|
||||
|
||||
## [1.4.7] - 2018-01-09
|
||||
|
||||
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||
|
72
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
72
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
@@ -1,60 +1,26 @@
|
||||
# Contributing
|
||||
Thank you for your interest in contributing to fsnotify! We try to review and
|
||||
merge PRs in a reasonable timeframe, but please be aware that:
|
||||
|
||||
## Issues
|
||||
- To avoid "wasted" work, please discus changes on the issue tracker first. You
|
||||
can just send PRs, but they may end up being rejected for one reason or the
|
||||
other.
|
||||
|
||||
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
|
||||
* Please indicate the platform you are using fsnotify on.
|
||||
* A code example to reproduce the problem is appreciated.
|
||||
- fsnotify is a cross-platform library, and changes must work reasonably well on
|
||||
all supported platforms.
|
||||
|
||||
## Pull Requests
|
||||
- Changes will need to be compatible; old code should still compile, and the
|
||||
runtime behaviour can't change in ways that are likely to lead to problems for
|
||||
users.
|
||||
|
||||
### Contributor License Agreement
|
||||
Testing
|
||||
-------
|
||||
Just `go test ./...` runs all the tests; the CI runs this on all supported
|
||||
platforms. Testing different platforms locally can be done with something like
|
||||
[goon] or [Vagrant], but this isn't super-easy to set up at the moment.
|
||||
|
||||
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
|
||||
Use the `-short` flag to make the "stress test" run faster.
|
||||
|
||||
Please indicate that you have signed the CLA in your pull request.
|
||||
|
||||
### How fsnotify is Developed
|
||||
|
||||
* Development is done on feature branches.
|
||||
* Tests are run on BSD, Linux, macOS and Windows.
|
||||
* Pull requests are reviewed and [applied to master][am] using [hub][].
|
||||
* Maintainers may modify or squash commits rather than asking contributors to.
|
||||
* To issue a new release, the maintainers will:
|
||||
* Update the CHANGELOG
|
||||
* Tag a version, which will become available through gopkg.in.
|
||||
|
||||
### How to Fork
|
||||
|
||||
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
|
||||
|
||||
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Ensure everything works and the tests pass (see below)
|
||||
4. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
|
||||
Contribute upstream:
|
||||
|
||||
1. Fork fsnotify on GitHub
|
||||
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
|
||||
3. Push to the branch (`git push fork my-new-feature`)
|
||||
4. Create a new Pull Request on GitHub
|
||||
|
||||
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
|
||||
|
||||
### Testing
|
||||
|
||||
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
|
||||
|
||||
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
|
||||
|
||||
### Maintainers
|
||||
|
||||
Help maintaining fsnotify is welcome. To be a maintainer:
|
||||
|
||||
* Submit a pull request and sign the CLA as above.
|
||||
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
|
||||
|
||||
All code changes should be internal pull requests.
|
||||
|
||||
Releases are tagged using [Semantic Versioning](http://semver.org/).
|
||||
[goon]: https://github.com/arp242/goon
|
||||
[Vagrant]: https://www.vagrantup.com/
|
||||
[integration_test.go]: /integration_test.go
|
||||
|
47
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
47
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
@@ -1,28 +1,25 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2012-2019 fsnotify Authors. All rights reserved.
|
||||
Copyright © 2012 The Go Authors. All rights reserved.
|
||||
Copyright © fsnotify Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
227
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
227
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
@@ -1,120 +1,161 @@
|
||||
# File system notifications for Go
|
||||
fsnotify is a Go library to provide cross-platform filesystem notifications on
|
||||
Windows, Linux, macOS, and BSD systems.
|
||||
|
||||
[](https://pkg.go.dev/github.com/fsnotify/fsnotify) [](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [](https://github.com/fsnotify/fsnotify/issues/413)
|
||||
Go 1.16 or newer is required; the full documentation is at
|
||||
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
||||
|
||||
fsnotify utilizes [`golang.org/x/sys`](https://pkg.go.dev/golang.org/x/sys) rather than [`syscall`](https://pkg.go.dev/syscall) from the standard library.
|
||||
**It's best to read the documentation at pkg.go.dev, as it's pinned to the last
|
||||
released version, whereas this README is for the last development version which
|
||||
may include additions/changes.**
|
||||
|
||||
Cross platform: Windows, Linux, BSD and macOS.
|
||||
---
|
||||
|
||||
| Adapter | OS | Status |
|
||||
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| inotify | Linux 2.6.27 or later, Android\* | Supported |
|
||||
| kqueue | BSD, macOS, iOS\* | Supported |
|
||||
| ReadDirectoryChangesW | Windows | Supported |
|
||||
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
||||
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
|
||||
| fanotify | Linux 2.6.37+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
|
||||
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||
Platform support:
|
||||
|
||||
\* Android and iOS are untested.
|
||||
| Adapter | OS | Status |
|
||||
| --------------------- | ---------------| -------------------------------------------------------------|
|
||||
| inotify | Linux 2.6.32+ | Supported |
|
||||
| kqueue | BSD, macOS | Supported |
|
||||
| ReadDirectoryChangesW | Windows | Supported |
|
||||
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
||||
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
|
||||
| fanotify | Linux 5.9+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
|
||||
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||
|
||||
Please see [the documentation](https://pkg.go.dev/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
|
||||
Linux and macOS should include Android and iOS, but these are currently untested.
|
||||
|
||||
## API stability
|
||||
|
||||
fsnotify is a fork of [howeyc/fsnotify](https://github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
|
||||
|
||||
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## Usage
|
||||
Usage
|
||||
-----
|
||||
A basic example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"log"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
func main() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
// Create new watcher.
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("event:", event)
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
log.Println("modified file:", event.Name)
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Start listening for events.
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("event:", event)
|
||||
if event.Has(fsnotify.Write) {
|
||||
log.Println("modified file:", event.Name)
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = watcher.Add("/tmp/foo")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
<-done
|
||||
// Add a path.
|
||||
err = watcher.Add("/tmp")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Block main goroutine forever.
|
||||
<-make(chan struct{})
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
|
||||
run with:
|
||||
|
||||
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||
% go run ./cmd/fsnotify
|
||||
|
||||
## FAQ
|
||||
FAQ
|
||||
---
|
||||
### Will a file still be watched when it's moved to another directory?
|
||||
No, not unless you are watching the location it was moved to.
|
||||
|
||||
**When a file is moved to another directory is it still being watched?**
|
||||
### Are subdirectories watched too?
|
||||
No, you must add watches for any directory you want to watch (a recursive
|
||||
watcher is on the roadmap: [#18]).
|
||||
|
||||
No (it shouldn't be, unless you are watching where it was moved to).
|
||||
|
||||
**When I watch a directory, are all subdirectories watched as well?**
|
||||
|
||||
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
|
||||
|
||||
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
||||
|
||||
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
|
||||
|
||||
**Why am I receiving multiple events for the same file on OS X?**
|
||||
|
||||
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
|
||||
|
||||
**How many files can be watched at once?**
|
||||
|
||||
There are OS-specific limits as to how many watches can be created:
|
||||
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
|
||||
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
||||
|
||||
**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?**
|
||||
|
||||
fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications.
|
||||
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||
|
||||
### Do I have to watch the Error and Event channels in a goroutine?
|
||||
As of now, yes (you can read both channels in the same goroutine using `select`,
|
||||
you don't need a separate goroutine for both channels; see the example).
|
||||
|
||||
### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
|
||||
fsnotify requires support from underlying OS to work. The current NFS and SMB
|
||||
protocols does not provide network level support for file notifications, and
|
||||
neither do the /proc and /sys virtual filesystems.
|
||||
|
||||
This could be fixed with a polling watcher ([#9]), but it's not yet implemented.
|
||||
|
||||
[#9]: https://github.com/fsnotify/fsnotify/issues/9
|
||||
|
||||
Platform-specific notes
|
||||
-----------------------
|
||||
### Linux
|
||||
When a file is removed a REMOVE event won't be emitted until all file
|
||||
descriptors are closed; it will emit a CHMOD instead:
|
||||
|
||||
fp := os.Open("file")
|
||||
os.Remove("file") // CHMOD
|
||||
fp.Close() // REMOVE
|
||||
|
||||
This is the event that inotify sends, so not much can be changed about this.
|
||||
|
||||
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
|
||||
the number of watches per user, and `fs.inotify.max_user_instances` specifies
|
||||
the maximum number of inotify instances per user. Every Watcher you create is an
|
||||
"instance", and every path you add is a "watch".
|
||||
|
||||
These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and
|
||||
`/proc/sys/fs/inotify/max_user_instances`
|
||||
|
||||
To increase them you can use `sysctl` or write the value to proc file:
|
||||
|
||||
# The default values on Linux 5.18
|
||||
sysctl fs.inotify.max_user_watches=124983
|
||||
sysctl fs.inotify.max_user_instances=128
|
||||
|
||||
To make the changes persist on reboot edit `/etc/sysctl.conf` or
|
||||
`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your
|
||||
distro's documentation):
|
||||
|
||||
fs.inotify.max_user_watches=124983
|
||||
fs.inotify.max_user_instances=128
|
||||
|
||||
Reaching the limit will result in a "no space left on device" or "too many open
|
||||
files" error.
|
||||
|
||||
### kqueue (macOS, all BSD systems)
|
||||
kqueue requires opening a file descriptor for every file that's being watched;
|
||||
so if you're watching a directory with five files then that's six file
|
||||
descriptors. You will run in to your system's "max open files" limit faster on
|
||||
these platforms.
|
||||
|
||||
The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to
|
||||
control the maximum number of open files.
|
||||
|
||||
### macOS
|
||||
Spotlight indexing on macOS can result in multiple events (see [#15]). A temporary
|
||||
workaround is to add your folder(s) to the *Spotlight Privacy settings* until we
|
||||
have a native FSEvents implementation (see [#11]).
|
||||
|
||||
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
||||
|
||||
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
|
||||
|
||||
## Related Projects
|
||||
|
||||
* [notify](https://github.com/rjeczalik/notify)
|
||||
* [fsevents](https://github.com/fsnotify/fsevents)
|
||||
|
||||
[#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||
|
162
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
162
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
//go:build solaris
|
||||
// +build solaris
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Watcher watches a set of paths, delivering events on a channel.
|
||||
//
|
||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||
// value).
|
||||
//
|
||||
// # Linux notes
|
||||
//
|
||||
// When a file is removed a Remove event won't be emitted until all file
|
||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||
//
|
||||
// fp := os.Open("file")
|
||||
// os.Remove("file") // Triggers Chmod
|
||||
// fp.Close() // Triggers Remove
|
||||
//
|
||||
// This is the event that inotify sends, so not much can be changed about this.
|
||||
//
|
||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||
// create is an "instance", and every path you add is a "watch".
|
||||
//
|
||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||
// /proc/sys/fs/inotify/max_user_instances
|
||||
//
|
||||
// To increase them you can use sysctl or write the value to the /proc file:
|
||||
//
|
||||
// # Default values on Linux 5.18
|
||||
// sysctl fs.inotify.max_user_watches=124983
|
||||
// sysctl fs.inotify.max_user_instances=128
|
||||
//
|
||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||
// your distro's documentation):
|
||||
//
|
||||
// fs.inotify.max_user_watches=124983
|
||||
// fs.inotify.max_user_instances=128
|
||||
//
|
||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||
// files" error.
|
||||
//
|
||||
// # kqueue notes (macOS, BSD)
|
||||
//
|
||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||
// so if you're watching a directory with five files then that's six file
|
||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||
// these platforms.
|
||||
//
|
||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||
// systems.
|
||||
//
|
||||
// # macOS notes
|
||||
//
|
||||
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||
//
|
||||
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||
type Watcher struct {
|
||||
// Events sends the filesystem change events.
|
||||
//
|
||||
// fsnotify can send the following events; a "path" here can refer to a
|
||||
// file, directory, symbolic link, or special file like a FIFO.
|
||||
//
|
||||
// fsnotify.Create A new path was created; this may be followed by one
|
||||
// or more Write events if data also gets written to a
|
||||
// file.
|
||||
//
|
||||
// fsnotify.Remove A path was removed.
|
||||
//
|
||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||
// old path as Event.Name, and a Create event will be
|
||||
// sent with the new name. Renames are only sent for
|
||||
// paths that are currently watched; e.g. moving an
|
||||
// unmonitored file into a monitored directory will
|
||||
// show up as just a Create. Similarly, renaming a file
|
||||
// to outside a monitored directory will show up as
|
||||
// only a Rename.
|
||||
//
|
||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||
// also trigger a Write. A single "write action"
|
||||
// initiated by the user may show up as one or multiple
|
||||
// writes, depending on when the system syncs things to
|
||||
// disk. For example when compiling a large Go program
|
||||
// you may get hundreds of Write events, so you
|
||||
// probably want to wait until you've stopped receiving
|
||||
// them (see the dedup example in cmd/fsnotify).
|
||||
//
|
||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||
// when a file is removed (or more accurately, when a
|
||||
// link to an inode is removed). On kqueue it's sent
|
||||
// and on kqueue when a file is truncated. On Windows
|
||||
// it's never sent.
|
||||
Events chan Event
|
||||
|
||||
// Errors sends any errors.
|
||||
Errors chan error
|
||||
}
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts monitoring the path for changes.
|
||||
//
|
||||
// A path can only be watched once; attempting to watch it more than once will
|
||||
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||
// added. A watch will be automatically removed if the path is deleted.
|
||||
//
|
||||
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||
// re-created, or if it's moved to a different filesystem.
|
||||
//
|
||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||
//
|
||||
// # Watching directories
|
||||
//
|
||||
// All files in a directory are monitored, including new files that are created
|
||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||
// non-recursive).
|
||||
//
|
||||
// # Watching files
|
||||
//
|
||||
// Watching individual files (rather than directories) is generally not
|
||||
// recommended as many tools update files atomically. Instead of "just" writing
|
||||
// to the file a temporary file will be written to first, and if successful the
|
||||
// temporary file is moved to to destination removing the original, or some
|
||||
// variant thereof. The watcher on the original file is now lost, as it no
|
||||
// longer exists.
|
||||
//
|
||||
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||
func (w *Watcher) Add(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops monitoring the path for changes.
|
||||
//
|
||||
// Directories are always removed non-recursively. For example, if you added
|
||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||
//
|
||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
return nil
|
||||
}
|
459
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
459
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
@@ -0,0 +1,459 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of paths, delivering events on a channel.
|
||||
//
|
||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||
// value).
|
||||
//
|
||||
// # Linux notes
|
||||
//
|
||||
// When a file is removed a Remove event won't be emitted until all file
|
||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||
//
|
||||
// fp := os.Open("file")
|
||||
// os.Remove("file") // Triggers Chmod
|
||||
// fp.Close() // Triggers Remove
|
||||
//
|
||||
// This is the event that inotify sends, so not much can be changed about this.
|
||||
//
|
||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||
// create is an "instance", and every path you add is a "watch".
|
||||
//
|
||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||
// /proc/sys/fs/inotify/max_user_instances
|
||||
//
|
||||
// To increase them you can use sysctl or write the value to the /proc file:
|
||||
//
|
||||
// # Default values on Linux 5.18
|
||||
// sysctl fs.inotify.max_user_watches=124983
|
||||
// sysctl fs.inotify.max_user_instances=128
|
||||
//
|
||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||
// your distro's documentation):
|
||||
//
|
||||
// fs.inotify.max_user_watches=124983
|
||||
// fs.inotify.max_user_instances=128
|
||||
//
|
||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||
// files" error.
|
||||
//
|
||||
// # kqueue notes (macOS, BSD)
|
||||
//
|
||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||
// so if you're watching a directory with five files then that's six file
|
||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||
// these platforms.
|
||||
//
|
||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||
// systems.
|
||||
//
|
||||
// # macOS notes
|
||||
//
|
||||
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||
//
|
||||
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||
type Watcher struct {
|
||||
// Events sends the filesystem change events.
|
||||
//
|
||||
// fsnotify can send the following events; a "path" here can refer to a
|
||||
// file, directory, symbolic link, or special file like a FIFO.
|
||||
//
|
||||
// fsnotify.Create A new path was created; this may be followed by one
|
||||
// or more Write events if data also gets written to a
|
||||
// file.
|
||||
//
|
||||
// fsnotify.Remove A path was removed.
|
||||
//
|
||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||
// old path as Event.Name, and a Create event will be
|
||||
// sent with the new name. Renames are only sent for
|
||||
// paths that are currently watched; e.g. moving an
|
||||
// unmonitored file into a monitored directory will
|
||||
// show up as just a Create. Similarly, renaming a file
|
||||
// to outside a monitored directory will show up as
|
||||
// only a Rename.
|
||||
//
|
||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||
// also trigger a Write. A single "write action"
|
||||
// initiated by the user may show up as one or multiple
|
||||
// writes, depending on when the system syncs things to
|
||||
// disk. For example when compiling a large Go program
|
||||
// you may get hundreds of Write events, so you
|
||||
// probably want to wait until you've stopped receiving
|
||||
// them (see the dedup example in cmd/fsnotify).
|
||||
//
|
||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||
// when a file is removed (or more accurately, when a
|
||||
// link to an inode is removed). On kqueue it's sent
|
||||
// and on kqueue when a file is truncated. On Windows
|
||||
// it's never sent.
|
||||
Events chan Event
|
||||
|
||||
// Errors sends any errors.
|
||||
Errors chan error
|
||||
|
||||
// Store fd here as os.File.Read() will no longer return on close after
|
||||
// calling Fd(). See: https://github.com/golang/go/issues/26439
|
||||
fd int
|
||||
mu sync.Mutex // Map access
|
||||
inotifyFile *os.File
|
||||
watches map[string]*watch // Map of inotify watches (key: path)
|
||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
doneResp chan struct{} // Channel to respond to Close
|
||||
}
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
// Create inotify fd
|
||||
// Need to set the FD to nonblocking mode in order for SetDeadline methods to work
|
||||
// Otherwise, blocking i/o operations won't terminate on close
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||
if fd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
w := &Watcher{
|
||||
fd: fd,
|
||||
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||
watches: make(map[string]*watch),
|
||||
paths: make(map[int]string),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
doneResp: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Returns true if the event was sent, or false if watcher is closed.
|
||||
func (w *Watcher) sendEvent(e Event) bool {
|
||||
select {
|
||||
case w.Events <- e:
|
||||
return true
|
||||
case <-w.done:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *Watcher) sendError(err error) bool {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
case <-w.done:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed() {
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||
close(w.done)
|
||||
w.mu.Unlock()
|
||||
|
||||
// Causes any blocking reads to return with an error, provided the file
|
||||
// still supports deadline operations.
|
||||
err := w.inotifyFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for goroutine to close
|
||||
<-w.doneResp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts monitoring the path for changes.
|
||||
//
|
||||
// A path can only be watched once; attempting to watch it more than once will
|
||||
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||
// added. A watch will be automatically removed if the path is deleted.
|
||||
//
|
||||
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||
// re-created, or if it's moved to a different filesystem.
|
||||
//
|
||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||
//
|
||||
// # Watching directories
|
||||
//
|
||||
// All files in a directory are monitored, including new files that are created
|
||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||
// non-recursive).
|
||||
//
|
||||
// # Watching files
|
||||
//
|
||||
// Watching individual files (rather than directories) is generally not
|
||||
// recommended as many tools update files atomically. Instead of "just" writing
|
||||
// to the file a temporary file will be written to first, and if successful the
|
||||
// temporary file is moved to to destination removing the original, or some
|
||||
// variant thereof. The watcher on the original file is now lost, as it no
|
||||
// longer exists.
|
||||
//
|
||||
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||
func (w *Watcher) Add(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
if w.isClosed() {
|
||||
return errors.New("inotify instance already closed")
|
||||
}
|
||||
|
||||
var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watchEntry := w.watches[name]
|
||||
if watchEntry != nil {
|
||||
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||
}
|
||||
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||
if wd == -1 {
|
||||
return errno
|
||||
}
|
||||
|
||||
if watchEntry == nil {
|
||||
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||
w.paths[wd] = name
|
||||
} else {
|
||||
watchEntry.wd = uint32(wd)
|
||||
watchEntry.flags = flags
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops monitoring the path for changes.
|
||||
//
|
||||
// Directories are always removed non-recursively. For example, if you added
|
||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||
//
|
||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
|
||||
// Fetch the watch.
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watch, ok := w.watches[name]
|
||||
|
||||
// Remove it from inotify.
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||
}
|
||||
|
||||
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||
// error, we need to clean up our internal state to ensure it matches
|
||||
// inotify's kernel state.
|
||||
delete(w.paths, int(watch.wd))
|
||||
delete(w.watches, name)
|
||||
|
||||
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||
// the inotify will already have been removed.
|
||||
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||
// by another thread and we have not received IN_IGNORE event.
|
||||
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||
if success == -1 {
|
||||
// TODO: Perhaps it's not helpful to return an error here in every case;
|
||||
// The only two possible errors are:
|
||||
//
|
||||
// - EBADF, which happens when w.fd is not a valid file descriptor
|
||||
// of any kind.
|
||||
// - EINVAL, which is when fd is not an inotify descriptor or wd
|
||||
// is not a valid watch descriptor. Watch descriptors are
|
||||
// invalidated when they are removed explicitly or implicitly;
|
||||
// explicitly by inotify_rm_watch, implicitly when the file they
|
||||
// are watching is deleted.
|
||||
return errno
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||
func (w *Watcher) WatchList() []string {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
entries := make([]string, 0, len(w.watches))
|
||||
for pathname := range w.watches {
|
||||
entries = append(entries, pathname)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
}
|
||||
|
||||
// readEvents reads from the inotify file descriptor, converts the
|
||||
// received events into Event objects and sends them via the Events channel
|
||||
func (w *Watcher) readEvents() {
|
||||
defer func() {
|
||||
close(w.doneResp)
|
||||
close(w.Errors)
|
||||
close(w.Events)
|
||||
}()
|
||||
|
||||
var (
|
||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
errno error // Syscall errno
|
||||
)
|
||||
for {
|
||||
// See if we have been closed.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := w.inotifyFile.Read(buf[:])
|
||||
switch {
|
||||
case errors.Unwrap(err) == os.ErrClosed:
|
||||
return
|
||||
case err != nil:
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if n < unix.SizeofInotifyEvent {
|
||||
var err error
|
||||
if n == 0 {
|
||||
// If EOF is received. This should really never happen.
|
||||
err = io.EOF
|
||||
} else if n < 0 {
|
||||
// If an error occurred while reading.
|
||||
err = errno
|
||||
} else {
|
||||
// Read was too short.
|
||||
err = errors.New("notify: short read in readEvents()")
|
||||
}
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||
var (
|
||||
// Point "raw" to the event in the buffer
|
||||
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
mask = uint32(raw.Mask)
|
||||
nameLen = uint32(raw.Len)
|
||||
)
|
||||
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
if !w.sendError(ErrEventOverflow) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If the event happened to the watched directory or the watched file, the kernel
|
||||
// doesn't append the filename to the event, but we would like to always fill the
|
||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||
// the "paths" map.
|
||||
w.mu.Lock()
|
||||
name, ok := w.paths[int(raw.Wd)]
|
||||
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||
// with the inotify kernel state which has already deleted the watch
|
||||
// automatically.
|
||||
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
delete(w.paths, int(raw.Wd))
|
||||
delete(w.watches, name)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if nameLen > 0 {
|
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
event := w.newEvent(name, mask)
|
||||
|
||||
// Send the events that are not ignored on the events channel
|
||||
if mask&unix.IN_IGNORED == 0 {
|
||||
if !w.sendEvent(event) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.SizeofInotifyEvent + nameLen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
707
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
Normal file
707
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
Normal file
@@ -0,0 +1,707 @@
|
||||
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||
// +build freebsd openbsd netbsd dragonfly darwin
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of paths, delivering events on a channel.
|
||||
//
|
||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||
// value).
|
||||
//
|
||||
// # Linux notes
|
||||
//
|
||||
// When a file is removed a Remove event won't be emitted until all file
|
||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||
//
|
||||
// fp := os.Open("file")
|
||||
// os.Remove("file") // Triggers Chmod
|
||||
// fp.Close() // Triggers Remove
|
||||
//
|
||||
// This is the event that inotify sends, so not much can be changed about this.
|
||||
//
|
||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||
// create is an "instance", and every path you add is a "watch".
|
||||
//
|
||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||
// /proc/sys/fs/inotify/max_user_instances
|
||||
//
|
||||
// To increase them you can use sysctl or write the value to the /proc file:
|
||||
//
|
||||
// # Default values on Linux 5.18
|
||||
// sysctl fs.inotify.max_user_watches=124983
|
||||
// sysctl fs.inotify.max_user_instances=128
|
||||
//
|
||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||
// your distro's documentation):
|
||||
//
|
||||
// fs.inotify.max_user_watches=124983
|
||||
// fs.inotify.max_user_instances=128
|
||||
//
|
||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||
// files" error.
|
||||
//
|
||||
// # kqueue notes (macOS, BSD)
|
||||
//
|
||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||
// so if you're watching a directory with five files then that's six file
|
||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||
// these platforms.
|
||||
//
|
||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||
// systems.
|
||||
//
|
||||
// # macOS notes
|
||||
//
|
||||
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||
//
|
||||
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||
type Watcher struct {
|
||||
// Events sends the filesystem change events.
|
||||
//
|
||||
// fsnotify can send the following events; a "path" here can refer to a
|
||||
// file, directory, symbolic link, or special file like a FIFO.
|
||||
//
|
||||
// fsnotify.Create A new path was created; this may be followed by one
|
||||
// or more Write events if data also gets written to a
|
||||
// file.
|
||||
//
|
||||
// fsnotify.Remove A path was removed.
|
||||
//
|
||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||
// old path as Event.Name, and a Create event will be
|
||||
// sent with the new name. Renames are only sent for
|
||||
// paths that are currently watched; e.g. moving an
|
||||
// unmonitored file into a monitored directory will
|
||||
// show up as just a Create. Similarly, renaming a file
|
||||
// to outside a monitored directory will show up as
|
||||
// only a Rename.
|
||||
//
|
||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||
// also trigger a Write. A single "write action"
|
||||
// initiated by the user may show up as one or multiple
|
||||
// writes, depending on when the system syncs things to
|
||||
// disk. For example when compiling a large Go program
|
||||
// you may get hundreds of Write events, so you
|
||||
// probably want to wait until you've stopped receiving
|
||||
// them (see the dedup example in cmd/fsnotify).
|
||||
//
|
||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||
// when a file is removed (or more accurately, when a
|
||||
// link to an inode is removed). On kqueue it's sent
|
||||
// and on kqueue when a file is truncated. On Windows
|
||||
// it's never sent.
|
||||
Events chan Event
|
||||
|
||||
// Errors sends any errors.
|
||||
Errors chan error
|
||||
|
||||
done chan struct{}
|
||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||
closepipe [2]int // Pipe used for closing.
|
||||
mu sync.Mutex // Protects access to watcher data
|
||||
watches map[string]int // Watched file descriptors (key: path).
|
||||
watchesByDir map[string]map[int]struct{} // Watched file descriptors indexed by the parent directory (key: dirname(path)).
|
||||
userWatches map[string]struct{} // Watches added with Watcher.Add()
|
||||
dirFlags map[string]uint32 // Watched directories to fflags used in kqueue.
|
||||
paths map[int]pathInfo // File descriptors to path names for processing kqueue events.
|
||||
fileExists map[string]struct{} // Keep track of if we know this file exists (to stop duplicate create events).
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
type pathInfo struct {
|
||||
name string
|
||||
isDir bool
|
||||
}
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
kq, closepipe, err := newKqueue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := &Watcher{
|
||||
kq: kq,
|
||||
closepipe: closepipe,
|
||||
watches: make(map[string]int),
|
||||
watchesByDir: make(map[string]map[int]struct{}),
|
||||
dirFlags: make(map[string]uint32),
|
||||
paths: make(map[int]pathInfo),
|
||||
fileExists: make(map[string]struct{}),
|
||||
userWatches: make(map[string]struct{}),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// newKqueue creates a new kernel event queue and returns a descriptor.
|
||||
//
|
||||
// This registers a new event on closepipe, which will trigger an event when
|
||||
// it's closed. This way we can use kevent() without timeout/polling; without
|
||||
// the closepipe, it would block forever and we wouldn't be able to stop it at
|
||||
// all.
|
||||
func newKqueue() (kq int, closepipe [2]int, err error) {
|
||||
kq, err = unix.Kqueue()
|
||||
if kq == -1 {
|
||||
return kq, closepipe, err
|
||||
}
|
||||
|
||||
// Register the close pipe.
|
||||
err = unix.Pipe(closepipe[:])
|
||||
if err != nil {
|
||||
unix.Close(kq)
|
||||
return kq, closepipe, err
|
||||
}
|
||||
|
||||
// Register changes to listen on the closepipe.
|
||||
changes := make([]unix.Kevent_t, 1)
|
||||
// SetKevent converts int to the platform-specific types.
|
||||
unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ,
|
||||
unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT)
|
||||
|
||||
ok, err := unix.Kevent(kq, changes, nil, nil)
|
||||
if ok == -1 {
|
||||
unix.Close(kq)
|
||||
unix.Close(closepipe[0])
|
||||
unix.Close(closepipe[1])
|
||||
return kq, closepipe, err
|
||||
}
|
||||
return kq, closepipe, nil
|
||||
}
|
||||
|
||||
// Returns true if the event was sent, or false if watcher is closed.
|
||||
func (w *Watcher) sendEvent(e Event) bool {
|
||||
select {
|
||||
case w.Events <- e:
|
||||
return true
|
||||
case <-w.done:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *Watcher) sendError(err error) bool {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
case <-w.done:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// copy paths to remove while locked
|
||||
pathsToRemove := make([]string, 0, len(w.watches))
|
||||
for name := range w.watches {
|
||||
pathsToRemove = append(pathsToRemove, name)
|
||||
}
|
||||
w.mu.Unlock() // Unlock before calling Remove, which also locks
|
||||
for _, name := range pathsToRemove {
|
||||
w.Remove(name)
|
||||
}
|
||||
|
||||
// Send "quit" message to the reader goroutine.
|
||||
unix.Close(w.closepipe[1])
|
||||
close(w.done)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts monitoring the path for changes.
|
||||
//
|
||||
// A path can only be watched once; attempting to watch it more than once will
|
||||
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||
// added. A watch will be automatically removed if the path is deleted.
|
||||
//
|
||||
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||
// re-created, or if it's moved to a different filesystem.
|
||||
//
|
||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||
//
|
||||
// # Watching directories
|
||||
//
|
||||
// All files in a directory are monitored, including new files that are created
|
||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||
// non-recursive).
|
||||
//
|
||||
// # Watching files
|
||||
//
|
||||
// Watching individual files (rather than directories) is generally not
|
||||
// recommended as many tools update files atomically. Instead of "just" writing
|
||||
// to the file a temporary file will be written to first, and if successful the
|
||||
// temporary file is moved to to destination removing the original, or some
|
||||
// variant thereof. The watcher on the original file is now lost, as it no
|
||||
// longer exists.
|
||||
//
|
||||
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||
func (w *Watcher) Add(name string) error {
|
||||
w.mu.Lock()
|
||||
w.userWatches[name] = struct{}{}
|
||||
w.mu.Unlock()
|
||||
_, err := w.addWatch(name, noteAllEvents)
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove stops monitoring the path for changes.
|
||||
//
|
||||
// Directories are always removed non-recursively. For example, if you added
|
||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||
//
|
||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
w.mu.Lock()
|
||||
watchfd, ok := w.watches[name]
|
||||
w.mu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||
}
|
||||
|
||||
err := w.register([]int{watchfd}, unix.EV_DELETE, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unix.Close(watchfd)
|
||||
|
||||
w.mu.Lock()
|
||||
isDir := w.paths[watchfd].isDir
|
||||
delete(w.watches, name)
|
||||
delete(w.userWatches, name)
|
||||
|
||||
parentName := filepath.Dir(name)
|
||||
delete(w.watchesByDir[parentName], watchfd)
|
||||
|
||||
if len(w.watchesByDir[parentName]) == 0 {
|
||||
delete(w.watchesByDir, parentName)
|
||||
}
|
||||
|
||||
delete(w.paths, watchfd)
|
||||
delete(w.dirFlags, name)
|
||||
delete(w.fileExists, name)
|
||||
w.mu.Unlock()
|
||||
|
||||
// Find all watched paths that are in this directory that are not external.
|
||||
if isDir {
|
||||
var pathsToRemove []string
|
||||
w.mu.Lock()
|
||||
for fd := range w.watchesByDir[name] {
|
||||
path := w.paths[fd]
|
||||
if _, ok := w.userWatches[path.name]; !ok {
|
||||
pathsToRemove = append(pathsToRemove, path.name)
|
||||
}
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, name := range pathsToRemove {
|
||||
// Since these are internal, not much sense in propagating error
|
||||
// to the user, as that will just confuse them with an error about
|
||||
// a path they did not explicitly watch themselves.
|
||||
w.Remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||
func (w *Watcher) WatchList() []string {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
entries := make([]string, 0, len(w.userWatches))
|
||||
for pathname := range w.userWatches {
|
||||
entries = append(entries, pathname)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||
|
||||
// addWatch adds name to the watched file set.
|
||||
// The flags are interpreted as described in kevent(2).
|
||||
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||
var isDir bool
|
||||
// Make ./name and name equivalent
|
||||
name = filepath.Clean(name)
|
||||
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return "", errors.New("kevent instance already closed")
|
||||
}
|
||||
watchfd, alreadyWatching := w.watches[name]
|
||||
// We already have a watch, but we can still override flags.
|
||||
if alreadyWatching {
|
||||
isDir = w.paths[watchfd].isDir
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if !alreadyWatching {
|
||||
fi, err := os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Don't watch sockets or named pipes
|
||||
if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Follow Symlinks
|
||||
//
|
||||
// Linux can add unresolvable symlinks to the watch list without issue,
|
||||
// and Windows can't do symlinks period. To maintain consistency, we
|
||||
// will act like everything is fine if the link can't be resolved.
|
||||
// There will simply be no file events for broken symlinks. Hence the
|
||||
// returns of nil on errors.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
name, err = filepath.EvalSymlinks(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
_, alreadyWatching = w.watches[name]
|
||||
w.mu.Unlock()
|
||||
|
||||
if alreadyWatching {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
fi, err = os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// Retry on EINTR; open() can return EINTR in practice on macOS.
|
||||
// See #354, and go issues 11180 and 39237.
|
||||
for {
|
||||
watchfd, err = unix.Open(name, openMode, 0)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if errors.Is(err, unix.EINTR) {
|
||||
continue
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
isDir = fi.IsDir()
|
||||
}
|
||||
|
||||
err := w.register([]int{watchfd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
|
||||
if err != nil {
|
||||
unix.Close(watchfd)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !alreadyWatching {
|
||||
w.mu.Lock()
|
||||
parentName := filepath.Dir(name)
|
||||
w.watches[name] = watchfd
|
||||
|
||||
watchesByDir, ok := w.watchesByDir[parentName]
|
||||
if !ok {
|
||||
watchesByDir = make(map[int]struct{}, 1)
|
||||
w.watchesByDir[parentName] = watchesByDir
|
||||
}
|
||||
watchesByDir[watchfd] = struct{}{}
|
||||
|
||||
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if isDir {
|
||||
// Watch the directory if it has not been watched before,
|
||||
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||
w.mu.Lock()
|
||||
|
||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||
// Store flags so this watch can be updated later
|
||||
w.dirFlags[name] = flags
|
||||
w.mu.Unlock()
|
||||
|
||||
if watchDir {
|
||||
if err := w.watchDirectoryFiles(name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// readEvents reads from kqueue and converts the received kevents into
|
||||
// Event values that it sends down the Events channel.
|
||||
func (w *Watcher) readEvents() {
|
||||
defer func() {
|
||||
err := unix.Close(w.kq)
|
||||
if err != nil {
|
||||
w.Errors <- err
|
||||
}
|
||||
unix.Close(w.closepipe[0])
|
||||
close(w.Events)
|
||||
close(w.Errors)
|
||||
}()
|
||||
|
||||
eventBuffer := make([]unix.Kevent_t, 10)
|
||||
for closed := false; !closed; {
|
||||
kevents, err := w.read(eventBuffer)
|
||||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||
if err != nil && err != unix.EINTR {
|
||||
if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
|
||||
closed = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Flush the events we received to the Events channel
|
||||
for _, kevent := range kevents {
|
||||
var (
|
||||
watchfd = int(kevent.Ident)
|
||||
mask = uint32(kevent.Fflags)
|
||||
)
|
||||
|
||||
// Shut down the loop when the pipe is closed, but only after all
|
||||
// other events have been processed.
|
||||
if watchfd == w.closepipe[0] {
|
||||
closed = true
|
||||
continue
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
path := w.paths[watchfd]
|
||||
w.mu.Unlock()
|
||||
|
||||
event := w.newEvent(path.name, mask)
|
||||
|
||||
if path.isDir && !event.Has(Remove) {
|
||||
// Double check to make sure the directory exists. This can
|
||||
// happen when we do a rm -fr on a recursively watched folders
|
||||
// and we receive a modification event first but the folder has
|
||||
// been deleted and later receive the delete event.
|
||||
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||
event.Op |= Remove
|
||||
}
|
||||
}
|
||||
|
||||
if event.Has(Rename) || event.Has(Remove) {
|
||||
w.Remove(event.Name)
|
||||
w.mu.Lock()
|
||||
delete(w.fileExists, event.Name)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if path.isDir && event.Has(Write) && !event.Has(Remove) {
|
||||
w.sendDirectoryChangeEvents(event.Name)
|
||||
} else {
|
||||
if !w.sendEvent(event) {
|
||||
closed = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if event.Has(Remove) {
|
||||
// Look for a file that may have overwritten this.
|
||||
// For example, mv f1 f2 will delete f2, then create f2.
|
||||
if path.isDir {
|
||||
fileDir := filepath.Clean(event.Name)
|
||||
w.mu.Lock()
|
||||
_, found := w.watches[fileDir]
|
||||
w.mu.Unlock()
|
||||
if found {
|
||||
// make sure the directory exists before we watch for changes. When we
|
||||
// do a recursive watch and perform rm -fr, the parent directory might
|
||||
// have gone missing, ignore the missing directory and let the
|
||||
// upcoming delete event remove the watch from the parent directory.
|
||||
if _, err := os.Lstat(fileDir); err == nil {
|
||||
w.sendDirectoryChangeEvents(fileDir)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filePath := filepath.Clean(event.Name)
|
||||
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fileInfo := range files {
|
||||
path := filepath.Join(dirPath, fileInfo.Name())
|
||||
|
||||
cleanPath, err := w.internalWatch(path, fileInfo)
|
||||
if err != nil {
|
||||
// No permission to read the file; that's not a problem: just skip.
|
||||
// But do add it to w.fileExists to prevent it from being picked up
|
||||
// as a "new" file later (it still shows up in the directory
|
||||
// listing).
|
||||
switch {
|
||||
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
|
||||
cleanPath = filepath.Clean(path)
|
||||
default:
|
||||
return fmt.Errorf("%q: %w", filepath.Join(dirPath, fileInfo.Name()), err)
|
||||
}
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.fileExists[cleanPath] = struct{}{}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Search the directory for new files and send an event for them.
|
||||
//
|
||||
// This functionality is to have the BSD watcher match the inotify, which sends
|
||||
// a create event for files created in a watched directory.
|
||||
func (w *Watcher) sendDirectoryChangeEvents(dir string) {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if !w.sendError(fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Search for new files
|
||||
for _, fi := range files {
|
||||
err := w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||
w.mu.Lock()
|
||||
_, doesExist := w.fileExists[filePath]
|
||||
w.mu.Unlock()
|
||||
if !doesExist {
|
||||
if !w.sendEvent(Event{Name: filePath, Op: Create}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.fileExists[filePath] = struct{}{}
|
||||
w.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||
if fileInfo.IsDir() {
|
||||
// mimic Linux providing delete events for subdirectories
|
||||
// but preserve the flags used if currently watching subdirectory
|
||||
w.mu.Lock()
|
||||
flags := w.dirFlags[name]
|
||||
w.mu.Unlock()
|
||||
|
||||
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||
return w.addWatch(name, flags)
|
||||
}
|
||||
|
||||
// watch file to mimic Linux inotify
|
||||
return w.addWatch(name, noteAllEvents)
|
||||
}
|
||||
|
||||
// Register events with the queue.
|
||||
func (w *Watcher) register(fds []int, flags int, fflags uint32) error {
|
||||
changes := make([]unix.Kevent_t, len(fds))
|
||||
for i, fd := range fds {
|
||||
// SetKevent converts int to the platform-specific types.
|
||||
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||
changes[i].Fflags = fflags
|
||||
}
|
||||
|
||||
// Register the events.
|
||||
success, err := unix.Kevent(w.kq, changes, nil, nil)
|
||||
if success == -1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// read retrieves pending events, or waits until an event occurs.
|
||||
func (w *Watcher) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
||||
n, err := unix.Kevent(w.kq, nil, events, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return events[0:n], nil
|
||||
}
|
66
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
Normal file
66
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
//go:build !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows
|
||||
// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct{}
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts monitoring the path for changes.
|
||||
//
|
||||
// A path can only be watched once; attempting to watch it more than once will
|
||||
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||
// added. A watch will be automatically removed if the path is deleted.
|
||||
//
|
||||
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||
// re-created, or if it's moved to a different filesystem.
|
||||
//
|
||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||
//
|
||||
// # Watching directories
|
||||
//
|
||||
// All files in a directory are monitored, including new files that are created
|
||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||
// non-recursive).
|
||||
//
|
||||
// # Watching files
|
||||
//
|
||||
// Watching individual files (rather than directories) is generally not
|
||||
// recommended as many tools update files atomically. Instead of "just" writing
|
||||
// to the file a temporary file will be written to first, and if successful the
|
||||
// temporary file is moved to to destination removing the original, or some
|
||||
// variant thereof. The watcher on the original file is now lost, as it no
|
||||
// longer exists.
|
||||
//
|
||||
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||
func (w *Watcher) Add(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops monitoring the path for changes.
|
||||
//
|
||||
// Directories are always removed non-recursively. For example, if you added
|
||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||
//
|
||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
return nil
|
||||
}
|
746
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
Normal file
746
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
Normal file
@@ -0,0 +1,746 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Watcher watches a set of paths, delivering events on a channel.
|
||||
//
|
||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||
// value).
|
||||
//
|
||||
// # Linux notes
|
||||
//
|
||||
// When a file is removed a Remove event won't be emitted until all file
|
||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||
//
|
||||
// fp := os.Open("file")
|
||||
// os.Remove("file") // Triggers Chmod
|
||||
// fp.Close() // Triggers Remove
|
||||
//
|
||||
// This is the event that inotify sends, so not much can be changed about this.
|
||||
//
|
||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||
// create is an "instance", and every path you add is a "watch".
|
||||
//
|
||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||
// /proc/sys/fs/inotify/max_user_instances
|
||||
//
|
||||
// To increase them you can use sysctl or write the value to the /proc file:
|
||||
//
|
||||
// # Default values on Linux 5.18
|
||||
// sysctl fs.inotify.max_user_watches=124983
|
||||
// sysctl fs.inotify.max_user_instances=128
|
||||
//
|
||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||
// your distro's documentation):
|
||||
//
|
||||
// fs.inotify.max_user_watches=124983
|
||||
// fs.inotify.max_user_instances=128
|
||||
//
|
||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||
// files" error.
|
||||
//
|
||||
// # kqueue notes (macOS, BSD)
|
||||
//
|
||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||
// so if you're watching a directory with five files then that's six file
|
||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||
// these platforms.
|
||||
//
|
||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||
// systems.
|
||||
//
|
||||
// # macOS notes
|
||||
//
|
||||
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||
//
|
||||
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||
type Watcher struct {
|
||||
// Events sends the filesystem change events.
|
||||
//
|
||||
// fsnotify can send the following events; a "path" here can refer to a
|
||||
// file, directory, symbolic link, or special file like a FIFO.
|
||||
//
|
||||
// fsnotify.Create A new path was created; this may be followed by one
|
||||
// or more Write events if data also gets written to a
|
||||
// file.
|
||||
//
|
||||
// fsnotify.Remove A path was removed.
|
||||
//
|
||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||
// old path as Event.Name, and a Create event will be
|
||||
// sent with the new name. Renames are only sent for
|
||||
// paths that are currently watched; e.g. moving an
|
||||
// unmonitored file into a monitored directory will
|
||||
// show up as just a Create. Similarly, renaming a file
|
||||
// to outside a monitored directory will show up as
|
||||
// only a Rename.
|
||||
//
|
||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||
// also trigger a Write. A single "write action"
|
||||
// initiated by the user may show up as one or multiple
|
||||
// writes, depending on when the system syncs things to
|
||||
// disk. For example when compiling a large Go program
|
||||
// you may get hundreds of Write events, so you
|
||||
// probably want to wait until you've stopped receiving
|
||||
// them (see the dedup example in cmd/fsnotify).
|
||||
//
|
||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||
// when a file is removed (or more accurately, when a
|
||||
// link to an inode is removed). On kqueue it's sent
|
||||
// and on kqueue when a file is truncated. On Windows
|
||||
// it's never sent.
|
||||
Events chan Event
|
||||
|
||||
// Errors sends any errors.
|
||||
Errors chan error
|
||||
|
||||
port windows.Handle // Handle to completion port
|
||||
input chan *input // Inputs to the reader are sent on this channel
|
||||
quit chan chan<- error
|
||||
|
||||
mu sync.Mutex // Protects access to watches, isClosed
|
||||
watches watchMap // Map of watches (key: i-number)
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
||||
}
|
||||
w := &Watcher{
|
||||
port: port,
|
||||
watches: make(watchMap),
|
||||
input: make(chan *input, 1),
|
||||
Events: make(chan Event, 50),
|
||||
Errors: make(chan error),
|
||||
quit: make(chan chan<- error, 1),
|
||||
}
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||
if mask == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
event := w.newEvent(name, uint32(mask))
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.quit <- ch
|
||||
case w.Events <- event:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *Watcher) sendError(err error) bool {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
case <-w.quit:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
w.mu.Unlock()
|
||||
|
||||
// Send "quit" message to the reader goroutine
|
||||
ch := make(chan error)
|
||||
w.quit <- ch
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-ch
|
||||
}
|
||||
|
||||
// Add starts monitoring the path for changes.
|
||||
//
|
||||
// A path can only be watched once; attempting to watch it more than once will
|
||||
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||
// added. A watch will be automatically removed if the path is deleted.
|
||||
//
|
||||
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||
// re-created, or if it's moved to a different filesystem.
|
||||
//
|
||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||
//
|
||||
// # Watching directories
|
||||
//
|
||||
// All files in a directory are monitored, including new files that are created
|
||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||
// non-recursive).
|
||||
//
|
||||
// # Watching files
|
||||
//
|
||||
// Watching individual files (rather than directories) is generally not
|
||||
// recommended as many tools update files atomically. Instead of "just" writing
|
||||
// to the file a temporary file will be written to first, and if successful the
|
||||
// temporary file is moved to to destination removing the original, or some
|
||||
// variant thereof. The watcher on the original file is now lost, as it no
|
||||
// longer exists.
|
||||
//
|
||||
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||
func (w *Watcher) Add(name string) error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return errors.New("watcher already closed")
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
in := &input{
|
||||
op: opAddWatch,
|
||||
path: filepath.Clean(name),
|
||||
flags: sysFSALLEVENTS,
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
// Remove stops monitoring the path for changes.
|
||||
//
|
||||
// Directories are always removed non-recursively. For example, if you added
|
||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||
//
|
||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
in := &input{
|
||||
op: opRemoveWatch,
|
||||
path: filepath.Clean(name),
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||
func (w *Watcher) WatchList() []string {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
entries := make([]string, 0, len(w.watches))
|
||||
for _, entry := range w.watches {
|
||||
for _, watchEntry := range entry {
|
||||
entries = append(entries, watchEntry.path)
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// These options are from the old golang.org/x/exp/winfsnotify, where you could
|
||||
// add various options to the watch. This has long since been removed.
|
||||
//
|
||||
// The "sys" in the name is misleading as they're not part of any "system".
|
||||
//
|
||||
// This should all be removed at some point, and just use windows.FILE_NOTIFY_*
|
||||
const (
|
||||
sysFSALLEVENTS = 0xfff
|
||||
sysFSATTRIB = 0x4
|
||||
sysFSCREATE = 0x100
|
||||
sysFSDELETE = 0x200
|
||||
sysFSDELETESELF = 0x400
|
||||
sysFSMODIFY = 0x2
|
||||
sysFSMOVE = 0xc0
|
||||
sysFSMOVEDFROM = 0x40
|
||||
sysFSMOVEDTO = 0x80
|
||||
sysFSMOVESELF = 0x800
|
||||
sysFSIGNORED = 0x8000
|
||||
)
|
||||
|
||||
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
const (
|
||||
opAddWatch = iota
|
||||
opRemoveWatch
|
||||
)
|
||||
|
||||
const (
|
||||
provisional uint64 = 1 << (32 + iota)
|
||||
)
|
||||
|
||||
type input struct {
|
||||
op int
|
||||
path string
|
||||
flags uint32
|
||||
reply chan error
|
||||
}
|
||||
|
||||
type inode struct {
|
||||
handle windows.Handle
|
||||
volume uint32
|
||||
index uint64
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
ov windows.Overlapped
|
||||
ino *inode // i-number
|
||||
path string // Directory path
|
||||
mask uint64 // Directory itself is being watched with these notify flags
|
||||
names map[string]uint64 // Map of names being watched and their notify flags
|
||||
rename string // Remembers the old name while renaming a file
|
||||
buf [65536]byte // 64K buffer
|
||||
}
|
||||
|
||||
type (
|
||||
indexMap map[uint64]*watch
|
||||
watchMap map[uint32]indexMap
|
||||
)
|
||||
|
||||
func (w *Watcher) wakeupReader() error {
|
||||
err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("PostQueuedCompletionStatus", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) getDir(pathname string) (dir string, err error) {
|
||||
attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
|
||||
if err != nil {
|
||||
return "", os.NewSyscallError("GetFileAttributes", err)
|
||||
}
|
||||
if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
dir = pathname
|
||||
} else {
|
||||
dir, _ = filepath.Split(pathname)
|
||||
dir = filepath.Clean(dir)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Watcher) getIno(path string) (ino *inode, err error) {
|
||||
h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
|
||||
windows.FILE_LIST_DIRECTORY,
|
||||
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
||||
nil, windows.OPEN_EXISTING,
|
||||
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("CreateFile", err)
|
||||
}
|
||||
|
||||
var fi windows.ByHandleFileInformation
|
||||
err = windows.GetFileInformationByHandle(h, &fi)
|
||||
if err != nil {
|
||||
windows.CloseHandle(h)
|
||||
return nil, os.NewSyscallError("GetFileInformationByHandle", err)
|
||||
}
|
||||
ino = &inode{
|
||||
handle: h,
|
||||
volume: fi.VolumeSerialNumber,
|
||||
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||
}
|
||||
return ino, nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) get(ino *inode) *watch {
|
||||
if i := m[ino.volume]; i != nil {
|
||||
return i[ino.index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) set(ino *inode, watch *watch) {
|
||||
i := m[ino.volume]
|
||||
if i == nil {
|
||||
i = make(indexMap)
|
||||
m[ino.volume] = i
|
||||
}
|
||||
i[ino.index] = watch
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||
dir, err := w.getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ino, err := w.getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watchEntry := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watchEntry == nil {
|
||||
_, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0)
|
||||
if err != nil {
|
||||
windows.CloseHandle(ino.handle)
|
||||
return os.NewSyscallError("CreateIoCompletionPort", err)
|
||||
}
|
||||
watchEntry = &watch{
|
||||
ino: ino,
|
||||
path: dir,
|
||||
names: make(map[string]uint64),
|
||||
}
|
||||
w.mu.Lock()
|
||||
w.watches.set(ino, watchEntry)
|
||||
w.mu.Unlock()
|
||||
flags |= provisional
|
||||
} else {
|
||||
windows.CloseHandle(ino.handle)
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask |= flags
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||
}
|
||||
|
||||
err = w.startRead(watchEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pathname == dir {
|
||||
watchEntry.mask &= ^provisional
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) remWatch(pathname string) error {
|
||||
dir, err := w.getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ino, err := w.getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
watch := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
|
||||
err = windows.CloseHandle(ino.handle)
|
||||
if err != nil {
|
||||
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||
}
|
||||
if watch == nil {
|
||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
||||
}
|
||||
if pathname == dir {
|
||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||
watch.mask = 0
|
||||
} else {
|
||||
name := filepath.Base(pathname)
|
||||
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
|
||||
return w.startRead(watch)
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) deleteWatch(watch *watch) {
|
||||
for name, mask := range watch.names {
|
||||
if mask&provisional == 0 {
|
||||
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||
}
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if watch.mask != 0 {
|
||||
if watch.mask&provisional == 0 {
|
||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||
}
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) startRead(watch *watch) error {
|
||||
err := windows.CancelIo(watch.ino.handle)
|
||||
if err != nil {
|
||||
w.sendError(os.NewSyscallError("CancelIo", err))
|
||||
w.deleteWatch(watch)
|
||||
}
|
||||
mask := w.toWindowsFlags(watch.mask)
|
||||
for _, m := range watch.names {
|
||||
mask |= w.toWindowsFlags(m)
|
||||
}
|
||||
if mask == 0 {
|
||||
err := windows.CloseHandle(watch.ino.handle)
|
||||
if err != nil {
|
||||
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||
}
|
||||
w.mu.Lock()
|
||||
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
rdErr := windows.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||
if rdErr != nil {
|
||||
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
|
||||
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||
// Watched directory was probably removed
|
||||
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||
err = nil
|
||||
}
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readEvents reads from the I/O completion port, converts the
|
||||
// received events into Event objects and sends them via the Events channel.
|
||||
// Entry point to the I/O thread.
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
n uint32
|
||||
key uintptr
|
||||
ov *windows.Overlapped
|
||||
)
|
||||
runtime.LockOSThread()
|
||||
|
||||
for {
|
||||
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
|
||||
// This error is handled after the watch == nil check below. NOTE: this
|
||||
// seems odd, note sure if it's correct.
|
||||
|
||||
watch := (*watch)(unsafe.Pointer(ov))
|
||||
if watch == nil {
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.mu.Lock()
|
||||
var indexes []indexMap
|
||||
for _, index := range w.watches {
|
||||
indexes = append(indexes, index)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, index := range indexes {
|
||||
for _, watch := range index {
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
}
|
||||
}
|
||||
|
||||
err := windows.CloseHandle(w.port)
|
||||
if err != nil {
|
||||
err = os.NewSyscallError("CloseHandle", err)
|
||||
}
|
||||
close(w.Events)
|
||||
close(w.Errors)
|
||||
ch <- err
|
||||
return
|
||||
case in := <-w.input:
|
||||
switch in.op {
|
||||
case opAddWatch:
|
||||
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||
case opRemoveWatch:
|
||||
in.reply <- w.remWatch(in.path)
|
||||
}
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch qErr {
|
||||
case windows.ERROR_MORE_DATA:
|
||||
if watch == nil {
|
||||
w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer"))
|
||||
} else {
|
||||
// The i/o succeeded but the buffer is full.
|
||||
// In theory we should be building up a full packet.
|
||||
// In practice we can get away with just carrying on.
|
||||
n = uint32(unsafe.Sizeof(watch.buf))
|
||||
}
|
||||
case windows.ERROR_ACCESS_DENIED:
|
||||
// Watched directory was probably removed
|
||||
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
continue
|
||||
case windows.ERROR_OPERATION_ABORTED:
|
||||
// CancelIo was called on this handle
|
||||
continue
|
||||
default:
|
||||
w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
|
||||
continue
|
||||
case nil:
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
for {
|
||||
if n == 0 {
|
||||
w.sendError(errors.New("short read in readEvents()"))
|
||||
break
|
||||
}
|
||||
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||
|
||||
// Create a buf that is the size of the path name
|
||||
size := int(raw.FileNameLength / 2)
|
||||
var buf []uint16
|
||||
// TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
||||
sh.Len = size
|
||||
sh.Cap = size
|
||||
name := windows.UTF16ToString(buf)
|
||||
fullname := filepath.Join(watch.path, name)
|
||||
|
||||
var mask uint64
|
||||
switch raw.Action {
|
||||
case windows.FILE_ACTION_REMOVED:
|
||||
mask = sysFSDELETESELF
|
||||
case windows.FILE_ACTION_MODIFIED:
|
||||
mask = sysFSMODIFY
|
||||
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
watch.rename = name
|
||||
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
// Update saved path of all sub-watches.
|
||||
old := filepath.Join(watch.path, watch.rename)
|
||||
w.mu.Lock()
|
||||
for _, watchMap := range w.watches {
|
||||
for _, ww := range watchMap {
|
||||
if strings.HasPrefix(ww.path, old) {
|
||||
ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old))
|
||||
}
|
||||
}
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if watch.names[watch.rename] != 0 {
|
||||
watch.names[name] |= watch.names[watch.rename]
|
||||
delete(watch.names, watch.rename)
|
||||
mask = sysFSMOVESELF
|
||||
}
|
||||
}
|
||||
|
||||
sendNameEvent := func() {
|
||||
w.sendEvent(fullname, watch.names[name]&mask)
|
||||
}
|
||||
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
sendNameEvent()
|
||||
}
|
||||
if raw.Action == windows.FILE_ACTION_REMOVED {
|
||||
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
|
||||
w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action))
|
||||
if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
fullname = filepath.Join(watch.path, watch.rename)
|
||||
sendNameEvent()
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
if raw.NextEntryOffset == 0 {
|
||||
break
|
||||
}
|
||||
offset += raw.NextEntryOffset
|
||||
|
||||
// Error!
|
||||
if offset >= n {
|
||||
w.sendError(errors.New(
|
||||
"Windows system assumed buffer larger than it is, events have likely been missed."))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.startRead(watch); err != nil {
|
||||
w.sendError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
|
||||
var m uint32
|
||||
if mask&sysFSMODIFY != 0 {
|
||||
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||
}
|
||||
if mask&sysFSATTRIB != 0 {
|
||||
m |= windows.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||
}
|
||||
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||
m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
|
||||
switch action {
|
||||
case windows.FILE_ACTION_ADDED:
|
||||
return sysFSCREATE
|
||||
case windows.FILE_ACTION_REMOVED:
|
||||
return sysFSDELETE
|
||||
case windows.FILE_ACTION_MODIFIED:
|
||||
return sysFSMODIFY
|
||||
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
return sysFSMOVEDFROM
|
||||
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
return sysFSMOVEDTO
|
||||
}
|
||||
return 0
|
||||
}
|
38
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
38
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
@@ -1,38 +0,0 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build solaris
|
||||
// +build solaris
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
return nil
|
||||
}
|
96
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
96
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
@@ -1,29 +1,37 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !plan9
|
||||
// +build !plan9
|
||||
|
||||
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||
// Package fsnotify provides a cross-platform interface for file system
|
||||
// notifications.
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Event represents a single file system notification.
|
||||
// Event represents a file system notification.
|
||||
type Event struct {
|
||||
Name string // Relative path to the file or directory.
|
||||
Op Op // File operation that triggered the event.
|
||||
// Path to the file or directory.
|
||||
//
|
||||
// Paths are relative to the input; for example with Add("dir") the Name
|
||||
// will be set to "dir/file" if you create that file, but if you use
|
||||
// Add("/path/to/dir") it will be "/path/to/dir/file".
|
||||
Name string
|
||||
|
||||
// File operation that triggered the event.
|
||||
//
|
||||
// This is a bitmask and some systems may send multiple operations at once.
|
||||
// Use the Event.Has() method instead of comparing with ==.
|
||||
Op Op
|
||||
}
|
||||
|
||||
// Op describes a set of file operations.
|
||||
type Op uint32
|
||||
|
||||
// These are the generalized file operations that can trigger a notification.
|
||||
// The operations fsnotify can trigger; see the documentation on [Watcher] for a
|
||||
// full description, and check them with [Event.Has].
|
||||
const (
|
||||
Create Op = 1 << iota
|
||||
Write
|
||||
@@ -32,38 +40,42 @@ const (
|
||||
Chmod
|
||||
)
|
||||
|
||||
func (op Op) String() string {
|
||||
// Use a buffer for efficient string concatenation
|
||||
var buffer bytes.Buffer
|
||||
|
||||
if op&Create == Create {
|
||||
buffer.WriteString("|CREATE")
|
||||
}
|
||||
if op&Remove == Remove {
|
||||
buffer.WriteString("|REMOVE")
|
||||
}
|
||||
if op&Write == Write {
|
||||
buffer.WriteString("|WRITE")
|
||||
}
|
||||
if op&Rename == Rename {
|
||||
buffer.WriteString("|RENAME")
|
||||
}
|
||||
if op&Chmod == Chmod {
|
||||
buffer.WriteString("|CHMOD")
|
||||
}
|
||||
if buffer.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
return buffer.String()[1:] // Strip leading pipe
|
||||
}
|
||||
|
||||
// String returns a string representation of the event in the form
|
||||
// "file: REMOVE|WRITE|..."
|
||||
func (e Event) String() string {
|
||||
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
|
||||
}
|
||||
|
||||
// Common errors that can be reported by a watcher
|
||||
var (
|
||||
ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||
ErrNonExistentWatch = errors.New("can't remove non-existent watcher")
|
||||
ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||
)
|
||||
|
||||
func (op Op) String() string {
|
||||
var b strings.Builder
|
||||
if op.Has(Create) {
|
||||
b.WriteString("|CREATE")
|
||||
}
|
||||
if op.Has(Remove) {
|
||||
b.WriteString("|REMOVE")
|
||||
}
|
||||
if op.Has(Write) {
|
||||
b.WriteString("|WRITE")
|
||||
}
|
||||
if op.Has(Rename) {
|
||||
b.WriteString("|RENAME")
|
||||
}
|
||||
if op.Has(Chmod) {
|
||||
b.WriteString("|CHMOD")
|
||||
}
|
||||
if b.Len() == 0 {
|
||||
return "[no events]"
|
||||
}
|
||||
return b.String()[1:]
|
||||
}
|
||||
|
||||
// Has reports if this operation has the given operation.
|
||||
func (o Op) Has(h Op) bool { return o&h == h }
|
||||
|
||||
// Has reports if this event has the given operation.
|
||||
func (e Event) Has(op Op) bool { return e.Op.Has(op) }
|
||||
|
||||
// String returns a string representation of the event with their path.
|
||||
func (e Event) String() string {
|
||||
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
|
||||
}
|
||||
|
36
vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go
generated
vendored
36
vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go
generated
vendored
@@ -1,36 +0,0 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows
|
||||
// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct{}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
return nil
|
||||
}
|
351
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
351
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
@@ -1,351 +0,0 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
mu sync.Mutex // Map access
|
||||
fd int
|
||||
poller *fdPoller
|
||||
watches map[string]*watch // Map of inotify watches (key: path)
|
||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
doneResp chan struct{} // Channel to respond to Close
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
// Create inotify fd
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
|
||||
if fd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
// Create epoll
|
||||
poller, err := newFdPoller(fd)
|
||||
if err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
w := &Watcher{
|
||||
fd: fd,
|
||||
poller: poller,
|
||||
watches: make(map[string]*watch),
|
||||
paths: make(map[int]string),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
doneResp: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *Watcher) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
if w.isClosed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||
close(w.done)
|
||||
|
||||
// Wake up goroutine
|
||||
w.poller.wake()
|
||||
|
||||
// Wait for goroutine to close
|
||||
<-w.doneResp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
if w.isClosed() {
|
||||
return errors.New("inotify instance already closed")
|
||||
}
|
||||
|
||||
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
|
||||
var flags uint32 = agnosticEvents
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watchEntry := w.watches[name]
|
||||
if watchEntry != nil {
|
||||
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||
}
|
||||
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||
if wd == -1 {
|
||||
return errno
|
||||
}
|
||||
|
||||
if watchEntry == nil {
|
||||
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||
w.paths[wd] = name
|
||||
} else {
|
||||
watchEntry.wd = uint32(wd)
|
||||
watchEntry.flags = flags
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
|
||||
// Fetch the watch.
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watch, ok := w.watches[name]
|
||||
|
||||
// Remove it from inotify.
|
||||
if !ok {
|
||||
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
||||
}
|
||||
|
||||
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||
// error, we need to clean up our internal state to ensure it matches
|
||||
// inotify's kernel state.
|
||||
delete(w.paths, int(watch.wd))
|
||||
delete(w.watches, name)
|
||||
|
||||
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||
// the inotify will already have been removed.
|
||||
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||
// by another thread and we have not received IN_IGNORE event.
|
||||
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||
if success == -1 {
|
||||
// TODO: Perhaps it's not helpful to return an error here in every case.
|
||||
// the only two possible errors are:
|
||||
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
||||
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
||||
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
||||
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
||||
return errno
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WatchList returns the directories and files that are being monitered.
|
||||
func (w *Watcher) WatchList() []string {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
entries := make([]string, 0, len(w.watches))
|
||||
for pathname := range w.watches {
|
||||
entries = append(entries, pathname)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
}
|
||||
|
||||
// readEvents reads from the inotify file descriptor, converts the
|
||||
// received events into Event objects and sends them via the Events channel
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
n int // Number of bytes read with read()
|
||||
errno error // Syscall errno
|
||||
ok bool // For poller.wait
|
||||
)
|
||||
|
||||
defer close(w.doneResp)
|
||||
defer close(w.Errors)
|
||||
defer close(w.Events)
|
||||
defer unix.Close(w.fd)
|
||||
defer w.poller.close()
|
||||
|
||||
for {
|
||||
// See if we have been closed.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
ok, errno = w.poller.wait()
|
||||
if errno != nil {
|
||||
select {
|
||||
case w.Errors <- errno:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
n, errno = unix.Read(w.fd, buf[:])
|
||||
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
||||
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
||||
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
||||
if errno == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
|
||||
// unix.Read might have been woken up by Close. If so, we're done.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
if n < unix.SizeofInotifyEvent {
|
||||
var err error
|
||||
if n == 0 {
|
||||
// If EOF is received. This should really never happen.
|
||||
err = io.EOF
|
||||
} else if n < 0 {
|
||||
// If an error occurred while reading.
|
||||
err = errno
|
||||
} else {
|
||||
// Read was too short.
|
||||
err = errors.New("notify: short read in readEvents()")
|
||||
}
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
|
||||
mask := uint32(raw.Mask)
|
||||
nameLen := uint32(raw.Len)
|
||||
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
select {
|
||||
case w.Errors <- ErrEventOverflow:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If the event happened to the watched directory or the watched file, the kernel
|
||||
// doesn't append the filename to the event, but we would like to always fill the
|
||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||
// the "paths" map.
|
||||
w.mu.Lock()
|
||||
name, ok := w.paths[int(raw.Wd)]
|
||||
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||
// with the inotify kernel state which has already deleted the watch
|
||||
// automatically.
|
||||
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
delete(w.paths, int(raw.Wd))
|
||||
delete(w.watches, name)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if nameLen > 0 {
|
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
event := newEvent(name, mask)
|
||||
|
||||
// Send the events that are not ignored on the events channel
|
||||
if !event.ignoreLinux(mask) {
|
||||
select {
|
||||
case w.Events <- event:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.SizeofInotifyEvent + nameLen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Certain types of events can be "ignored" and not sent over the Events
|
||||
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
||||
// against files that do not exist.
|
||||
func (e *Event) ignoreLinux(mask uint32) bool {
|
||||
// Ignore anything the inotify API says to ignore
|
||||
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the event is not a DELETE or RENAME, the file must exist.
|
||||
// Otherwise the event is ignored.
|
||||
// *Note*: this was put in place because it was seen that a MODIFY
|
||||
// event was sent after the DELETE. This ignores that MODIFY and
|
||||
// assumes a DELETE will come or has come if the file doesn't exist.
|
||||
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
|
||||
_, statErr := os.Lstat(e.Name)
|
||||
return os.IsNotExist(statErr)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
@@ -1,187 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type fdPoller struct {
|
||||
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||
epfd int // Epoll file descriptor
|
||||
pipe [2]int // Pipe for waking up
|
||||
}
|
||||
|
||||
func emptyPoller(fd int) *fdPoller {
|
||||
poller := new(fdPoller)
|
||||
poller.fd = fd
|
||||
poller.epfd = -1
|
||||
poller.pipe[0] = -1
|
||||
poller.pipe[1] = -1
|
||||
return poller
|
||||
}
|
||||
|
||||
// Create a new inotify poller.
|
||||
// This creates an inotify handler, and an epoll handler.
|
||||
func newFdPoller(fd int) (*fdPoller, error) {
|
||||
var errno error
|
||||
poller := emptyPoller(fd)
|
||||
defer func() {
|
||||
if errno != nil {
|
||||
poller.close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Create epoll fd
|
||||
poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
|
||||
if poller.epfd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
// Register inotify fd with epoll
|
||||
event := unix.EpollEvent{
|
||||
Fd: int32(poller.fd),
|
||||
Events: unix.EPOLLIN,
|
||||
}
|
||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
// Register pipe fd with epoll
|
||||
event = unix.EpollEvent{
|
||||
Fd: int32(poller.pipe[0]),
|
||||
Events: unix.EPOLLIN,
|
||||
}
|
||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
return poller, nil
|
||||
}
|
||||
|
||||
// Wait using epoll.
|
||||
// Returns true if something is ready to be read,
|
||||
// false if there is not.
|
||||
func (poller *fdPoller) wait() (bool, error) {
|
||||
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
||||
// I don't know whether epoll_wait returns the number of events returned,
|
||||
// or the total number of events ready.
|
||||
// I decided to catch both by making the buffer one larger than the maximum.
|
||||
events := make([]unix.EpollEvent, 7)
|
||||
for {
|
||||
n, errno := unix.EpollWait(poller.epfd, events, -1)
|
||||
if n == -1 {
|
||||
if errno == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return false, errno
|
||||
}
|
||||
if n == 0 {
|
||||
// If there are no events, try again.
|
||||
continue
|
||||
}
|
||||
if n > 6 {
|
||||
// This should never happen. More events were returned than should be possible.
|
||||
return false, errors.New("epoll_wait returned more events than I know what to do with")
|
||||
}
|
||||
ready := events[:n]
|
||||
epollhup := false
|
||||
epollerr := false
|
||||
epollin := false
|
||||
for _, event := range ready {
|
||||
if event.Fd == int32(poller.fd) {
|
||||
if event.Events&unix.EPOLLHUP != 0 {
|
||||
// This should not happen, but if it does, treat it as a wakeup.
|
||||
epollhup = true
|
||||
}
|
||||
if event.Events&unix.EPOLLERR != 0 {
|
||||
// If an error is waiting on the file descriptor, we should pretend
|
||||
// something is ready to read, and let unix.Read pick up the error.
|
||||
epollerr = true
|
||||
}
|
||||
if event.Events&unix.EPOLLIN != 0 {
|
||||
// There is data to read.
|
||||
epollin = true
|
||||
}
|
||||
}
|
||||
if event.Fd == int32(poller.pipe[0]) {
|
||||
if event.Events&unix.EPOLLHUP != 0 {
|
||||
// Write pipe descriptor was closed, by us. This means we're closing down the
|
||||
// watcher, and we should wake up.
|
||||
}
|
||||
if event.Events&unix.EPOLLERR != 0 {
|
||||
// If an error is waiting on the pipe file descriptor.
|
||||
// This is an absolute mystery, and should never ever happen.
|
||||
return false, errors.New("Error on the pipe descriptor.")
|
||||
}
|
||||
if event.Events&unix.EPOLLIN != 0 {
|
||||
// This is a regular wakeup, so we have to clear the buffer.
|
||||
err := poller.clearWake()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if epollhup || epollerr || epollin {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Close the write end of the poller.
|
||||
func (poller *fdPoller) wake() error {
|
||||
buf := make([]byte, 1)
|
||||
n, errno := unix.Write(poller.pipe[1], buf)
|
||||
if n == -1 {
|
||||
if errno == unix.EAGAIN {
|
||||
// Buffer is full, poller will wake.
|
||||
return nil
|
||||
}
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (poller *fdPoller) clearWake() error {
|
||||
// You have to be woken up a LOT in order to get to 100!
|
||||
buf := make([]byte, 100)
|
||||
n, errno := unix.Read(poller.pipe[0], buf)
|
||||
if n == -1 {
|
||||
if errno == unix.EAGAIN {
|
||||
// Buffer is empty, someone else cleared our wake.
|
||||
return nil
|
||||
}
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close all poller file descriptors, but not the one passed to it.
|
||||
func (poller *fdPoller) close() {
|
||||
if poller.pipe[1] != -1 {
|
||||
unix.Close(poller.pipe[1])
|
||||
}
|
||||
if poller.pipe[0] != -1 {
|
||||
unix.Close(poller.pipe[0])
|
||||
}
|
||||
if poller.epfd != -1 {
|
||||
unix.Close(poller.epfd)
|
||||
}
|
||||
}
|
535
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
535
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
@@ -1,535 +0,0 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||
// +build freebsd openbsd netbsd dragonfly darwin
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
|
||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||
|
||||
mu sync.Mutex // Protects access to watcher data
|
||||
watches map[string]int // Map of watched file descriptors (key: path).
|
||||
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
||||
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
type pathInfo struct {
|
||||
name string
|
||||
isDir bool
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
kq, err := kqueue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := &Watcher{
|
||||
kq: kq,
|
||||
watches: make(map[string]int),
|
||||
dirFlags: make(map[string]uint32),
|
||||
paths: make(map[int]pathInfo),
|
||||
fileExists: make(map[string]bool),
|
||||
externalWatches: make(map[string]bool),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// copy paths to remove while locked
|
||||
var pathsToRemove = make([]string, 0, len(w.watches))
|
||||
for name := range w.watches {
|
||||
pathsToRemove = append(pathsToRemove, name)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
// unlock before calling Remove, which also locks
|
||||
|
||||
for _, name := range pathsToRemove {
|
||||
w.Remove(name)
|
||||
}
|
||||
|
||||
// send a "quit" message to the reader goroutine
|
||||
close(w.done)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
w.mu.Lock()
|
||||
w.externalWatches[name] = true
|
||||
w.mu.Unlock()
|
||||
_, err := w.addWatch(name, noteAllEvents)
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
w.mu.Lock()
|
||||
watchfd, ok := w.watches[name]
|
||||
w.mu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
||||
}
|
||||
|
||||
const registerRemove = unix.EV_DELETE
|
||||
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unix.Close(watchfd)
|
||||
|
||||
w.mu.Lock()
|
||||
isDir := w.paths[watchfd].isDir
|
||||
delete(w.watches, name)
|
||||
delete(w.paths, watchfd)
|
||||
delete(w.dirFlags, name)
|
||||
w.mu.Unlock()
|
||||
|
||||
// Find all watched paths that are in this directory that are not external.
|
||||
if isDir {
|
||||
var pathsToRemove []string
|
||||
w.mu.Lock()
|
||||
for _, path := range w.paths {
|
||||
wdir, _ := filepath.Split(path.name)
|
||||
if filepath.Clean(wdir) == name {
|
||||
if !w.externalWatches[path.name] {
|
||||
pathsToRemove = append(pathsToRemove, path.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, name := range pathsToRemove {
|
||||
// Since these are internal, not much sense in propagating error
|
||||
// to the user, as that will just confuse them with an error about
|
||||
// a path they did not explicitly watch themselves.
|
||||
w.Remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WatchList returns the directories and files that are being monitered.
|
||||
func (w *Watcher) WatchList() []string {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
entries := make([]string, 0, len(w.watches))
|
||||
for pathname := range w.watches {
|
||||
entries = append(entries, pathname)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||
|
||||
// keventWaitTime to block on each read from kevent
|
||||
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
||||
|
||||
// addWatch adds name to the watched file set.
|
||||
// The flags are interpreted as described in kevent(2).
|
||||
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||
var isDir bool
|
||||
// Make ./name and name equivalent
|
||||
name = filepath.Clean(name)
|
||||
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return "", errors.New("kevent instance already closed")
|
||||
}
|
||||
watchfd, alreadyWatching := w.watches[name]
|
||||
// We already have a watch, but we can still override flags.
|
||||
if alreadyWatching {
|
||||
isDir = w.paths[watchfd].isDir
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if !alreadyWatching {
|
||||
fi, err := os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Don't watch sockets.
|
||||
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Don't watch named pipes.
|
||||
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Follow Symlinks
|
||||
// Unfortunately, Linux can add bogus symlinks to watch list without
|
||||
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
||||
// consistency, we will act like everything is fine. There will simply
|
||||
// be no file events for broken symlinks.
|
||||
// Hence the returns of nil on errors.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
name, err = filepath.EvalSymlinks(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
_, alreadyWatching = w.watches[name]
|
||||
w.mu.Unlock()
|
||||
|
||||
if alreadyWatching {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
fi, err = os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
watchfd, err = unix.Open(name, openMode, 0700)
|
||||
if watchfd == -1 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
isDir = fi.IsDir()
|
||||
}
|
||||
|
||||
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
|
||||
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
||||
unix.Close(watchfd)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !alreadyWatching {
|
||||
w.mu.Lock()
|
||||
w.watches[name] = watchfd
|
||||
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if isDir {
|
||||
// Watch the directory if it has not been watched before,
|
||||
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||
w.mu.Lock()
|
||||
|
||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||
// Store flags so this watch can be updated later
|
||||
w.dirFlags[name] = flags
|
||||
w.mu.Unlock()
|
||||
|
||||
if watchDir {
|
||||
if err := w.watchDirectoryFiles(name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// readEvents reads from kqueue and converts the received kevents into
|
||||
// Event values that it sends down the Events channel.
|
||||
func (w *Watcher) readEvents() {
|
||||
eventBuffer := make([]unix.Kevent_t, 10)
|
||||
|
||||
loop:
|
||||
for {
|
||||
// See if there is a message on the "done" channel
|
||||
select {
|
||||
case <-w.done:
|
||||
break loop
|
||||
default:
|
||||
}
|
||||
|
||||
// Get new events
|
||||
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
||||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||
if err != nil && err != unix.EINTR {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
break loop
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Flush the events we received to the Events channel
|
||||
for len(kevents) > 0 {
|
||||
kevent := &kevents[0]
|
||||
watchfd := int(kevent.Ident)
|
||||
mask := uint32(kevent.Fflags)
|
||||
w.mu.Lock()
|
||||
path := w.paths[watchfd]
|
||||
w.mu.Unlock()
|
||||
event := newEvent(path.name, mask)
|
||||
|
||||
if path.isDir && !(event.Op&Remove == Remove) {
|
||||
// Double check to make sure the directory exists. This can happen when
|
||||
// we do a rm -fr on a recursively watched folders and we receive a
|
||||
// modification event first but the folder has been deleted and later
|
||||
// receive the delete event
|
||||
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||
// mark is as delete event
|
||||
event.Op |= Remove
|
||||
}
|
||||
}
|
||||
|
||||
if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
||||
w.Remove(event.Name)
|
||||
w.mu.Lock()
|
||||
delete(w.fileExists, event.Name)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||
w.sendDirectoryChangeEvents(event.Name)
|
||||
} else {
|
||||
// Send the event on the Events channel.
|
||||
select {
|
||||
case w.Events <- event:
|
||||
case <-w.done:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if event.Op&Remove == Remove {
|
||||
// Look for a file that may have overwritten this.
|
||||
// For example, mv f1 f2 will delete f2, then create f2.
|
||||
if path.isDir {
|
||||
fileDir := filepath.Clean(event.Name)
|
||||
w.mu.Lock()
|
||||
_, found := w.watches[fileDir]
|
||||
w.mu.Unlock()
|
||||
if found {
|
||||
// make sure the directory exists before we watch for changes. When we
|
||||
// do a recursive watch and perform rm -fr, the parent directory might
|
||||
// have gone missing, ignore the missing directory and let the
|
||||
// upcoming delete event remove the watch from the parent directory.
|
||||
if _, err := os.Lstat(fileDir); err == nil {
|
||||
w.sendDirectoryChangeEvents(fileDir)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filePath := filepath.Clean(event.Name)
|
||||
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next event
|
||||
kevents = kevents[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup
|
||||
err := unix.Close(w.kq)
|
||||
if err != nil {
|
||||
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
close(w.Events)
|
||||
close(w.Errors)
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func newCreateEvent(name string) Event {
|
||||
return Event{Name: name, Op: Create}
|
||||
}
|
||||
|
||||
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fileInfo := range files {
|
||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.fileExists[filePath] = true
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendDirectoryEvents searches the directory for newly created files
|
||||
// and sends them over the event channel. This functionality is to have
|
||||
// the BSD version of fsnotify match Linux inotify which provides a
|
||||
// create event for files created in a watched directory.
|
||||
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Search for new files
|
||||
for _, fileInfo := range files {
|
||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||
w.mu.Lock()
|
||||
_, doesExist := w.fileExists[filePath]
|
||||
w.mu.Unlock()
|
||||
if !doesExist {
|
||||
// Send create event
|
||||
select {
|
||||
case w.Events <- newCreateEvent(filePath):
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.fileExists[filePath] = true
|
||||
w.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||
if fileInfo.IsDir() {
|
||||
// mimic Linux providing delete events for subdirectories
|
||||
// but preserve the flags used if currently watching subdirectory
|
||||
w.mu.Lock()
|
||||
flags := w.dirFlags[name]
|
||||
w.mu.Unlock()
|
||||
|
||||
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||
return w.addWatch(name, flags)
|
||||
}
|
||||
|
||||
// watch file to mimic Linux inotify
|
||||
return w.addWatch(name, noteAllEvents)
|
||||
}
|
||||
|
||||
// kqueue creates a new kernel event queue and returns a descriptor.
|
||||
func kqueue() (kq int, err error) {
|
||||
kq, err = unix.Kqueue()
|
||||
if kq == -1 {
|
||||
return kq, err
|
||||
}
|
||||
return kq, nil
|
||||
}
|
||||
|
||||
// register events with the queue
|
||||
func register(kq int, fds []int, flags int, fflags uint32) error {
|
||||
changes := make([]unix.Kevent_t, len(fds))
|
||||
|
||||
for i, fd := range fds {
|
||||
// SetKevent converts int to the platform-specific types:
|
||||
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||
changes[i].Fflags = fflags
|
||||
}
|
||||
|
||||
// register the events
|
||||
success, err := unix.Kevent(kq, changes, nil, nil)
|
||||
if success == -1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// read retrieves pending events, or waits until an event occurs.
|
||||
// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
||||
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
|
||||
n, err := unix.Kevent(kq, nil, events, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return events[0:n], nil
|
||||
}
|
||||
|
||||
// durationToTimespec prepares a timeout value
|
||||
func durationToTimespec(d time.Duration) unix.Timespec {
|
||||
return unix.NsecToTimespec(d.Nanoseconds())
|
||||
}
|
208
vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
generated
vendored
Normal file
208
vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env zsh
|
||||
[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
|
||||
setopt err_exit no_unset pipefail extended_glob
|
||||
|
||||
# Simple script to update the godoc comments on all watchers. Probably took me
|
||||
# more time to write this than doing it manually, but ah well 🙃
|
||||
|
||||
watcher=$(<<EOF
|
||||
// Watcher watches a set of paths, delivering events on a channel.
|
||||
//
|
||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||
// value).
|
||||
//
|
||||
// # Linux notes
|
||||
//
|
||||
// When a file is removed a Remove event won't be emitted until all file
|
||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||
//
|
||||
// fp := os.Open("file")
|
||||
// os.Remove("file") // Triggers Chmod
|
||||
// fp.Close() // Triggers Remove
|
||||
//
|
||||
// This is the event that inotify sends, so not much can be changed about this.
|
||||
//
|
||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||
// create is an "instance", and every path you add is a "watch".
|
||||
//
|
||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||
// /proc/sys/fs/inotify/max_user_instances
|
||||
//
|
||||
// To increase them you can use sysctl or write the value to the /proc file:
|
||||
//
|
||||
// # Default values on Linux 5.18
|
||||
// sysctl fs.inotify.max_user_watches=124983
|
||||
// sysctl fs.inotify.max_user_instances=128
|
||||
//
|
||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||
// your distro's documentation):
|
||||
//
|
||||
// fs.inotify.max_user_watches=124983
|
||||
// fs.inotify.max_user_instances=128
|
||||
//
|
||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||
// files" error.
|
||||
//
|
||||
// # kqueue notes (macOS, BSD)
|
||||
//
|
||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||
// so if you're watching a directory with five files then that's six file
|
||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||
// these platforms.
|
||||
//
|
||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||
// systems.
|
||||
//
|
||||
// # macOS notes
|
||||
//
|
||||
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||
//
|
||||
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||
EOF
|
||||
)
|
||||
|
||||
new=$(<<EOF
|
||||
// NewWatcher creates a new Watcher.
|
||||
EOF
|
||||
)
|
||||
|
||||
add=$(<<EOF
|
||||
// Add starts monitoring the path for changes.
|
||||
//
|
||||
// A path can only be watched once; attempting to watch it more than once will
|
||||
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||
// added. A watch will be automatically removed if the path is deleted.
|
||||
//
|
||||
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||
// re-created, or if it's moved to a different filesystem.
|
||||
//
|
||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||
//
|
||||
// # Watching directories
|
||||
//
|
||||
// All files in a directory are monitored, including new files that are created
|
||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||
// non-recursive).
|
||||
//
|
||||
// # Watching files
|
||||
//
|
||||
// Watching individual files (rather than directories) is generally not
|
||||
// recommended as many tools update files atomically. Instead of "just" writing
|
||||
// to the file a temporary file will be written to first, and if successful the
|
||||
// temporary file is moved to to destination removing the original, or some
|
||||
// variant thereof. The watcher on the original file is now lost, as it no
|
||||
// longer exists.
|
||||
//
|
||||
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||
EOF
|
||||
)
|
||||
|
||||
remove=$(<<EOF
|
||||
// Remove stops monitoring the path for changes.
|
||||
//
|
||||
// Directories are always removed non-recursively. For example, if you added
|
||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||
//
|
||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||
EOF
|
||||
)
|
||||
|
||||
close=$(<<EOF
|
||||
// Close removes all watches and closes the events channel.
|
||||
EOF
|
||||
)
|
||||
|
||||
watchlist=$(<<EOF
|
||||
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||
EOF
|
||||
)
|
||||
|
||||
events=$(<<EOF
|
||||
// Events sends the filesystem change events.
|
||||
//
|
||||
// fsnotify can send the following events; a "path" here can refer to a
|
||||
// file, directory, symbolic link, or special file like a FIFO.
|
||||
//
|
||||
// fsnotify.Create A new path was created; this may be followed by one
|
||||
// or more Write events if data also gets written to a
|
||||
// file.
|
||||
//
|
||||
// fsnotify.Remove A path was removed.
|
||||
//
|
||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||
// old path as Event.Name, and a Create event will be
|
||||
// sent with the new name. Renames are only sent for
|
||||
// paths that are currently watched; e.g. moving an
|
||||
// unmonitored file into a monitored directory will
|
||||
// show up as just a Create. Similarly, renaming a file
|
||||
// to outside a monitored directory will show up as
|
||||
// only a Rename.
|
||||
//
|
||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||
// also trigger a Write. A single "write action"
|
||||
// initiated by the user may show up as one or multiple
|
||||
// writes, depending on when the system syncs things to
|
||||
// disk. For example when compiling a large Go program
|
||||
// you may get hundreds of Write events, so you
|
||||
// probably want to wait until you've stopped receiving
|
||||
// them (see the dedup example in cmd/fsnotify).
|
||||
//
|
||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||
// when a file is removed (or more accurately, when a
|
||||
// link to an inode is removed). On kqueue it's sent
|
||||
// and on kqueue when a file is truncated. On Windows
|
||||
// it's never sent.
|
||||
EOF
|
||||
)
|
||||
|
||||
errors=$(<<EOF
|
||||
// Errors sends any errors.
|
||||
EOF
|
||||
)
|
||||
|
||||
set-cmt() {
|
||||
local pat=$1
|
||||
local cmt=$2
|
||||
|
||||
IFS=$'\n' local files=($(grep -n $pat backend_*~*_test.go))
|
||||
for f in $files; do
|
||||
IFS=':' local fields=($=f)
|
||||
local file=$fields[1]
|
||||
local end=$(( $fields[2] - 1 ))
|
||||
|
||||
# Find start of comment.
|
||||
local start=0
|
||||
IFS=$'\n' local lines=($(head -n$end $file))
|
||||
for (( i = 1; i <= $#lines; i++ )); do
|
||||
local line=$lines[-$i]
|
||||
if ! grep -q '^[[:space:]]*//' <<<$line; then
|
||||
start=$(( end - (i - 2) ))
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
head -n $(( start - 1 )) $file >/tmp/x
|
||||
print -r -- $cmt >>/tmp/x
|
||||
tail -n+$(( end + 1 )) $file >>/tmp/x
|
||||
mv /tmp/x $file
|
||||
done
|
||||
}
|
||||
|
||||
set-cmt '^type Watcher struct ' $watcher
|
||||
set-cmt '^func NewWatcher(' $new
|
||||
set-cmt '^func (w \*Watcher) Add(' $add
|
||||
set-cmt '^func (w \*Watcher) Remove(' $remove
|
||||
set-cmt '^func (w \*Watcher) Close(' $close
|
||||
set-cmt '^func (w \*Watcher) WatchList(' $watchlist
|
||||
set-cmt '^[[:space:]]*Events *chan Event$' $events
|
||||
set-cmt '^[[:space:]]*Errors *chan error$' $errors
|
@@ -1,7 +1,3 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||
// +build freebsd openbsd netbsd dragonfly
|
||||
|
@@ -1,7 +1,3 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
586
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
586
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
@@ -1,586 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
mu sync.Mutex // Map access
|
||||
port syscall.Handle // Handle to completion port
|
||||
watches watchMap // Map of watches (key: i-number)
|
||||
input chan *input // Inputs to the reader are sent on this channel
|
||||
quit chan chan<- error
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
||||
if e != nil {
|
||||
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
||||
}
|
||||
w := &Watcher{
|
||||
port: port,
|
||||
watches: make(watchMap),
|
||||
input: make(chan *input, 1),
|
||||
Events: make(chan Event, 50),
|
||||
Errors: make(chan error),
|
||||
quit: make(chan chan<- error, 1),
|
||||
}
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
if w.isClosed {
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// Send "quit" message to the reader goroutine
|
||||
ch := make(chan error)
|
||||
w.quit <- ch
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-ch
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
if w.isClosed {
|
||||
return errors.New("watcher already closed")
|
||||
}
|
||||
in := &input{
|
||||
op: opAddWatch,
|
||||
path: filepath.Clean(name),
|
||||
flags: sysFSALLEVENTS,
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
in := &input{
|
||||
op: opRemoveWatch,
|
||||
path: filepath.Clean(name),
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
// WatchList returns the directories and files that are being monitered.
|
||||
func (w *Watcher) WatchList() []string {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
entries := make([]string, 0, len(w.watches))
|
||||
for _, entry := range w.watches {
|
||||
for _, watchEntry := range entry {
|
||||
entries = append(entries, watchEntry.path)
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
const (
|
||||
// Options for AddWatch
|
||||
sysFSONESHOT = 0x80000000
|
||||
sysFSONLYDIR = 0x1000000
|
||||
|
||||
// Events
|
||||
sysFSACCESS = 0x1
|
||||
sysFSALLEVENTS = 0xfff
|
||||
sysFSATTRIB = 0x4
|
||||
sysFSCLOSE = 0x18
|
||||
sysFSCREATE = 0x100
|
||||
sysFSDELETE = 0x200
|
||||
sysFSDELETESELF = 0x400
|
||||
sysFSMODIFY = 0x2
|
||||
sysFSMOVE = 0xc0
|
||||
sysFSMOVEDFROM = 0x40
|
||||
sysFSMOVEDTO = 0x80
|
||||
sysFSMOVESELF = 0x800
|
||||
|
||||
// Special events
|
||||
sysFSIGNORED = 0x8000
|
||||
sysFSQOVERFLOW = 0x4000
|
||||
)
|
||||
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
const (
|
||||
opAddWatch = iota
|
||||
opRemoveWatch
|
||||
)
|
||||
|
||||
const (
|
||||
provisional uint64 = 1 << (32 + iota)
|
||||
)
|
||||
|
||||
type input struct {
|
||||
op int
|
||||
path string
|
||||
flags uint32
|
||||
reply chan error
|
||||
}
|
||||
|
||||
type inode struct {
|
||||
handle syscall.Handle
|
||||
volume uint32
|
||||
index uint64
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
ov syscall.Overlapped
|
||||
ino *inode // i-number
|
||||
path string // Directory path
|
||||
mask uint64 // Directory itself is being watched with these notify flags
|
||||
names map[string]uint64 // Map of names being watched and their notify flags
|
||||
rename string // Remembers the old name while renaming a file
|
||||
buf [4096]byte
|
||||
}
|
||||
|
||||
type indexMap map[uint64]*watch
|
||||
type watchMap map[uint32]indexMap
|
||||
|
||||
func (w *Watcher) wakeupReader() error {
|
||||
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||
if e != nil {
|
||||
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDir(pathname string) (dir string, err error) {
|
||||
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
||||
if e != nil {
|
||||
return "", os.NewSyscallError("GetFileAttributes", e)
|
||||
}
|
||||
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
dir = pathname
|
||||
} else {
|
||||
dir, _ = filepath.Split(pathname)
|
||||
dir = filepath.Clean(dir)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getIno(path string) (ino *inode, err error) {
|
||||
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
||||
syscall.FILE_LIST_DIRECTORY,
|
||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
||||
nil, syscall.OPEN_EXISTING,
|
||||
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
|
||||
if e != nil {
|
||||
return nil, os.NewSyscallError("CreateFile", e)
|
||||
}
|
||||
var fi syscall.ByHandleFileInformation
|
||||
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
||||
syscall.CloseHandle(h)
|
||||
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
||||
}
|
||||
ino = &inode{
|
||||
handle: h,
|
||||
volume: fi.VolumeSerialNumber,
|
||||
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||
}
|
||||
return ino, nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) get(ino *inode) *watch {
|
||||
if i := m[ino.volume]; i != nil {
|
||||
return i[ino.index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) set(ino *inode, watch *watch) {
|
||||
i := m[ino.volume]
|
||||
if i == nil {
|
||||
i = make(indexMap)
|
||||
m[ino.volume] = i
|
||||
}
|
||||
i[ino.index] = watch
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||
dir, err := getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flags&sysFSONLYDIR != 0 && pathname != dir {
|
||||
return nil
|
||||
}
|
||||
ino, err := getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watchEntry := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watchEntry == nil {
|
||||
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
||||
syscall.CloseHandle(ino.handle)
|
||||
return os.NewSyscallError("CreateIoCompletionPort", e)
|
||||
}
|
||||
watchEntry = &watch{
|
||||
ino: ino,
|
||||
path: dir,
|
||||
names: make(map[string]uint64),
|
||||
}
|
||||
w.mu.Lock()
|
||||
w.watches.set(ino, watchEntry)
|
||||
w.mu.Unlock()
|
||||
flags |= provisional
|
||||
} else {
|
||||
syscall.CloseHandle(ino.handle)
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask |= flags
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||
}
|
||||
if err = w.startRead(watchEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask &= ^provisional
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) remWatch(pathname string) error {
|
||||
dir, err := getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ino, err := getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watch := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watch == nil {
|
||||
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
||||
}
|
||||
if pathname == dir {
|
||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||
watch.mask = 0
|
||||
} else {
|
||||
name := filepath.Base(pathname)
|
||||
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
return w.startRead(watch)
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) deleteWatch(watch *watch) {
|
||||
for name, mask := range watch.names {
|
||||
if mask&provisional == 0 {
|
||||
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||
}
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if watch.mask != 0 {
|
||||
if watch.mask&provisional == 0 {
|
||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||
}
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) startRead(watch *watch) error {
|
||||
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
||||
w.Errors <- os.NewSyscallError("CancelIo", e)
|
||||
w.deleteWatch(watch)
|
||||
}
|
||||
mask := toWindowsFlags(watch.mask)
|
||||
for _, m := range watch.names {
|
||||
mask |= toWindowsFlags(m)
|
||||
}
|
||||
if mask == 0 {
|
||||
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
||||
w.Errors <- os.NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
w.mu.Lock()
|
||||
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||
if e != nil {
|
||||
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
||||
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||
// Watched directory was probably removed
|
||||
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
|
||||
if watch.mask&sysFSONESHOT != 0 {
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readEvents reads from the I/O completion port, converts the
|
||||
// received events into Event objects and sends them via the Events channel.
|
||||
// Entry point to the I/O thread.
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
n, key uint32
|
||||
ov *syscall.Overlapped
|
||||
)
|
||||
runtime.LockOSThread()
|
||||
|
||||
for {
|
||||
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
||||
watch := (*watch)(unsafe.Pointer(ov))
|
||||
|
||||
if watch == nil {
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.mu.Lock()
|
||||
var indexes []indexMap
|
||||
for _, index := range w.watches {
|
||||
indexes = append(indexes, index)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, index := range indexes {
|
||||
for _, watch := range index {
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if e := syscall.CloseHandle(w.port); e != nil {
|
||||
err = os.NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
close(w.Events)
|
||||
close(w.Errors)
|
||||
ch <- err
|
||||
return
|
||||
case in := <-w.input:
|
||||
switch in.op {
|
||||
case opAddWatch:
|
||||
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||
case opRemoveWatch:
|
||||
in.reply <- w.remWatch(in.path)
|
||||
}
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch e {
|
||||
case syscall.ERROR_MORE_DATA:
|
||||
if watch == nil {
|
||||
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
||||
} else {
|
||||
// The i/o succeeded but the buffer is full.
|
||||
// In theory we should be building up a full packet.
|
||||
// In practice we can get away with just carrying on.
|
||||
n = uint32(unsafe.Sizeof(watch.buf))
|
||||
}
|
||||
case syscall.ERROR_ACCESS_DENIED:
|
||||
// Watched directory was probably removed
|
||||
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
continue
|
||||
case syscall.ERROR_OPERATION_ABORTED:
|
||||
// CancelIo was called on this handle
|
||||
continue
|
||||
default:
|
||||
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
||||
continue
|
||||
case nil:
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
for {
|
||||
if n == 0 {
|
||||
w.Events <- newEvent("", sysFSQOVERFLOW)
|
||||
w.Errors <- errors.New("short read in readEvents()")
|
||||
break
|
||||
}
|
||||
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||
// TODO: Consider using unsafe.Slice that is available from go1.17
|
||||
// https://stackoverflow.com/questions/51187973/how-to-create-an-array-or-a-slice-from-an-array-unsafe-pointer-in-golang
|
||||
// instead of using a fixed syscall.MAX_PATH buf, we create a buf that is the size of the path name
|
||||
size := int(raw.FileNameLength / 2)
|
||||
var buf []uint16
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
||||
sh.Len = size
|
||||
sh.Cap = size
|
||||
name := syscall.UTF16ToString(buf)
|
||||
fullname := filepath.Join(watch.path, name)
|
||||
|
||||
var mask uint64
|
||||
switch raw.Action {
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
mask = sysFSDELETESELF
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
mask = sysFSMODIFY
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
watch.rename = name
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
if watch.names[watch.rename] != 0 {
|
||||
watch.names[name] |= watch.names[watch.rename]
|
||||
delete(watch.names, watch.rename)
|
||||
mask = sysFSMOVESELF
|
||||
}
|
||||
}
|
||||
|
||||
sendNameEvent := func() {
|
||||
if w.sendEvent(fullname, watch.names[name]&mask) {
|
||||
if watch.names[name]&sysFSONESHOT != 0 {
|
||||
delete(watch.names, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
sendNameEvent()
|
||||
}
|
||||
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
||||
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
||||
if watch.mask&sysFSONESHOT != 0 {
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
fullname = filepath.Join(watch.path, watch.rename)
|
||||
sendNameEvent()
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
if raw.NextEntryOffset == 0 {
|
||||
break
|
||||
}
|
||||
offset += raw.NextEntryOffset
|
||||
|
||||
// Error!
|
||||
if offset >= n {
|
||||
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.startRead(watch); err != nil {
|
||||
w.Errors <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||
if mask == 0 {
|
||||
return false
|
||||
}
|
||||
event := newEvent(name, uint32(mask))
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.quit <- ch
|
||||
case w.Events <- event:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toWindowsFlags(mask uint64) uint32 {
|
||||
var m uint32
|
||||
if mask&sysFSACCESS != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
||||
}
|
||||
if mask&sysFSMODIFY != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||
}
|
||||
if mask&sysFSATTRIB != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||
}
|
||||
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func toFSnotifyFlags(action uint32) uint64 {
|
||||
switch action {
|
||||
case syscall.FILE_ACTION_ADDED:
|
||||
return sysFSCREATE
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
return sysFSDELETE
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
return sysFSMODIFY
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
return sysFSMOVEDFROM
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
return sysFSMOVEDTO
|
||||
}
|
||||
return 0
|
||||
}
|
9
vendor/github.com/gomarkdown/markdown/.gitpod.yml
generated
vendored
Normal file
9
vendor/github.com/gomarkdown/markdown/.gitpod.yml
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# This configuration file was automatically generated by Gitpod.
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
|
||||
tasks:
|
||||
- init: go get && go build ./... && go test ./...
|
||||
command: go run
|
||||
|
||||
|
24
vendor/github.com/gomarkdown/markdown/README.md
generated
vendored
24
vendor/github.com/gomarkdown/markdown/README.md
generated
vendored
@@ -2,9 +2,11 @@
|
||||
|
||||
[](https://pkg.go.dev/github.com/gomarkdown/markdown)
|
||||
|
||||
Package `github.com/gomarkdown/markdown` is a very fast Go library for parsing [Markdown](https://daringfireball.net/projects/markdown/) documents and rendering them to HTML.
|
||||
Package `github.com/gomarkdown/markdown` is a Go library for parsing Markdown text and rendering as HTML.
|
||||
|
||||
It's fast and supports common extensions.
|
||||
It's very fast and supports common extensions.
|
||||
|
||||
Try code examples online: https://replit.com/@kjk1?path=folder/gomarkdown
|
||||
|
||||
## API Docs:
|
||||
|
||||
@@ -15,17 +17,7 @@ It's fast and supports common extensions.
|
||||
|
||||
## Users
|
||||
|
||||
Some tools using this package:
|
||||
|
||||
- https://github.com/MichaelMure/go-term-markdown : markdown renderer for the terminal
|
||||
- https://github.com/artyom/mdserver : web server that serves markdown files
|
||||
- https://github.com/rsdoiel/mkpage : content management system generating static websites
|
||||
- https://github.com/cugu/dashboard : creates a badge dashboard from a yaml file
|
||||
- https://github.com/ieyasu/go-bwiki : simple wiki
|
||||
- https://github.com/romanyx/mdopen : view markdown files in the default browser
|
||||
- https://github.com/ystyle/sqlmanager : a library for manager sql with markdown like beetsql
|
||||
- https://gitlab.com/kendellfab/fazer : library for making templates
|
||||
- https://github.com/blmayer/tasker : a simple task list web app
|
||||
Some tools using this package: https://pkg.go.dev/github.com/gomarkdown/markdown?tab=importedby
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -36,6 +28,8 @@ md := []byte("## markdown document")
|
||||
output := markdown.ToHTML(md, nil, nil)
|
||||
```
|
||||
|
||||
Try it online: https://replit.com/@kjk1/gomarkdown-basic
|
||||
|
||||
## Customizing markdown parser
|
||||
|
||||
Markdown format is loosely specified and there are multiple extensions invented after original specification was created.
|
||||
@@ -57,6 +51,8 @@ md := []byte("markdown text")
|
||||
html := markdown.ToHTML(md, parser, nil)
|
||||
```
|
||||
|
||||
Try it online: https://replit.com/@kjk1/gomarkdown-customized-html-renderer
|
||||
|
||||
## Customizing HTML renderer
|
||||
|
||||
Similarly, HTML renderer can be configured with different [options](https://pkg.go.dev/github.com/gomarkdown/markdown/html#RendererOptions)
|
||||
@@ -77,6 +73,8 @@ md := []byte("markdown text")
|
||||
html := markdown.ToHTML(md, nil, renderer)
|
||||
```
|
||||
|
||||
Try it online: https://replit.com/@kjk1/gomarkdown-customized-html-renderer
|
||||
|
||||
HTML renderer also supports reusing most of the logic and overriding rendering of only specific nodes.
|
||||
|
||||
You can provide [RenderNodeFunc](https://pkg.go.dev/github.com/gomarkdown/markdown/html#RenderNodeFunc) in [RendererOptions](https://pkg.go.dev/github.com/gomarkdown/markdown/html#RendererOptions).
|
||||
|
9
vendor/github.com/gomarkdown/markdown/ast/node.go
generated
vendored
9
vendor/github.com/gomarkdown/markdown/ast/node.go
generated
vendored
@@ -157,9 +157,13 @@ func (l *Leaf) GetChildren() []Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetChildren will panic becuase Leaf cannot have children
|
||||
// SetChildren will panic if trying to set non-empty children
|
||||
// because Leaf cannot have children
|
||||
func (l *Leaf) SetChildren(newChildren []Node) {
|
||||
panic("leaf node cannot have children")
|
||||
if len(newChildren) != 0 {
|
||||
panic("leaf node cannot have children")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Document represents markdown document node, a root of ast
|
||||
@@ -272,6 +276,7 @@ type CrossReference struct {
|
||||
Container
|
||||
|
||||
Destination []byte // Destination is where the reference points to
|
||||
Suffix []byte // Potential citation suffix, i.e. (#myid, text)
|
||||
}
|
||||
|
||||
// Citation is a citation node.
|
||||
|
1
vendor/github.com/gomarkdown/markdown/fuzz.go
generated
vendored
1
vendor/github.com/gomarkdown/markdown/fuzz.go
generated
vendored
@@ -1,3 +1,4 @@
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package markdown
|
||||
|
156
vendor/github.com/gomarkdown/markdown/html/renderer.go
generated
vendored
156
vendor/github.com/gomarkdown/markdown/html/renderer.go
generated
vendored
@@ -132,6 +132,11 @@ type Renderer struct {
|
||||
// if > 0, will strip html tags in Out and Outs
|
||||
DisableTags int
|
||||
|
||||
// IsSafeURLOverride allows overriding the default URL matcher. URL is
|
||||
// safe if the overriding function returns true. Can be used to extend
|
||||
// the default list of safe URLs.
|
||||
IsSafeURLOverride func(url []byte) bool
|
||||
|
||||
sr *SPRenderer
|
||||
|
||||
documentMatter ast.DocumentMatters // keep track of front/main/back matter.
|
||||
@@ -211,71 +216,12 @@ func NewRenderer(opts RendererOptions) *Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
func isHTMLTag(tag []byte, tagname string) bool {
|
||||
found, _ := findHTMLTagPos(tag, tagname)
|
||||
return found
|
||||
}
|
||||
|
||||
// Look for a character, but ignore it when it's in any kind of quotes, it
|
||||
// might be JavaScript
|
||||
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
|
||||
inSingleQuote := false
|
||||
inDoubleQuote := false
|
||||
inGraveQuote := false
|
||||
i := start
|
||||
for i < len(html) {
|
||||
switch {
|
||||
case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
|
||||
return i
|
||||
case html[i] == '\'':
|
||||
inSingleQuote = !inSingleQuote
|
||||
case html[i] == '"':
|
||||
inDoubleQuote = !inDoubleQuote
|
||||
case html[i] == '`':
|
||||
inGraveQuote = !inGraveQuote
|
||||
}
|
||||
i++
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
|
||||
i := 0
|
||||
if i < len(tag) && tag[0] != '<' {
|
||||
return false, -1
|
||||
}
|
||||
i++
|
||||
i = skipSpace(tag, i)
|
||||
|
||||
if i < len(tag) && tag[i] == '/' {
|
||||
i++
|
||||
}
|
||||
|
||||
i = skipSpace(tag, i)
|
||||
j := 0
|
||||
for ; i < len(tag); i, j = i+1, j+1 {
|
||||
if j >= len(tagname) {
|
||||
break
|
||||
}
|
||||
|
||||
if strings.ToLower(string(tag[i]))[0] != tagname[j] {
|
||||
return false, -1
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(tag) {
|
||||
return false, -1
|
||||
}
|
||||
|
||||
rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
|
||||
if rightAngle >= i {
|
||||
return true, rightAngle
|
||||
}
|
||||
|
||||
return false, -1
|
||||
}
|
||||
|
||||
func isRelativeLink(link []byte) (yes bool) {
|
||||
// empty links considerd relative
|
||||
if len(link) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// a tag begin with '#'
|
||||
if link[0] == '#' {
|
||||
return true
|
||||
@@ -305,6 +251,9 @@ func isRelativeLink(link []byte) (yes bool) {
|
||||
}
|
||||
|
||||
func (r *Renderer) addAbsPrefix(link []byte) []byte {
|
||||
if len(link) == 0 {
|
||||
return link
|
||||
}
|
||||
if r.opts.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
|
||||
newDest := r.opts.AbsolutePrefix
|
||||
if link[0] != '/' {
|
||||
@@ -344,19 +293,16 @@ func isMailto(link []byte) bool {
|
||||
return bytes.HasPrefix(link, []byte("mailto:"))
|
||||
}
|
||||
|
||||
func needSkipLink(flags Flags, dest []byte) bool {
|
||||
func needSkipLink(r *Renderer, dest []byte) bool {
|
||||
flags := r.opts.Flags
|
||||
if flags&SkipLinks != 0 {
|
||||
return true
|
||||
}
|
||||
return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
|
||||
}
|
||||
|
||||
func isSmartypantable(node ast.Node) bool {
|
||||
switch node.GetParent().(type) {
|
||||
case *ast.Link, *ast.CodeBlock, *ast.Code:
|
||||
return false
|
||||
isSafeURL := r.IsSafeURLOverride
|
||||
if isSafeURL == nil {
|
||||
isSafeURL = parser.IsSafeURL
|
||||
}
|
||||
return true
|
||||
return flags&Safelink != 0 && !isSafeURL(dest) && !isMailto(dest)
|
||||
}
|
||||
|
||||
func appendLanguageAttr(attrs []string, info []byte) []string {
|
||||
@@ -552,7 +498,7 @@ func (r *Renderer) linkExit(w io.Writer, link *ast.Link) {
|
||||
// Link writes ast.Link node
|
||||
func (r *Renderer) Link(w io.Writer, link *ast.Link, entering bool) {
|
||||
// mark it but don't link it if it is not a safe link: no smartypants
|
||||
if needSkipLink(r.opts.Flags, link.Destination) {
|
||||
if needSkipLink(r, link.Destination) {
|
||||
r.OutOneOf(w, entering, "<tt>", "</tt>")
|
||||
return
|
||||
}
|
||||
@@ -1297,41 +1243,6 @@ func isListItemTerm(node ast.Node) bool {
|
||||
return ok && data.ListFlags&ast.ListTypeTerm != 0
|
||||
}
|
||||
|
||||
// TODO: move to internal package
|
||||
func skipSpace(data []byte, i int) int {
|
||||
n := len(data)
|
||||
for i < n && isSpace(data[i]) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// TODO: move to internal package
|
||||
var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
|
||||
var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
|
||||
|
||||
func isSafeLink(link []byte) bool {
|
||||
for _, path := range validPaths {
|
||||
if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) {
|
||||
if len(link) == len(path) {
|
||||
return true
|
||||
} else if isAlnum(link[len(path)]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, prefix := range validUris {
|
||||
// TODO: handle unicode here
|
||||
// case-insensitive prefix test
|
||||
if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isAlnum(link[len(prefix)]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: move to internal package
|
||||
// Create a url-safe slug for fragments
|
||||
func slugify(in []byte) []byte {
|
||||
@@ -1367,33 +1278,6 @@ func slugify(in []byte) []byte {
|
||||
return out[a : b+1]
|
||||
}
|
||||
|
||||
// TODO: move to internal package
|
||||
// isAlnum returns true if c is a digit or letter
|
||||
// TODO: check when this is looking for ASCII alnum and when it should use unicode
|
||||
func isAlnum(c byte) bool {
|
||||
return (c >= '0' && c <= '9') || isLetter(c)
|
||||
}
|
||||
|
||||
// isSpace returns true if c is a white-space charactr
|
||||
func isSpace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||
}
|
||||
|
||||
// isLetter returns true if c is ascii letter
|
||||
func isLetter(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
}
|
||||
|
||||
// isPunctuation returns true if c is a punctuation symbol.
|
||||
func isPunctuation(c byte) bool {
|
||||
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
|
||||
if c == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// BlockAttrs takes a node and checks if it has block level attributes set. If so it
|
||||
// will return a slice each containing a "key=value(s)" string.
|
||||
func BlockAttrs(node ast.Node) []string {
|
||||
|
8
vendor/github.com/gomarkdown/markdown/html/smartypants.go
generated
vendored
8
vendor/github.com/gomarkdown/markdown/html/smartypants.go
generated
vendored
@@ -3,10 +3,18 @@ package html
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
)
|
||||
|
||||
// SmartyPants rendering
|
||||
|
||||
var (
|
||||
isSpace = parser.IsSpace
|
||||
isAlnum = parser.IsAlnum
|
||||
isPunctuation = parser.IsPunctuation
|
||||
)
|
||||
|
||||
// SPRenderer is a struct containing state of a Smartypants renderer.
|
||||
type SPRenderer struct {
|
||||
inSingleQuote bool
|
||||
|
30
vendor/github.com/gomarkdown/markdown/parser/block.go
generated
vendored
30
vendor/github.com/gomarkdown/markdown/parser/block.go
generated
vendored
@@ -24,8 +24,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
reBackslashOrAmp = regexp.MustCompile("[\\&]")
|
||||
reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
|
||||
reBackslashOrAmp = regexp.MustCompile(`[\&]`)
|
||||
reEntityOrEscapedChar = regexp.MustCompile(`(?i)\\` + escapable + "|" + charEntity)
|
||||
|
||||
// blockTags is a set of tags that are recognized as HTML block tags.
|
||||
// Any of these can be included in markdown text without special escaping.
|
||||
@@ -909,18 +909,18 @@ func syntaxRange(data []byte, iout *int) (int, int) {
|
||||
|
||||
// strip all whitespace at the beginning and the end
|
||||
// of the {} block
|
||||
for syn > 0 && isSpace(data[syntaxStart]) {
|
||||
for syn > 0 && IsSpace(data[syntaxStart]) {
|
||||
syntaxStart++
|
||||
syn--
|
||||
}
|
||||
|
||||
for syn > 0 && isSpace(data[syntaxStart+syn-1]) {
|
||||
for syn > 0 && IsSpace(data[syntaxStart+syn-1]) {
|
||||
syn--
|
||||
}
|
||||
|
||||
i++
|
||||
} else {
|
||||
for i < n && !isSpace(data[i]) {
|
||||
for i < n && !IsSpace(data[i]) {
|
||||
syn++
|
||||
i++
|
||||
}
|
||||
@@ -1419,6 +1419,16 @@ gatherlines:
|
||||
|
||||
chunk := data[line+indentIndex : i]
|
||||
|
||||
// If there is a fence line (marking starting of a code block)
|
||||
// without indent do not process it as part of the list.
|
||||
if p.extensions&FencedCode != 0 {
|
||||
fenceLineEnd, _ := isFenceLine(chunk, nil, "")
|
||||
if fenceLineEnd > 0 && indent == 0 {
|
||||
*flags |= ast.ListItemEndOfList
|
||||
break gatherlines
|
||||
}
|
||||
}
|
||||
|
||||
// evaluate how this line fits in
|
||||
switch {
|
||||
// is this a nested list item?
|
||||
@@ -1675,6 +1685,12 @@ func (p *Parser) paragraph(data []byte) int {
|
||||
return i
|
||||
}
|
||||
|
||||
// if there's a block quote, paragraph is over
|
||||
if p.quotePrefix(current) > 0 {
|
||||
p.renderParagraph(data[:i])
|
||||
return i
|
||||
}
|
||||
|
||||
// if there's a fenced code block, paragraph is over
|
||||
if p.extensions&FencedCode != 0 {
|
||||
if p.fencedCodeBlock(current, false) > 0 {
|
||||
@@ -1761,7 +1777,7 @@ func skipUntilChar(data []byte, i int, c byte) int {
|
||||
|
||||
func skipAlnum(data []byte, i int) int {
|
||||
n := len(data)
|
||||
for i < n && isAlnum(data[i]) {
|
||||
for i < n && IsAlnum(data[i]) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
@@ -1769,7 +1785,7 @@ func skipAlnum(data []byte, i int) int {
|
||||
|
||||
func skipSpace(data []byte, i int) int {
|
||||
n := len(data)
|
||||
for i < n && isSpace(data[i]) {
|
||||
for i < n && IsSpace(data[i]) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
|
2
vendor/github.com/gomarkdown/markdown/parser/caption.go
generated
vendored
2
vendor/github.com/gomarkdown/markdown/parser/caption.go
generated
vendored
@@ -58,7 +58,7 @@ func captionID(data []byte) (string, int) {
|
||||
}
|
||||
// remains must be whitespace.
|
||||
for l := k + 1; l < end; l++ {
|
||||
if !isSpace(data[l]) {
|
||||
if !IsSpace(data[l]) {
|
||||
return "", 0
|
||||
}
|
||||
}
|
||||
|
86
vendor/github.com/gomarkdown/markdown/parser/inline.go
generated
vendored
86
vendor/github.com/gomarkdown/markdown/parser/inline.go
generated
vendored
@@ -68,7 +68,7 @@ func emphasis(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
if n > 2 && data[1] != c {
|
||||
// whitespace cannot follow an opening emphasis;
|
||||
// strikethrough only takes two characters '~~'
|
||||
if isSpace(data[1]) {
|
||||
if IsSpace(data[1]) {
|
||||
return 0, nil
|
||||
}
|
||||
if p.extensions&SuperSubscript != 0 && c == '~' {
|
||||
@@ -80,7 +80,7 @@ func emphasis(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
}
|
||||
ret++ // we started with data[1:] above.
|
||||
for i := 1; i < ret; i++ {
|
||||
if isSpace(data[i]) && !isEscape(data, i) {
|
||||
if IsSpace(data[i]) && !isEscape(data, i) {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func emphasis(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
}
|
||||
|
||||
if n > 3 && data[1] == c && data[2] != c {
|
||||
if isSpace(data[2]) {
|
||||
if IsSpace(data[2]) {
|
||||
return 0, nil
|
||||
}
|
||||
ret, node := helperDoubleEmphasis(p, data[2:], c)
|
||||
@@ -109,7 +109,7 @@ func emphasis(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
}
|
||||
|
||||
if n > 4 && data[1] == c && data[2] == c && data[3] != c {
|
||||
if c == '~' || isSpace(data[3]) {
|
||||
if c == '~' || IsSpace(data[3]) {
|
||||
return 0, nil
|
||||
}
|
||||
ret, node := helperTripleEmphasis(p, data, 3, c)
|
||||
@@ -155,8 +155,9 @@ func codeSpan(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
if data[j] == '\n' {
|
||||
break
|
||||
}
|
||||
if !isSpace(data[j]) {
|
||||
if !IsSpace(data[j]) {
|
||||
hasCharsAfterDelimiter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +256,7 @@ func maybeInlineFootnoteOrSuper(p *Parser, data []byte, offset int) (int, ast.No
|
||||
return 0, nil
|
||||
}
|
||||
for i := offset; i < offset+ret; i++ {
|
||||
if isSpace(data[i]) && !isEscape(data, i) {
|
||||
if IsSpace(data[i]) && !isEscape(data, i) {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
@@ -420,7 +421,7 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
|
||||
// skip whitespace after title
|
||||
titleE = i - 1
|
||||
for titleE > titleB && isSpace(data[titleE]) {
|
||||
for titleE > titleB && IsSpace(data[titleE]) {
|
||||
titleE--
|
||||
}
|
||||
|
||||
@@ -432,7 +433,7 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
}
|
||||
|
||||
// remove whitespace at the end of the link
|
||||
for linkE > linkB && isSpace(data[linkE-1]) {
|
||||
for linkE > linkB && IsSpace(data[linkE-1]) {
|
||||
linkE--
|
||||
}
|
||||
|
||||
@@ -601,9 +602,8 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
}
|
||||
|
||||
// links need something to click on and somewhere to go
|
||||
if len(uLink) == 0 || (t == linkNormal && txtE <= 1) {
|
||||
return 0, nil
|
||||
}
|
||||
// [](http://bla) is legal in CommonMark, so allow txtE <=1 for linkNormal
|
||||
// [bla]() is also legal in CommonMark, so allow empty uLink
|
||||
}
|
||||
|
||||
// call the relevant rendering function
|
||||
@@ -826,7 +826,9 @@ func linkEndsWithEntity(data []byte, linkEnd int) bool {
|
||||
}
|
||||
|
||||
// hasPrefixCaseInsensitive is a custom implementation of
|
||||
// strings.HasPrefix(strings.ToLower(s), prefix)
|
||||
//
|
||||
// strings.HasPrefix(strings.ToLower(s), prefix)
|
||||
//
|
||||
// we rolled our own because ToLower pulls in a huge machinery of lowercasing
|
||||
// anything from Unicode and that's very slow. Since this func will only be
|
||||
// used on ASCII protocol prefixes, we can take shortcuts.
|
||||
@@ -888,7 +890,7 @@ func autoLink(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
|
||||
// scan backward for a word boundary
|
||||
rewind := 0
|
||||
for offset-rewind > 0 && rewind <= 7 && isLetter(data[offset-rewind-1]) {
|
||||
for offset-rewind > 0 && rewind <= 7 && IsLetter(data[offset-rewind-1]) {
|
||||
rewind++
|
||||
}
|
||||
if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters
|
||||
@@ -898,7 +900,11 @@ func autoLink(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
origData := data
|
||||
data = data[offset-rewind:]
|
||||
|
||||
if !isSafeLink(data) {
|
||||
isSafeURL := p.IsSafeURLOverride
|
||||
if isSafeURL == nil {
|
||||
isSafeURL = IsSafeURL
|
||||
}
|
||||
if !isSafeURL(data) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -991,39 +997,7 @@ func autoLink(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
}
|
||||
|
||||
func isEndOfLink(char byte) bool {
|
||||
return isSpace(char) || char == '<'
|
||||
}
|
||||
|
||||
var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
|
||||
var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
|
||||
|
||||
func isSafeLink(link []byte) bool {
|
||||
nLink := len(link)
|
||||
for _, path := range validPaths {
|
||||
nPath := len(path)
|
||||
linkPrefix := link[:nPath]
|
||||
if nLink >= nPath && bytes.Equal(linkPrefix, path) {
|
||||
if nLink == nPath {
|
||||
return true
|
||||
} else if isAlnum(link[nPath]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, prefix := range validUris {
|
||||
// TODO: handle unicode here
|
||||
// case-insensitive prefix test
|
||||
nPrefix := len(prefix)
|
||||
if nLink > nPrefix {
|
||||
linkPrefix := bytes.ToLower(link[:nPrefix])
|
||||
if bytes.Equal(linkPrefix, prefix) && isAlnum(link[nPrefix]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return IsSpace(char) || char == '<'
|
||||
}
|
||||
|
||||
// return the length of the given tag, or 0 is it's not valid
|
||||
@@ -1045,7 +1019,7 @@ func tagLength(data []byte) (autolink autolinkType, end int) {
|
||||
i = 1
|
||||
}
|
||||
|
||||
if !isAlnum(data[i]) {
|
||||
if !IsAlnum(data[i]) {
|
||||
return notAutolink, 0
|
||||
}
|
||||
|
||||
@@ -1053,7 +1027,7 @@ func tagLength(data []byte) (autolink autolinkType, end int) {
|
||||
autolink = notAutolink
|
||||
|
||||
// try to find the beginning of an URI
|
||||
for i < len(data) && (isAlnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
|
||||
for i < len(data) && (IsAlnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
|
||||
i++
|
||||
}
|
||||
|
||||
@@ -1078,7 +1052,7 @@ func tagLength(data []byte) (autolink autolinkType, end int) {
|
||||
for i < len(data) {
|
||||
if data[i] == '\\' {
|
||||
i += 2
|
||||
} else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isSpace(data[i]) {
|
||||
} else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || IsSpace(data[i]) {
|
||||
break
|
||||
} else {
|
||||
i++
|
||||
@@ -1110,7 +1084,7 @@ func isMailtoAutoLink(data []byte) int {
|
||||
|
||||
// address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@'
|
||||
for i, c := range data {
|
||||
if isAlnum(c) {
|
||||
if IsAlnum(c) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1119,7 +1093,7 @@ func isMailtoAutoLink(data []byte) int {
|
||||
nb++
|
||||
|
||||
case '-', '.', '_':
|
||||
break
|
||||
// no-op but not defult
|
||||
|
||||
case '>':
|
||||
if nb == 1 {
|
||||
@@ -1231,10 +1205,10 @@ func helperEmphasis(p *Parser, data []byte, c byte) (int, ast.Node) {
|
||||
continue
|
||||
}
|
||||
|
||||
if data[i] == c && !isSpace(data[i-1]) {
|
||||
if data[i] == c && !IsSpace(data[i-1]) {
|
||||
|
||||
if p.extensions&NoIntraEmphasis != 0 {
|
||||
if !(i+1 == len(data) || isSpace(data[i+1]) || isPunctuation(data[i+1])) {
|
||||
if !(i+1 == len(data) || IsSpace(data[i+1]) || IsPunctuation(data[i+1])) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1258,7 +1232,7 @@ func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, ast.Node) {
|
||||
}
|
||||
i += length
|
||||
|
||||
if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isSpace(data[i-1]) {
|
||||
if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !IsSpace(data[i-1]) {
|
||||
var node ast.Node = &ast.Strong{}
|
||||
if c == '~' {
|
||||
node = &ast.Del{}
|
||||
@@ -1284,7 +1258,7 @@ func helperTripleEmphasis(p *Parser, data []byte, offset int, c byte) (int, ast.
|
||||
i += length
|
||||
|
||||
// skip whitespace preceded symbols
|
||||
if data[i] != c || isSpace(data[i-1]) {
|
||||
if data[i] != c || IsSpace(data[i-1]) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
99
vendor/github.com/gomarkdown/markdown/parser/parser.go
generated
vendored
99
vendor/github.com/gomarkdown/markdown/parser/parser.go
generated
vendored
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gomarkdown/markdown/ast"
|
||||
)
|
||||
@@ -85,6 +84,11 @@ type Parser struct {
|
||||
// the bottom will be used to fill in the link details.
|
||||
ReferenceOverride ReferenceOverrideFunc
|
||||
|
||||
// IsSafeURLOverride allows overriding the default URL matcher. URL is
|
||||
// safe if the overriding function returns true. Can be used to extend
|
||||
// the default list of safe URLs.
|
||||
IsSafeURLOverride func(url []byte) bool
|
||||
|
||||
Opts Options
|
||||
|
||||
// after parsing, this is AST root of parsed markdown text
|
||||
@@ -388,35 +392,35 @@ func (p *Parser) parseRefsToAST() {
|
||||
//
|
||||
// Consider this markdown with reference-style links:
|
||||
//
|
||||
// [link][ref]
|
||||
// [link][ref]
|
||||
//
|
||||
// [ref]: /url/ "tooltip title"
|
||||
// [ref]: /url/ "tooltip title"
|
||||
//
|
||||
// It will be ultimately converted to this HTML:
|
||||
//
|
||||
// <p><a href=\"/url/\" title=\"title\">link</a></p>
|
||||
// <p><a href=\"/url/\" title=\"title\">link</a></p>
|
||||
//
|
||||
// And a reference structure will be populated as follows:
|
||||
//
|
||||
// p.refs["ref"] = &reference{
|
||||
// link: "/url/",
|
||||
// title: "tooltip title",
|
||||
// }
|
||||
// p.refs["ref"] = &reference{
|
||||
// link: "/url/",
|
||||
// title: "tooltip title",
|
||||
// }
|
||||
//
|
||||
// Alternatively, reference can contain information about a footnote. Consider
|
||||
// this markdown:
|
||||
//
|
||||
// Text needing a footnote.[^a]
|
||||
// Text needing a footnote.[^a]
|
||||
//
|
||||
// [^a]: This is the note
|
||||
// [^a]: This is the note
|
||||
//
|
||||
// A reference structure will be populated as follows:
|
||||
//
|
||||
// p.refs["a"] = &reference{
|
||||
// link: "a",
|
||||
// title: "This is the note",
|
||||
// noteID: <some positive int>,
|
||||
// }
|
||||
// p.refs["a"] = &reference{
|
||||
// link: "a",
|
||||
// title: "This is the note",
|
||||
// noteID: <some positive int>,
|
||||
// }
|
||||
//
|
||||
// TODO: As you can see, it begs for splitting into two dedicated structures
|
||||
// for refs and for footnotes.
|
||||
@@ -691,8 +695,8 @@ gatherLines:
|
||||
return
|
||||
}
|
||||
|
||||
// isPunctuation returns true if c is a punctuation symbol.
|
||||
func isPunctuation(c byte) bool {
|
||||
// IsPunctuation returns true if c is a punctuation symbol.
|
||||
func IsPunctuation(c byte) bool {
|
||||
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
|
||||
if c == r {
|
||||
return true
|
||||
@@ -701,25 +705,69 @@ func isPunctuation(c byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// isSpace returns true if c is a white-space charactr
|
||||
func isSpace(c byte) bool {
|
||||
// IsSpace returns true if c is a white-space charactr
|
||||
func IsSpace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||
}
|
||||
|
||||
// isLetter returns true if c is ascii letter
|
||||
func isLetter(c byte) bool {
|
||||
// IsLetter returns true if c is ascii letter
|
||||
func IsLetter(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
}
|
||||
|
||||
// isAlnum returns true if c is a digit or letter
|
||||
// IsAlnum returns true if c is a digit or letter
|
||||
// TODO: check when this is looking for ASCII alnum and when it should use unicode
|
||||
func isAlnum(c byte) bool {
|
||||
return (c >= '0' && c <= '9') || isLetter(c)
|
||||
func IsAlnum(c byte) bool {
|
||||
return (c >= '0' && c <= '9') || IsLetter(c)
|
||||
}
|
||||
|
||||
var URIs = [][]byte{
|
||||
[]byte("http://"),
|
||||
[]byte("https://"),
|
||||
[]byte("ftp://"),
|
||||
[]byte("mailto:"),
|
||||
}
|
||||
|
||||
var Paths = [][]byte{
|
||||
[]byte("/"),
|
||||
[]byte("./"),
|
||||
[]byte("../"),
|
||||
}
|
||||
|
||||
// IsSafeURL returns true if url starts with one of the valid schemes or is a relative path.
|
||||
func IsSafeURL(url []byte) bool {
|
||||
nLink := len(url)
|
||||
for _, path := range Paths {
|
||||
nPath := len(path)
|
||||
linkPrefix := url[:nPath]
|
||||
if nLink >= nPath && bytes.Equal(linkPrefix, path) {
|
||||
if nLink == nPath {
|
||||
return true
|
||||
} else if IsAlnum(url[nPath]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, prefix := range URIs {
|
||||
// TODO: handle unicode here
|
||||
// case-insensitive prefix test
|
||||
nPrefix := len(prefix)
|
||||
if nLink > nPrefix {
|
||||
linkPrefix := bytes.ToLower(url[:nPrefix])
|
||||
if bytes.Equal(linkPrefix, prefix) && IsAlnum(url[nPrefix]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: this is not used
|
||||
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
|
||||
// always ends output with a newline
|
||||
/*
|
||||
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
|
||||
// first, check for common cases: no tabs, or only tabs at beginning of line
|
||||
i, prefix := 0, 0
|
||||
@@ -775,6 +823,7 @@ func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Find if a line counts as indented or not.
|
||||
// Returns number of characters the indent is (0 = not indented).
|
||||
@@ -805,7 +854,7 @@ func slugify(in []byte) []byte {
|
||||
sym := false
|
||||
|
||||
for _, ch := range in {
|
||||
if isAlnum(ch) {
|
||||
if IsAlnum(ch) {
|
||||
sym = false
|
||||
out = append(out, ch)
|
||||
} else if sym {
|
||||
|
23
vendor/github.com/gomarkdown/markdown/parser/ref.go
generated
vendored
23
vendor/github.com/gomarkdown/markdown/parser/ref.go
generated
vendored
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/gomarkdown/markdown/ast"
|
||||
)
|
||||
|
||||
// parse '(#r)', where r does not contain spaces. Or.
|
||||
// (!item) (!item, subitem), for an index, (!!item) signals primary.
|
||||
// parse '(#r, text)', where r does not contain spaces, but text may (similar to a citation). Or. (!item) (!item,
|
||||
// subitem), for an index, (!!item) signals primary.
|
||||
func maybeShortRefOrIndex(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
if len(data[offset:]) < 4 {
|
||||
return 0, nil
|
||||
@@ -25,8 +25,8 @@ func maybeShortRefOrIndex(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
switch {
|
||||
case c == ')':
|
||||
break Loop
|
||||
case !isAlnum(c):
|
||||
if c == '_' || c == '-' || c == ':' {
|
||||
case !IsAlnum(c):
|
||||
if c == '_' || c == '-' || c == ':' || c == ' ' || c == ',' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
@@ -45,6 +45,21 @@ func maybeShortRefOrIndex(p *Parser, data []byte, offset int) (int, ast.Node) {
|
||||
id := data[2:i]
|
||||
node := &ast.CrossReference{}
|
||||
node.Destination = id
|
||||
if c := bytes.Index(id, []byte(",")); c > 0 {
|
||||
idpart := id[:c]
|
||||
suff := id[c+1:]
|
||||
suff = bytes.TrimSpace(suff)
|
||||
node.Destination = idpart
|
||||
node.Suffix = suff
|
||||
}
|
||||
if bytes.Index(node.Destination, []byte(" ")) > 0 {
|
||||
// no spaces allowed in id
|
||||
return 0, nil
|
||||
}
|
||||
if bytes.Index(node.Destination, []byte(",")) > 0 {
|
||||
// nor comma
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return i + 1, node
|
||||
|
||||
|
6
vendor/github.com/google/gops/agent/agent.go
generated
vendored
6
vendor/github.com/google/gops/agent/agent.go
generated
vendored
@@ -120,14 +120,14 @@ func Listen(opts Options) error {
|
||||
return err
|
||||
}
|
||||
|
||||
go listen()
|
||||
go listen(listener)
|
||||
return nil
|
||||
}
|
||||
|
||||
func listen() {
|
||||
func listen(l net.Listener) {
|
||||
buf := make([]byte, 1)
|
||||
for {
|
||||
fd, err := listener.Accept()
|
||||
fd, err := l.Accept()
|
||||
if err != nil {
|
||||
// No great way to check for this, see https://golang.org/issues/4373.
|
||||
if !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
|
4
vendor/github.com/google/gops/internal/internal.go
generated
vendored
4
vendor/github.com/google/gops/internal/internal.go
generated
vendored
@@ -22,8 +22,8 @@ func ConfigDir() (string, error) {
|
||||
return configDir, nil
|
||||
}
|
||||
|
||||
if osUserConfigDir := getOSUserConfigDir(); osUserConfigDir != "" {
|
||||
return filepath.Join(osUserConfigDir, "gops"), nil
|
||||
if userConfigDir, err := os.UserConfigDir(); err == nil {
|
||||
return filepath.Join(userConfigDir, "gops"), nil
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
|
20
vendor/github.com/google/gops/internal/internal_go1_13.go
generated
vendored
20
vendor/github.com/google/gops/internal/internal_go1_13.go
generated
vendored
@@ -1,20 +0,0 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func getOSUserConfigDir() string {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return configDir
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user