start irc module
This commit is contained in:
parent
f29f982f7f
commit
f224c77bac
9
Gopkg.lock
generated
9
Gopkg.lock
generated
@ -188,6 +188,14 @@
|
||||
revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf"
|
||||
version = "v8.18.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:54c3b474a0e4a8e65c74e0dbb272bcf30a4bab48883d34f958cbb9d8e8ef387a"
|
||||
name = "gopkg.in/irc.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "4901bf6be124ba1558d3657e91286393c97fb47f"
|
||||
version = "v2.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
@ -209,6 +217,7 @@
|
||||
"github.com/lib/pq",
|
||||
"github.com/robfig/cron",
|
||||
"golang.org/x/crypto/bcrypt",
|
||||
"gopkg.in/irc.v2",
|
||||
"gopkg.in/yaml.v2",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
|
13
model/line_message_log.go
Normal file
13
model/line_message_log.go
Normal file
@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// LineMessageLog -
|
||||
type LineMessageLog struct {
|
||||
ID string `db:"id" cc:"id"`
|
||||
Group string `db:"group" cc:"group"`
|
||||
User string `db:"user" cc:"user"`
|
||||
Message string `db:"message" cc:"message"`
|
||||
Ctime time.Time `db:"ctime" cc:"ctime"`
|
||||
Mtime time.Time `db:"mtime" cc:"mtime"`
|
||||
}
|
11
model/line_user.go
Normal file
11
model/line_user.go
Normal file
@ -0,0 +1,11 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// LineUser -
|
||||
type LineUser struct {
|
||||
ID string `db:"id" cc:"id"`
|
||||
Name string `db:"name" cc:"name"`
|
||||
Ctime time.Time `db:"ctime" cc:"ctime"`
|
||||
Mtime time.Time `db:"mtime" cc:"mtime"`
|
||||
}
|
13
model/youtube_channel.go
Normal file
13
model/youtube_channel.go
Normal file
@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// YoutubeChannel -
|
||||
type YoutubeChannel struct {
|
||||
ID string `db:"id" cc:"id"`
|
||||
Name string `db:"name" cc:"name"`
|
||||
LastVideo string `db:"lastvideo" cc:"lastvideo"`
|
||||
Expire int32 `db:"expire" cc:"expire"`
|
||||
Ctime time.Time `db:"ctime" cc:"ctime"`
|
||||
Mtime time.Time `db:"mtime" cc:"mtime"`
|
||||
}
|
@ -11,5 +11,6 @@ func SetBackground() {
|
||||
c = cron.New()
|
||||
c.AddFunc("0 * * * * *", readFacebookPage)
|
||||
c.AddFunc("*/20 * * * * *", getStreamStatus)
|
||||
c.AddFunc("*/5 * * * * *", checkOpay)
|
||||
c.Start()
|
||||
}
|
||||
|
21
module/background/opay.go
Normal file
21
module/background/opay.go
Normal file
@ -0,0 +1,21 @@
|
||||
package background
|
||||
|
||||
import (
|
||||
"git.trj.tw/golang/mtfosbot/model"
|
||||
)
|
||||
|
||||
func checkOpay() {
|
||||
channels, err := model.GetAllTwitchChannel()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, v := range channels {
|
||||
if len(v.OpayID) > 0 && v.Join {
|
||||
go getOpayData(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getOpayData(ch *model.TwitchChannel) {
|
||||
|
||||
}
|
33
module/twitch-irc/twitch-irc.go
Normal file
33
module/twitch-irc/twitch-irc.go
Normal file
@ -0,0 +1,33 @@
|
||||
package twitchirc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"gopkg.in/irc.v2"
|
||||
|
||||
"git.trj.tw/golang/mtfosbot/module/config"
|
||||
)
|
||||
|
||||
var client *irc.Client
|
||||
|
||||
// InitIRC -
|
||||
func InitIRC() (err error) {
|
||||
conf := config.GetConf()
|
||||
conn, err := net.Dial("tcp", conf.Twitch.ChatHost)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config := irc.ClientConfig{
|
||||
Handler: irc.HandlerFunc(ircHandle),
|
||||
}
|
||||
|
||||
client = irc.NewClient(conn, config)
|
||||
|
||||
err = client.Run()
|
||||
return
|
||||
}
|
||||
|
||||
func ircHandle(c *irc.Client, m *irc.Message) {
|
||||
fmt.Println(m.String())
|
||||
}
|
3
vendor/gopkg.in/irc.v2/.gitignore
generated
vendored
Normal file
3
vendor/gopkg.in/irc.v2/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.cover
|
||||
*.test
|
||||
*.out
|
3
vendor/gopkg.in/irc.v2/.gitmodules
generated
vendored
Normal file
3
vendor/gopkg.in/irc.v2/.gitmodules
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "testcases"]
|
||||
path = testcases
|
||||
url = https://github.com/go-irc/irc-parser-tests/
|
21
vendor/gopkg.in/irc.v2/.travis.yml
generated
vendored
Normal file
21
vendor/gopkg.in/irc.v2/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
language: go
|
||||
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
# Grab all deps (should just be test deps)
|
||||
- go get -v -t ./...
|
||||
# Linting deps
|
||||
- go get github.com/alecthomas/gometalinter
|
||||
- gometalinter --install
|
||||
# Remove the go file from the test cases dir as it fails linting
|
||||
- rm ./testcases/*.go
|
||||
|
||||
script:
|
||||
- gometalinter --fast ./... -D gas
|
||||
- go test -race -v ./...
|
||||
- go test -covermode=count -coverprofile=profile.cov
|
||||
|
||||
after_script:
|
||||
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
18
vendor/gopkg.in/irc.v2/LICENSE
generated
vendored
Normal file
18
vendor/gopkg.in/irc.v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
Copyright 2016 Kaleb Elwert
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
104
vendor/gopkg.in/irc.v2/README.md
generated
vendored
Normal file
104
vendor/gopkg.in/irc.v2/README.md
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
# go-irc
|
||||
|
||||
[![GoDoc](https://img.shields.io/badge/doc-GoDoc-blue.svg)](https://godoc.org/github.com/go-irc/irc)
|
||||
[![Build Status](https://img.shields.io/travis/go-irc/irc.svg)](https://travis-ci.org/go-irc/irc)
|
||||
[![Coverage Status](https://img.shields.io/coveralls/go-irc/irc.svg)](https://coveralls.io/github/go-irc/irc?branch=master)
|
||||
|
||||
This package was originally created to only handle message parsing,
|
||||
but has since been expanded to include a small abstraction around a
|
||||
connection and a simple client.
|
||||
|
||||
This library is not designed to hide any of the IRC elements from
|
||||
you. If you just want to build a simple chat bot and don't want to
|
||||
deal with IRC in particular, there are a number of other libraries
|
||||
which provide a more full featured client if that's what you're
|
||||
looking for.
|
||||
|
||||
This library is meant to stay as simple as possible so it can be a
|
||||
building block for other packages.
|
||||
|
||||
This library aims for API compatibility whenever possible. New
|
||||
functions and other additions will most likely not result in a major
|
||||
version increase unless they break the API. This library aims to
|
||||
follow the semver recommendations mentioned on gopkg.in.
|
||||
|
||||
Due to complications in how to support x/net/context vs the built-in context
|
||||
package, only go 1.7+ is officially supported.
|
||||
|
||||
## Import Paths
|
||||
|
||||
All development happens on the `master` branch and when features are
|
||||
considered stable enough, a new release will be tagged.
|
||||
|
||||
As a result of this, there are multiple import locations.
|
||||
|
||||
* `gopkg.in/irc.v2` should be used to develop against the commits
|
||||
tagged as stable
|
||||
* `github.com/go-irc/irc` should be used to develop against the master branch
|
||||
|
||||
## Development
|
||||
|
||||
In order to run the tests, make sure all submodules are up to date. If you are
|
||||
just using this library, these are not needed.
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/belak/irc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn, err := net.Dial("tcp", "chat.freenode.net:6667")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
config := irc.ClientConfig{
|
||||
Nick: "i_have_a_nick",
|
||||
Pass: "password",
|
||||
User: "username",
|
||||
Name: "Full Name",
|
||||
Handler: irc.HandlerFunc(func(c *irc.Client, m *irc.Message) {
|
||||
if m.Command == "001" {
|
||||
// 001 is a welcome event, so we join channels there
|
||||
c.Write("JOIN #bot-test-chan")
|
||||
} else if m.Command == "PRIVMSG" && m.FromChannel() {
|
||||
// Create a handler on all messages.
|
||||
c.WriteMessage(&irc.Message{
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{
|
||||
m.Params[0],
|
||||
m.Trailing(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
// Create the client
|
||||
client := irc.NewClient(conn, config)
|
||||
err = client.Run()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Major Version Changes
|
||||
|
||||
### v1
|
||||
|
||||
Initial release
|
||||
|
||||
### v2
|
||||
|
||||
- CTCP messages will no longer be rewritten. The decision was made that this
|
||||
library should pass through all messages without mangling them.
|
||||
- Remove Message.FromChannel as this is not always accurate, while
|
||||
Client.FromChannel should always be accurate.
|
331
vendor/gopkg.in/irc.v2/client.go
generated
vendored
Normal file
331
vendor/gopkg.in/irc.v2/client.go
generated
vendored
Normal file
@ -0,0 +1,331 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClientConfig is a structure used to configure a Client.
|
||||
type ClientConfig struct {
|
||||
// General connection information.
|
||||
Nick string
|
||||
Pass string
|
||||
User string
|
||||
Name string
|
||||
|
||||
// Connection settings
|
||||
PingFrequency time.Duration
|
||||
PingTimeout time.Duration
|
||||
|
||||
// SendLimit is how frequent messages can be sent. If this is zero,
|
||||
// there will be no limit.
|
||||
SendLimit time.Duration
|
||||
|
||||
// SendBurst is the number of messages which can be sent in a burst.
|
||||
SendBurst int
|
||||
|
||||
// Handler is used for message dispatching.
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
type cap struct {
|
||||
// Requested means that this cap was requested by the user
|
||||
Requested bool
|
||||
|
||||
// Required will be true if this cap is non-optional
|
||||
Required bool
|
||||
|
||||
// Enabled means that this cap was accepted by the server
|
||||
Enabled bool
|
||||
|
||||
// Available means that the server supports this cap
|
||||
Available bool
|
||||
}
|
||||
|
||||
// Client is a wrapper around Conn which is designed to make common operations
|
||||
// much simpler.
|
||||
type Client struct {
|
||||
*Conn
|
||||
config ClientConfig
|
||||
|
||||
// Internal state
|
||||
currentNick string
|
||||
limiter chan struct{}
|
||||
incomingPongChan chan string
|
||||
errChan chan error
|
||||
caps map[string]cap
|
||||
remainingCapResponses int
|
||||
connected bool
|
||||
}
|
||||
|
||||
// NewClient creates a client given an io stream and a client config.
|
||||
func NewClient(rw io.ReadWriter, config ClientConfig) *Client {
|
||||
c := &Client{
|
||||
Conn: NewConn(rw),
|
||||
config: config,
|
||||
errChan: make(chan error, 1),
|
||||
caps: make(map[string]cap),
|
||||
}
|
||||
|
||||
// Replace the writer writeCallback with one of our own
|
||||
c.Conn.Writer.writeCallback = c.writeCallback
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) writeCallback(w *Writer, line string) error {
|
||||
if c.limiter != nil {
|
||||
<-c.limiter
|
||||
}
|
||||
|
||||
_, err := w.writer.Write([]byte(line + "\r\n"))
|
||||
if err != nil {
|
||||
c.sendError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// maybeStartLimiter will start a ticker which will limit how quickly messages
|
||||
// can be written to the connection if the SendLimit is set in the config.
|
||||
func (c *Client) maybeStartLimiter(wg *sync.WaitGroup, exiting chan struct{}) {
|
||||
if c.config.SendLimit == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
// If SendBurst is 0, this will be unbuffered, so keep that in mind.
|
||||
c.limiter = make(chan struct{}, c.config.SendBurst)
|
||||
limitTick := time.NewTicker(c.config.SendLimit)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
var done bool
|
||||
for !done {
|
||||
select {
|
||||
case <-limitTick.C:
|
||||
select {
|
||||
case c.limiter <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case <-exiting:
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
limitTick.Stop()
|
||||
close(c.limiter)
|
||||
c.limiter = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// maybeStartPingLoop will start a goroutine to send out PING messages at the
|
||||
// PingFrequency in the config if the frequency is not 0.
|
||||
func (c *Client) maybeStartPingLoop(wg *sync.WaitGroup, exiting chan struct{}) {
|
||||
if c.config.PingFrequency <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
c.incomingPongChan = make(chan string, 5)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
pingHandlers := make(map[string]chan struct{})
|
||||
ticker := time.NewTicker(c.config.PingFrequency)
|
||||
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Each time we get a tick, we send off a ping and start a
|
||||
// goroutine to handle the pong.
|
||||
timestamp := time.Now().Unix()
|
||||
pongChan := make(chan struct{}, 1)
|
||||
pingHandlers[fmt.Sprintf("%d", timestamp)] = pongChan
|
||||
wg.Add(1)
|
||||
go c.handlePing(timestamp, pongChan, wg, exiting)
|
||||
case data := <-c.incomingPongChan:
|
||||
// Make sure the pong gets routed to the correct
|
||||
// goroutine.
|
||||
|
||||
c := pingHandlers[data]
|
||||
delete(pingHandlers, data)
|
||||
|
||||
if c != nil {
|
||||
c <- struct{}{}
|
||||
}
|
||||
case <-exiting:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) handlePing(timestamp int64, pongChan chan struct{}, wg *sync.WaitGroup, exiting chan struct{}) {
|
||||
defer wg.Done()
|
||||
|
||||
c.Writef("PING :%d", timestamp)
|
||||
|
||||
timer := time.NewTimer(c.config.PingTimeout)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
c.sendError(errors.New("Ping Timeout"))
|
||||
case <-pongChan:
|
||||
return
|
||||
case <-exiting:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// maybeStartCapHandshake will run a CAP LS and all the relevant CAP REQ
|
||||
// commands if there are any CAPs requested.
|
||||
func (c *Client) maybeStartCapHandshake() {
|
||||
if len(c.caps) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.Write("CAP LS")
|
||||
c.remainingCapResponses = 1 // We count the CAP LS response as a normal response
|
||||
for key, cap := range c.caps {
|
||||
if cap.Requested {
|
||||
c.Writef("CAP REQ :%s", key)
|
||||
c.remainingCapResponses++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CapRequest allows you to request IRCv3 capabilities from the server during
|
||||
// the handshake. The behavior is undefined if this is called before the
|
||||
// handshake completes so it is recommended that this be called before Run. If
|
||||
// the CAP is marked as required, the client will exit if that CAP could not be
|
||||
// negotiated during the handshake.
|
||||
func (c *Client) CapRequest(capName string, required bool) {
|
||||
cap := c.caps[capName]
|
||||
cap.Requested = true
|
||||
cap.Required = cap.Required || required
|
||||
c.caps[capName] = cap
|
||||
}
|
||||
|
||||
// CapEnabled allows you to check if a CAP is enabled for this connection. Note
|
||||
// that it will not be populated until after the CAP handshake is done, so it is
|
||||
// recommended to wait to check this until after a message like 001.
|
||||
func (c *Client) CapEnabled(capName string) bool {
|
||||
return c.caps[capName].Enabled
|
||||
}
|
||||
|
||||
// CapAvailable allows you to check if a CAP is available on this server. Note
|
||||
// that it will not be populated until after the CAP handshake is done, so it is
|
||||
// recommended to wait to check this until after a message like 001.
|
||||
func (c *Client) CapAvailable(capName string) bool {
|
||||
return c.caps[capName].Available
|
||||
}
|
||||
|
||||
func (c *Client) sendError(err error) {
|
||||
select {
|
||||
case c.errChan <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) startReadLoop(wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
m, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
c.sendError(err)
|
||||
break
|
||||
}
|
||||
|
||||
if f, ok := clientFilters[m.Command]; ok {
|
||||
f(c, m)
|
||||
}
|
||||
|
||||
if c.config.Handler != nil {
|
||||
c.config.Handler.Handle(c, m)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
// Run starts the main loop for this IRC connection. Note that it may break in
|
||||
// strange and unexpected ways if it is called again before the first connection
|
||||
// exits.
|
||||
func (c *Client) Run() error {
|
||||
return c.RunContext(context.TODO())
|
||||
}
|
||||
|
||||
// RunContext is the same as Run but a context.Context can be passed in for
|
||||
// cancelation.
|
||||
func (c *Client) RunContext(ctx context.Context) error {
|
||||
// exiting is used by the main goroutine here to ensure any sub-goroutines
|
||||
// get closed when exiting.
|
||||
exiting := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
c.maybeStartLimiter(&wg, exiting)
|
||||
c.maybeStartPingLoop(&wg, exiting)
|
||||
|
||||
c.currentNick = c.config.Nick
|
||||
|
||||
if c.config.Pass != "" {
|
||||
c.Writef("PASS :%s", c.config.Pass)
|
||||
}
|
||||
|
||||
c.maybeStartCapHandshake()
|
||||
|
||||
// This feels wrong because it results in CAP LS, CAP REQ, NICK, USER, CAP
|
||||
// END, but it works and lets us keep the code a bit simpler.
|
||||
c.Writef("NICK :%s", c.config.Nick)
|
||||
c.Writef("USER %s 0.0.0.0 0.0.0.0 :%s", c.config.User, c.config.Name)
|
||||
|
||||
// Now that the handshake is pretty much done, we can start listening for
|
||||
// messages.
|
||||
c.startReadLoop(&wg)
|
||||
|
||||
// Wait for an error from any goroutine or for the context to time out, then
|
||||
// signal we're exiting and wait for the goroutines to exit.
|
||||
var err error
|
||||
select {
|
||||
case err = <-c.errChan:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
close(exiting)
|
||||
wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CurrentNick returns what the nick of the client is known to be at this point
|
||||
// in time.
|
||||
func (c *Client) CurrentNick() string {
|
||||
return c.currentNick
|
||||
}
|
||||
|
||||
// FromChannel takes a Message representing a PRIVMSG and returns if that
|
||||
// message came from a channel or directly from a user.
|
||||
func (c *Client) FromChannel(m *Message) bool {
|
||||
if len(m.Params) < 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// The first param is the target, so if this doesn't match the current nick,
|
||||
// the message came from a channel.
|
||||
return m.Params[0] != c.currentNick
|
||||
}
|
151
vendor/gopkg.in/irc.v2/client_handlers.go
generated
vendored
Normal file
151
vendor/gopkg.in/irc.v2/client_handlers.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type clientFilter func(*Client, *Message)
|
||||
|
||||
// clientFilters are pre-processing which happens for certain message
|
||||
// types. These were moved from below to keep the complexity of each
|
||||
// component down.
|
||||
var clientFilters = map[string]clientFilter{
|
||||
"001": handle001,
|
||||
"433": handle433,
|
||||
"437": handle437,
|
||||
"PING": handlePing,
|
||||
"PONG": handlePong,
|
||||
"NICK": handleNick,
|
||||
"CAP": handleCap,
|
||||
}
|
||||
|
||||
// From rfc2812 section 5.1 (Command responses)
|
||||
//
|
||||
// 001 RPL_WELCOME
|
||||
// "Welcome to the Internet Relay Network
|
||||
// <nick>!<user>@<host>"
|
||||
func handle001(c *Client, m *Message) {
|
||||
c.currentNick = m.Params[0]
|
||||
c.connected = true
|
||||
}
|
||||
|
||||
// From rfc2812 section 5.2 (Error Replies)
|
||||
//
|
||||
// 433 ERR_NICKNAMEINUSE
|
||||
// "<nick> :Nickname is already in use"
|
||||
//
|
||||
// - Returned when a NICK message is processed that results
|
||||
// in an attempt to change to a currently existing
|
||||
// nickname.
|
||||
func handle433(c *Client, m *Message) {
|
||||
// We only want to try and handle nick collisions during the initial
|
||||
// handshake.
|
||||
if c.connected {
|
||||
return
|
||||
}
|
||||
c.currentNick = c.currentNick + "_"
|
||||
c.Writef("NICK :%s", c.currentNick)
|
||||
}
|
||||
|
||||
// From rfc2812 section 5.2 (Error Replies)
|
||||
//
|
||||
// 437 ERR_UNAVAILRESOURCE
|
||||
// "<nick/channel> :Nick/channel is temporarily unavailable"
|
||||
//
|
||||
// - Returned by a server to a user trying to join a channel
|
||||
// currently blocked by the channel delay mechanism.
|
||||
//
|
||||
// - Returned by a server to a user trying to change nickname
|
||||
// when the desired nickname is blocked by the nick delay
|
||||
// mechanism.
|
||||
func handle437(c *Client, m *Message) {
|
||||
// We only want to try and handle nick collisions during the initial
|
||||
// handshake.
|
||||
if c.connected {
|
||||
return
|
||||
}
|
||||
c.currentNick = c.currentNick + "_"
|
||||
c.Writef("NICK :%s", c.currentNick)
|
||||
}
|
||||
|
||||
func handlePing(c *Client, m *Message) {
|
||||
reply := m.Copy()
|
||||
reply.Command = "PONG"
|
||||
c.WriteMessage(reply)
|
||||
}
|
||||
|
||||
func handlePong(c *Client, m *Message) {
|
||||
if c.incomingPongChan != nil {
|
||||
select {
|
||||
case c.incomingPongChan <- m.Trailing():
|
||||
default:
|
||||
// Note that this return isn't really needed, but it helps some code
|
||||
// coverage tools actually see this line.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleNick(c *Client, m *Message) {
|
||||
if m.Prefix.Name == c.currentNick && len(m.Params) > 0 {
|
||||
c.currentNick = m.Params[0]
|
||||
}
|
||||
}
|
||||
|
||||
var capFilters = map[string]clientFilter{
|
||||
"LS": handleCapLs,
|
||||
"ACK": handleCapAck,
|
||||
"NAK": handleCapNak,
|
||||
}
|
||||
|
||||
func handleCap(c *Client, m *Message) {
|
||||
if c.remainingCapResponses <= 0 || len(m.Params) <= 2 {
|
||||
return
|
||||
}
|
||||
|
||||
if filter, ok := capFilters[m.Params[1]]; ok {
|
||||
filter(c, m)
|
||||
}
|
||||
|
||||
if c.remainingCapResponses <= 0 {
|
||||
for key, cap := range c.caps {
|
||||
if cap.Required && !cap.Enabled {
|
||||
c.sendError(fmt.Errorf("CAP %s requested but not accepted", key))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Write("CAP END")
|
||||
}
|
||||
}
|
||||
|
||||
func handleCapLs(c *Client, m *Message) {
|
||||
for _, key := range strings.Split(m.Trailing(), " ") {
|
||||
cap := c.caps[key]
|
||||
cap.Available = true
|
||||
c.caps[key] = cap
|
||||
}
|
||||
c.remainingCapResponses--
|
||||
}
|
||||
|
||||
func handleCapAck(c *Client, m *Message) {
|
||||
for _, key := range strings.Split(m.Trailing(), " ") {
|
||||
cap := c.caps[key]
|
||||
cap.Enabled = true
|
||||
c.caps[key] = cap
|
||||
}
|
||||
c.remainingCapResponses--
|
||||
}
|
||||
|
||||
func handleCapNak(c *Client, m *Message) {
|
||||
// If we got a NAK and this REQ was required, we need to bail
|
||||
// with an error.
|
||||
for _, key := range strings.Split(m.Trailing(), " ") {
|
||||
if c.caps[key].Required {
|
||||
c.sendError(fmt.Errorf("CAP %s requested but was rejected", key))
|
||||
return
|
||||
}
|
||||
}
|
||||
c.remainingCapResponses--
|
||||
}
|
103
vendor/gopkg.in/irc.v2/conn.go
generated
vendored
Normal file
103
vendor/gopkg.in/irc.v2/conn.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Conn represents a simple IRC client. It embeds an irc.Reader and an
|
||||
// irc.Writer.
|
||||
type Conn struct {
|
||||
*Reader
|
||||
*Writer
|
||||
}
|
||||
|
||||
// NewConn creates a new Conn
|
||||
func NewConn(rw io.ReadWriter) *Conn {
|
||||
return &Conn{
|
||||
NewReader(rw),
|
||||
NewWriter(rw),
|
||||
}
|
||||
}
|
||||
|
||||
// Writer is the outgoing side of a connection.
|
||||
type Writer struct {
|
||||
// DebugCallback is called for each outgoing message. The name of this may
|
||||
// not be stable.
|
||||
DebugCallback func(line string)
|
||||
|
||||
// Internal fields
|
||||
writer io.Writer
|
||||
writeCallback func(w *Writer, line string) error
|
||||
}
|
||||
|
||||
func defaultWriteCallback(w *Writer, line string) error {
|
||||
_, err := w.writer.Write([]byte(line + "\r\n"))
|
||||
return err
|
||||
}
|
||||
|
||||
// NewWriter creates an irc.Writer from an io.Writer.
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{nil, w, defaultWriteCallback}
|
||||
}
|
||||
|
||||
// Write is a simple function which will write the given line to the
|
||||
// underlying connection.
|
||||
func (w *Writer) Write(line string) error {
|
||||
if w.DebugCallback != nil {
|
||||
w.DebugCallback(line)
|
||||
}
|
||||
|
||||
return w.writeCallback(w, line)
|
||||
}
|
||||
|
||||
// Writef is a wrapper around the connection's Write method and
|
||||
// fmt.Sprintf. Simply use it to send a message as you would normally
|
||||
// use fmt.Printf.
|
||||
func (w *Writer) Writef(format string, args ...interface{}) error {
|
||||
return w.Write(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// WriteMessage writes the given message to the stream
|
||||
func (w *Writer) WriteMessage(m *Message) error {
|
||||
return w.Write(m.String())
|
||||
}
|
||||
|
||||
// Reader is the incoming side of a connection. The data will be
|
||||
// buffered, so do not re-use the io.Reader used to create the
|
||||
// irc.Reader.
|
||||
type Reader struct {
|
||||
// DebugCallback is called for each incoming message. The name of this may
|
||||
// not be stable.
|
||||
DebugCallback func(string)
|
||||
|
||||
// Internal fields
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
// NewReader creates an irc.Reader from an io.Reader. Note that once a reader is
|
||||
// passed into this function, you should no longer use it as it is being used
|
||||
// inside a bufio.Reader so you cannot rely on only the amount of data for a
|
||||
// Message being read when you call ReadMessage.
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
return &Reader{
|
||||
nil,
|
||||
bufio.NewReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
// ReadMessage returns the next message from the stream or an error.
|
||||
func (r *Reader) ReadMessage() (*Message, error) {
|
||||
line, err := r.reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.DebugCallback != nil {
|
||||
r.DebugCallback(line)
|
||||
}
|
||||
|
||||
// Parse the message from our line
|
||||
return ParseMessage(line)
|
||||
}
|
1
vendor/gopkg.in/irc.v2/go.mod
generated
vendored
Normal file
1
vendor/gopkg.in/irc.v2/go.mod
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
module "github.com/go-irc/irc/v2"
|
16
vendor/gopkg.in/irc.v2/handler.go
generated
vendored
Normal file
16
vendor/gopkg.in/irc.v2/handler.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package irc
|
||||
|
||||
// Handler is a simple interface meant for dispatching a message from
|
||||
// a Client connection.
|
||||
type Handler interface {
|
||||
Handle(*Client, *Message)
|
||||
}
|
||||
|
||||
// HandlerFunc is a simple wrapper around a function which allows it
|
||||
// to be used as a Handler.
|
||||
type HandlerFunc func(*Client, *Message)
|
||||
|
||||
// Handle calls f(c, m)
|
||||
func (f HandlerFunc) Handle(c *Client, m *Message) {
|
||||
f(c, m)
|
||||
}
|
394
vendor/gopkg.in/irc.v2/parser.go
generated
vendored
Normal file
394
vendor/gopkg.in/irc.v2/parser.go
generated
vendored
Normal file
@ -0,0 +1,394 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var tagDecodeSlashMap = map[rune]rune{
|
||||
':': ';',
|
||||
's': ' ',
|
||||
'\\': '\\',
|
||||
'r': '\r',
|
||||
'n': '\n',
|
||||
}
|
||||
|
||||
var tagEncodeMap = map[rune]string{
|
||||
';': "\\:",
|
||||
' ': "\\s",
|
||||
'\\': "\\\\",
|
||||
'\r': "\\r",
|
||||
'\n': "\\n",
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrZeroLengthMessage is returned when parsing if the input is
|
||||
// zero-length.
|
||||
ErrZeroLengthMessage = errors.New("irc: Cannot parse zero-length message")
|
||||
|
||||
// ErrMissingDataAfterPrefix is returned when parsing if there is
|
||||
// no message data after the prefix.
|
||||
ErrMissingDataAfterPrefix = errors.New("irc: No message data after prefix")
|
||||
|
||||
// ErrMissingDataAfterTags is returned when parsing if there is no
|
||||
// message data after the tags.
|
||||
ErrMissingDataAfterTags = errors.New("irc: No message data after tags")
|
||||
|
||||
// ErrMissingCommand is returned when parsing if there is no
|
||||
// command in the parsed message.
|
||||
ErrMissingCommand = errors.New("irc: Missing message command")
|
||||
)
|
||||
|
||||
// TagValue represents the value of a tag.
|
||||
type TagValue string
|
||||
|
||||
// ParseTagValue parses a TagValue from the connection. If you need to
|
||||
// set a TagValue, you probably want to just set the string itself, so
|
||||
// it will be encoded properly.
|
||||
func ParseTagValue(v string) TagValue {
|
||||
ret := &bytes.Buffer{}
|
||||
|
||||
input := bytes.NewBufferString(v)
|
||||
|
||||
for {
|
||||
c, _, err := input.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if c == '\\' {
|
||||
c2, _, err := input.ReadRune()
|
||||
|
||||
// If we got a backslash then the end of the tag value, we should
|
||||
// just ignore the backslash.
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if replacement, ok := tagDecodeSlashMap[c2]; ok {
|
||||
ret.WriteRune(replacement)
|
||||
} else {
|
||||
ret.WriteRune(c2)
|
||||
}
|
||||
} else {
|
||||
ret.WriteRune(c)
|
||||
}
|
||||
}
|
||||
|
||||
return TagValue(ret.String())
|
||||
}
|
||||
|
||||
// Encode converts a TagValue to the format in the connection.
|
||||
func (v TagValue) Encode() string {
|
||||
ret := &bytes.Buffer{}
|
||||
|
||||
for _, c := range v {
|
||||
if replacement, ok := tagEncodeMap[c]; ok {
|
||||
ret.WriteString(replacement)
|
||||
} else {
|
||||
ret.WriteRune(c)
|
||||
}
|
||||
}
|
||||
|
||||
return ret.String()
|
||||
}
|
||||
|
||||
// Tags represents the IRCv3 message tags.
|
||||
type Tags map[string]TagValue
|
||||
|
||||
// ParseTags takes a tag string and parses it into a tag map. It will
|
||||
// always return a tag map, even if there are no valid tags.
|
||||
func ParseTags(line string) Tags {
|
||||
ret := Tags{}
|
||||
|
||||
tags := strings.Split(line, ";")
|
||||
for _, tag := range tags {
|
||||
parts := strings.SplitN(tag, "=", 2)
|
||||
if len(parts) < 2 {
|
||||
ret[parts[0]] = ""
|
||||
continue
|
||||
}
|
||||
|
||||
ret[parts[0]] = ParseTagValue(parts[1])
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetTag is a convenience method to look up a tag in the map.
|
||||
func (t Tags) GetTag(key string) (string, bool) {
|
||||
ret, ok := t[key]
|
||||
return string(ret), ok
|
||||
}
|
||||
|
||||
// Copy will create a new copy of all IRC tags attached to this
|
||||
// message.
|
||||
func (t Tags) Copy() Tags {
|
||||
ret := Tags{}
|
||||
|
||||
for k, v := range t {
|
||||
ret[k] = v
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// String ensures this is stringable
|
||||
func (t Tags) String() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for k, v := range t {
|
||||
buf.WriteByte(';')
|
||||
buf.WriteString(k)
|
||||
if v != "" {
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(v.Encode())
|
||||
}
|
||||
}
|
||||
|
||||
// We don't need the first byte because that's an extra ';'
|
||||
// character.
|
||||
buf.ReadByte()
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Prefix represents the prefix of a message, generally the user who sent it
|
||||
type Prefix struct {
|
||||
// Name will contain the nick of who sent the message, the
|
||||
// server who sent the message, or a blank string
|
||||
Name string
|
||||
|
||||
// User will either contain the user who sent the message or a blank string
|
||||
User string
|
||||
|
||||
// Host will either contain the host of who sent the message or a blank string
|
||||
Host string
|
||||
}
|
||||
|
||||
// ParsePrefix takes an identity string and parses it into an
|
||||
// identity struct. It will always return an Prefix struct and never
|
||||
// nil.
|
||||
func ParsePrefix(line string) *Prefix {
|
||||
// Start by creating an Prefix with nothing but the host
|
||||
id := &Prefix{
|
||||
Name: line,
|
||||
}
|
||||
|
||||
uh := strings.SplitN(id.Name, "@", 2)
|
||||
if len(uh) == 2 {
|
||||
id.Name, id.Host = uh[0], uh[1]
|
||||
}
|
||||
|
||||
nu := strings.SplitN(id.Name, "!", 2)
|
||||
if len(nu) == 2 {
|
||||
id.Name, id.User = nu[0], nu[1]
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Copy will create a new copy of an Prefix
|
||||
func (p *Prefix) Copy() *Prefix {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
newPrefix := &Prefix{}
|
||||
|
||||
*newPrefix = *p
|
||||
|
||||
return newPrefix
|
||||
}
|
||||
|
||||
// String ensures this is stringable
|
||||
func (p *Prefix) String() string {
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteString(p.Name)
|
||||
|
||||
if p.User != "" {
|
||||
buf.WriteString("!")
|
||||
buf.WriteString(p.User)
|
||||
}
|
||||
|
||||
if p.Host != "" {
|
||||
buf.WriteString("@")
|
||||
buf.WriteString(p.Host)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Message represents a line parsed from the server
|
||||
type Message struct {
|
||||
// Each message can have IRCv3 tags
|
||||
Tags
|
||||
|
||||
// Each message can have a Prefix
|
||||
*Prefix
|
||||
|
||||
// Command is which command is being called.
|
||||
Command string
|
||||
|
||||
// Params are all the arguments for the command.
|
||||
Params []string
|
||||
}
|
||||
|
||||
// MustParseMessage calls ParseMessage and either returns the message
|
||||
// or panics if an error is returned.
|
||||
func MustParseMessage(line string) *Message {
|
||||
m, err := ParseMessage(line)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ParseMessage takes a message string (usually a whole line) and
|
||||
// parses it into a Message struct. This will return nil in the case
|
||||
// of invalid messages.
|
||||
func ParseMessage(line string) (*Message, error) {
|
||||
// Trim the line and make sure we have data
|
||||
line = strings.TrimRight(line, "\r\n")
|
||||
if len(line) == 0 {
|
||||
return nil, ErrZeroLengthMessage
|
||||
}
|
||||
|
||||
c := &Message{
|
||||
Tags: Tags{},
|
||||
Prefix: &Prefix{},
|
||||
}
|
||||
|
||||
if line[0] == '@' {
|
||||
split := strings.SplitN(line, " ", 2)
|
||||
if len(split) < 2 {
|
||||
return nil, ErrMissingDataAfterTags
|
||||
}
|
||||
|
||||
c.Tags = ParseTags(split[0][1:])
|
||||
line = split[1]
|
||||
}
|
||||
|
||||
if line[0] == ':' {
|
||||
split := strings.SplitN(line, " ", 2)
|
||||
if len(split) < 2 {
|
||||
return nil, ErrMissingDataAfterPrefix
|
||||
}
|
||||
|
||||
// Parse the identity, if there was one
|
||||
c.Prefix = ParsePrefix(split[0][1:])
|
||||
line = split[1]
|
||||
}
|
||||
|
||||
// Split out the trailing then the rest of the args. Because
|
||||
// we expect there to be at least one result as an arg (the
|
||||
// command) we don't need to special case the trailing arg and
|
||||
// can just attempt a split on " :"
|
||||
split := strings.SplitN(line, " :", 2)
|
||||
c.Params = strings.FieldsFunc(split[0], func(r rune) bool {
|
||||
return r == ' '
|
||||
})
|
||||
|
||||
// If there are no args, we need to bail because we need at
|
||||
// least the command.
|
||||
if len(c.Params) == 0 {
|
||||
return nil, ErrMissingCommand
|
||||
}
|
||||
|
||||
// If we had a trailing arg, append it to the other args
|
||||
if len(split) == 2 {
|
||||
c.Params = append(c.Params, split[1])
|
||||
}
|
||||
|
||||
// Because of how it's parsed, the Command will show up as the
|
||||
// first arg.
|
||||
c.Command = strings.ToUpper(c.Params[0])
|
||||
c.Params = c.Params[1:]
|
||||
|
||||
// If there are no params, set it to nil, to make writing tests and other
|
||||
// things simpler.
|
||||
if len(c.Params) == 0 {
|
||||
c.Params = nil
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Trailing returns the last argument in the Message or an empty string
|
||||
// if there are no args
|
||||
func (m *Message) Trailing() string {
|
||||
if len(m.Params) < 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return m.Params[len(m.Params)-1]
|
||||
}
|
||||
|
||||
// Copy will create a new copy of an message
|
||||
func (m *Message) Copy() *Message {
|
||||
// Create a new message
|
||||
newMessage := &Message{}
|
||||
|
||||
// Copy stuff from the old message
|
||||
*newMessage = *m
|
||||
|
||||
// Copy any IRcv3 tags
|
||||
newMessage.Tags = m.Tags.Copy()
|
||||
|
||||
// Copy the Prefix
|
||||
newMessage.Prefix = m.Prefix.Copy()
|
||||
|
||||
// Copy the Params slice
|
||||
newMessage.Params = append(make([]string, 0, len(m.Params)), m.Params...)
|
||||
|
||||
// Similar to parsing, if Params is empty, set it to nil
|
||||
if len(newMessage.Params) == 0 {
|
||||
newMessage.Params = nil
|
||||
}
|
||||
|
||||
return newMessage
|
||||
}
|
||||
|
||||
// String ensures this is stringable
|
||||
func (m *Message) String() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Write any IRCv3 tags if they exist in the message
|
||||
if len(m.Tags) > 0 {
|
||||
buf.WriteByte('@')
|
||||
buf.WriteString(m.Tags.String())
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
|
||||
// Add the prefix if we have one
|
||||
if m.Prefix != nil && m.Prefix.Name != "" {
|
||||
buf.WriteByte(':')
|
||||
buf.WriteString(m.Prefix.String())
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
|
||||
// Add the command since we know we'll always have one
|
||||
buf.WriteString(m.Command)
|
||||
|
||||
if len(m.Params) > 0 {
|
||||
args := m.Params[:len(m.Params)-1]
|
||||
trailing := m.Params[len(m.Params)-1]
|
||||
|
||||
if len(args) > 0 {
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(strings.Join(args, " "))
|
||||
}
|
||||
|
||||
// If trailing is zero-length, contains a space or starts with
|
||||
// a : we need to actually specify that it's trailing.
|
||||
if len(trailing) == 0 || strings.ContainsRune(trailing, ' ') || trailing[0] == ':' {
|
||||
buf.WriteString(" :")
|
||||
} else {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
buf.WriteString(trailing)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
50
vendor/gopkg.in/irc.v2/utils.go
generated
vendored
Normal file
50
vendor/gopkg.in/irc.v2/utils.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var maskTranslations = map[byte]string{
|
||||
'?': ".",
|
||||
'*': ".*",
|
||||
}
|
||||
|
||||
// MaskToRegex converts an irc mask to a go Regexp for more convenient
|
||||
// use. This should never return an error, but we have this here just
|
||||
// in case.
|
||||
func MaskToRegex(rawMask string) (*regexp.Regexp, error) {
|
||||
input := bytes.NewBufferString(rawMask)
|
||||
|
||||
output := &bytes.Buffer{}
|
||||
output.WriteByte('^')
|
||||
|
||||
for {
|
||||
c, err := input.ReadByte()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if c == '\\' {
|
||||
c, err = input.ReadByte()
|
||||
if err != nil {
|
||||
output.WriteString(regexp.QuoteMeta("\\"))
|
||||
break
|
||||
}
|
||||
|
||||
if c == '?' || c == '*' || c == '\\' {
|
||||
output.WriteString(regexp.QuoteMeta(string(c)))
|
||||
} else {
|
||||
output.WriteString(regexp.QuoteMeta("\\" + string(c)))
|
||||
}
|
||||
} else if trans, ok := maskTranslations[c]; ok {
|
||||
output.WriteString(trans)
|
||||
} else {
|
||||
output.WriteString(regexp.QuoteMeta(string(c)))
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteByte('$')
|
||||
|
||||
return regexp.Compile(output.String())
|
||||
}
|
Loading…
Reference in New Issue
Block a user