0

Initial commit

This commit is contained in:
eleksir
2022-09-04 02:34:54 +03:00
commit d9de157ad8
1017 changed files with 407553 additions and 0 deletions

27
vendor/github.com/thoj/go-ircevent/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
// Copyright (c) 2009 Thomas Jager. All rights reserved.
//
// 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.
//
// 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.

79
vendor/github.com/thoj/go-ircevent/README.markdown generated vendored Normal file
View File

@@ -0,0 +1,79 @@
Description
-----------
Event based irc client library.
Features
--------
* Event based. Register Callbacks for the events you need to handle.
* Handles basic irc demands for you
* Standard CTCP
* Reconnections on errors
* Detect stoned servers
Install
-------
$ go get github.com/thoj/go-ircevent
Example
-------
See [examples/simple/simple.go](examples/simple/simple.go) and [irc_test.go](irc_test.go)
Events for callbacks
--------------------
* 001 Welcome
* PING
* CTCP Unknown CTCP
* CTCP_VERSION Version request (Handled internaly)
* CTCP_USERINFO
* CTCP_CLIENTINFO
* CTCP_TIME
* CTCP_PING
* CTCP_ACTION (/me)
* PRIVMSG
* MODE
* JOIN
+Many more
AddCallback Example
-------------------
ircobj.AddCallback("PRIVMSG", func(event *irc.Event) {
//event.Message() contains the message
//event.Nick Contains the sender
//event.Arguments[0] Contains the channel
});
Please note: Callbacks are run in the main thread. If a callback needs a long
time to execute please run it in a new thread.
Example:
ircobj.AddCallback("PRIVMSG", func(event *irc.Event) {
go func(event *irc.Event) {
//event.Message() contains the message
//event.Nick Contains the sender
//event.Arguments[0] Contains the channel
}(event)
});
Commands
--------
ircobj := irc.IRC("<nick>", "<user>") //Create new ircobj
//Set options
ircobj.UseTLS = true //default is false
//ircobj.TLSOptions //set ssl options
ircobj.Password = "[server password]"
//Commands
ircobj.Connect("irc.someserver.com:6667") //Connect to server
ircobj.SendRaw("<string>") //sends string to server. Adds \r\n
ircobj.SendRawf("<formatstring>", ...) //sends formatted string to server.n
ircobj.Join("<#channel> [password]")
ircobj.Nick("newnick")
ircobj.Privmsg("<nickname | #channel>", "msg") // sends a message to either a certain nick or a channel
ircobj.Privmsgf(<nickname | #channel>, "<formatstring>", ...)
ircobj.Notice("<nickname | #channel>", "msg")
ircobj.Noticef("<nickname | #channel>", "<formatstring>", ...)

631
vendor/github.com/thoj/go-ircevent/irc.go generated vendored Normal file
View File

@@ -0,0 +1,631 @@
// Copyright 2009 Thomas Jager <mail@jager.no> All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
This package provides an event based IRC client library. It allows to
register callbacks for the events you need to handle. Its features
include handling standard CTCP, reconnecting on errors and detecting
stones servers.
Details of the IRC protocol can be found in the following RFCs:
https://tools.ietf.org/html/rfc1459
https://tools.ietf.org/html/rfc2810
https://tools.ietf.org/html/rfc2811
https://tools.ietf.org/html/rfc2812
https://tools.ietf.org/html/rfc2813
The details of the client-to-client protocol (CTCP) can be found here: http://www.irchelp.org/irchelp/rfc/ctcpspec.html
*/
package irc
import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"time"
"golang.org/x/net/proxy"
"golang.org/x/text/encoding"
)
const (
VERSION = "go-ircevent v2.1"
)
const CAP_TIMEOUT = time.Second * 15
var ErrDisconnected = errors.New("Disconnect Called")
// Read data from a connection. To be used as a goroutine.
func (irc *Connection) readLoop() {
defer irc.Done()
r := irc.Encoding.NewDecoder().Reader(irc.socket)
br := bufio.NewReaderSize(r, 512)
errChan := irc.ErrorChan()
for {
select {
case <-irc.end:
return
default:
// Set a read deadline based on the combined timeout and ping frequency
// We should ALWAYS have received a response from the server within the timeout
// after our own pings
if irc.socket != nil {
irc.socket.SetReadDeadline(time.Now().Add(irc.Timeout + irc.PingFreq))
}
msg, err := br.ReadString('\n')
// We got past our blocking read, so bin timeout
if irc.socket != nil {
var zero time.Time
irc.socket.SetReadDeadline(zero)
}
if err != nil {
errChan <- err
return
}
if irc.Debug {
irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg))
}
irc.lastMessageMutex.Lock()
irc.lastMessage = time.Now()
irc.lastMessageMutex.Unlock()
event, err := parseToEvent(msg)
if err == nil {
event.Connection = irc
irc.RunCallbacks(event)
}
}
}
}
// Unescape tag values as defined in the IRCv3.2 message tags spec
// http://ircv3.net/specs/core/message-tags-3.2.html
func unescapeTagValue(value string) string {
value = strings.Replace(value, "\\:", ";", -1)
value = strings.Replace(value, "\\s", " ", -1)
value = strings.Replace(value, "\\\\", "\\", -1)
value = strings.Replace(value, "\\r", "\r", -1)
value = strings.Replace(value, "\\n", "\n", -1)
return value
}
//Parse raw irc messages
func parseToEvent(msg string) (*Event, error) {
msg = strings.TrimSuffix(msg, "\n") //Remove \r\n
msg = strings.TrimSuffix(msg, "\r")
event := &Event{Raw: msg}
if len(msg) < 5 {
return nil, errors.New("Malformed msg from server")
}
if msg[0] == '@' {
// IRCv3 Message Tags
if i := strings.Index(msg, " "); i > -1 {
event.Tags = make(map[string]string)
tags := strings.Split(msg[1:i], ";")
for _, data := range tags {
parts := strings.SplitN(data, "=", 2)
if len(parts) == 1 {
event.Tags[parts[0]] = ""
} else {
event.Tags[parts[0]] = unescapeTagValue(parts[1])
}
}
msg = msg[i+1 : len(msg)]
} else {
return nil, errors.New("Malformed msg from server")
}
}
if msg[0] == ':' {
if i := strings.Index(msg, " "); i > -1 {
event.Source = msg[1:i]
msg = msg[i+1 : len(msg)]
} else {
return nil, errors.New("Malformed msg from server")
}
if i, j := strings.Index(event.Source, "!"), strings.Index(event.Source, "@"); i > -1 && j > -1 && i < j {
event.Nick = event.Source[0:i]
event.User = event.Source[i+1 : j]
event.Host = event.Source[j+1 : len(event.Source)]
}
}
split := strings.SplitN(msg, " :", 2)
args := strings.Split(split[0], " ")
event.Code = strings.ToUpper(args[0])
event.Arguments = args[1:]
if len(split) > 1 {
event.Arguments = append(event.Arguments, split[1])
}
return event, nil
}
// Loop to write to a connection. To be used as a goroutine.
func (irc *Connection) writeLoop() {
defer irc.Done()
w := irc.Encoding.NewEncoder().Writer(irc.socket)
errChan := irc.ErrorChan()
for {
select {
case <-irc.end:
return
case b, ok := <-irc.pwrite:
if !ok || b == "" || irc.socket == nil {
return
}
if irc.Debug {
irc.Log.Printf("--> %s\n", strings.TrimSpace(b))
}
// Set a write deadline based on the time out
irc.socket.SetWriteDeadline(time.Now().Add(irc.Timeout))
_, err := w.Write([]byte(b))
// Past blocking write, bin timeout
var zero time.Time
irc.socket.SetWriteDeadline(zero)
if err != nil {
errChan <- err
return
}
}
}
}
// Pings the server if we have not received any messages for 5 minutes
// to keep the connection alive. To be used as a goroutine.
func (irc *Connection) pingLoop() {
defer irc.Done()
ticker := time.NewTicker(1 * time.Minute) // Tick every minute for monitoring
ticker2 := time.NewTicker(irc.PingFreq) // Tick at the ping frequency.
for {
select {
case <-ticker.C:
//Ping if we haven't received anything from the server within the keep alive period
irc.lastMessageMutex.Lock()
if time.Since(irc.lastMessage) >= irc.KeepAlive {
irc.SendRawf("PING %d", time.Now().UnixNano())
}
irc.lastMessageMutex.Unlock()
case <-ticker2.C:
//Ping at the ping frequency
irc.SendRawf("PING %d", time.Now().UnixNano())
//Try to recapture nickname if it's not as configured.
irc.Lock()
if irc.nick != irc.nickcurrent {
irc.nickcurrent = irc.nick
irc.SendRawf("NICK %s", irc.nick)
}
irc.Unlock()
case <-irc.end:
ticker.Stop()
ticker2.Stop()
return
}
}
}
func (irc *Connection) isQuitting() bool {
irc.Lock()
defer irc.Unlock()
return irc.quit
}
// Main loop to control the connection.
func (irc *Connection) Loop() {
errChan := irc.ErrorChan()
for !irc.isQuitting() {
err := <-errChan
if irc.end != nil {
close(irc.end)
}
irc.Wait()
for !irc.isQuitting() {
irc.Log.Printf("Error, disconnected: %s\n", err)
if err = irc.Reconnect(); err != nil {
irc.Log.Printf("Error while reconnecting: %s\n", err)
time.Sleep(60 * time.Second)
} else {
errChan = irc.ErrorChan()
break
}
}
}
}
// Quit the current connection and disconnect from the server
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.6
func (irc *Connection) Quit() {
quit := "QUIT"
if irc.QuitMessage != "" {
quit = fmt.Sprintf("QUIT :%s", irc.QuitMessage)
}
irc.SendRaw(quit)
irc.Lock()
irc.stopped = true
irc.quit = true
irc.Unlock()
}
// Use the connection to join a given channel.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.1
func (irc *Connection) Join(channel string) {
irc.pwrite <- fmt.Sprintf("JOIN %s\r\n", channel)
}
// Leave a given channel.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.2
func (irc *Connection) Part(channel string) {
irc.pwrite <- fmt.Sprintf("PART %s\r\n", channel)
}
// Send a notification to a nickname. This is similar to Privmsg but must not receive replies.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2
func (irc *Connection) Notice(target, message string) {
irc.pwrite <- fmt.Sprintf("NOTICE %s :%s\r\n", target, message)
}
// Send a formated notification to a nickname.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2
func (irc *Connection) Noticef(target, format string, a ...interface{}) {
irc.Notice(target, fmt.Sprintf(format, a...))
}
// Send (action) message to a target (channel or nickname).
// No clear RFC on this one...
func (irc *Connection) Action(target, message string) {
irc.pwrite <- fmt.Sprintf("PRIVMSG %s :\001ACTION %s\001\r\n", target, message)
}
// Send formatted (action) message to a target (channel or nickname).
func (irc *Connection) Actionf(target, format string, a ...interface{}) {
irc.Action(target, fmt.Sprintf(format, a...))
}
// Send (private) message to a target (channel or nickname).
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.1
func (irc *Connection) Privmsg(target, message string) {
irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message)
}
// Send formated string to specified target (channel or nickname).
func (irc *Connection) Privmsgf(target, format string, a ...interface{}) {
irc.Privmsg(target, fmt.Sprintf(format, a...))
}
// Kick <user> from <channel> with <msg>. For no message, pass empty string ("")
func (irc *Connection) Kick(user, channel, msg string) {
var cmd bytes.Buffer
cmd.WriteString(fmt.Sprintf("KICK %s %s", channel, user))
if msg != "" {
cmd.WriteString(fmt.Sprintf(" :%s", msg))
}
cmd.WriteString("\r\n")
irc.pwrite <- cmd.String()
}
// Kick all <users> from <channel> with <msg>. For no message, pass
// empty string ("")
func (irc *Connection) MultiKick(users []string, channel string, msg string) {
var cmd bytes.Buffer
cmd.WriteString(fmt.Sprintf("KICK %s %s", channel, strings.Join(users, ",")))
if msg != "" {
cmd.WriteString(fmt.Sprintf(" :%s", msg))
}
cmd.WriteString("\r\n")
irc.pwrite <- cmd.String()
}
// Send raw string.
func (irc *Connection) SendRaw(message string) {
irc.pwrite <- message + "\r\n"
}
// Send raw formated string.
func (irc *Connection) SendRawf(format string, a ...interface{}) {
irc.SendRaw(fmt.Sprintf(format, a...))
}
// Set (new) nickname.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.2
func (irc *Connection) Nick(n string) {
irc.nick = n
irc.SendRawf("NICK %s", n)
}
// Determine nick currently used with the connection.
func (irc *Connection) GetNick() string {
return irc.nickcurrent
}
// Query information about a particular nickname.
// RFC 1459: https://tools.ietf.org/html/rfc1459#section-4.5.2
func (irc *Connection) Whois(nick string) {
irc.SendRawf("WHOIS %s", nick)
}
// Query information about a given nickname in the server.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.5.1
func (irc *Connection) Who(nick string) {
irc.SendRawf("WHO %s", nick)
}
// Set different modes for a target (channel or nickname).
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.3
func (irc *Connection) Mode(target string, modestring ...string) {
if len(modestring) > 0 {
mode := strings.Join(modestring, " ")
irc.SendRawf("MODE %s %s", target, mode)
return
}
irc.SendRawf("MODE %s", target)
}
func (irc *Connection) ErrorChan() chan error {
return irc.Error
}
// Returns true if the connection is connected to an IRC server.
func (irc *Connection) Connected() bool {
return !irc.stopped
}
// A disconnect sends all buffered messages (if possible),
// stops all goroutines and then closes the socket.
func (irc *Connection) Disconnect() {
irc.Lock()
defer irc.Unlock()
if irc.end != nil {
close(irc.end)
}
irc.Wait()
irc.end = nil
if irc.pwrite != nil {
close(irc.pwrite)
}
if irc.socket != nil {
irc.socket.Close()
}
irc.ErrorChan() <- ErrDisconnected
}
// Reconnect to a server using the current connection.
func (irc *Connection) Reconnect() error {
irc.end = make(chan struct{})
return irc.Connect(irc.Server)
}
// Connect to a given server using the current connection configuration.
// This function also takes care of identification if a password is provided.
// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1
func (irc *Connection) Connect(server string) error {
irc.Server = server
// mark Server as stopped since there can be an error during connect
irc.stopped = true
// make sure everything is ready for connection
if len(irc.Server) == 0 {
return errors.New("empty 'server'")
}
if strings.Index(irc.Server, ":") == 0 {
return errors.New("hostname is missing")
}
if strings.Index(irc.Server, ":") == len(irc.Server)-1 {
return errors.New("port missing")
}
_, ports, err := net.SplitHostPort(irc.Server)
if err != nil {
return errors.New("wrong address string")
}
port, err := strconv.Atoi(ports)
if err != nil {
return errors.New("extracting port failed")
}
if !((port >= 0) && (port <= 65535)) {
return errors.New("port number outside valid range")
}
if irc.Log == nil {
return errors.New("'Log' points to nil")
}
if len(irc.nick) == 0 {
return errors.New("empty 'nick'")
}
if len(irc.user) == 0 {
return errors.New("empty 'user'")
}
dialer := proxy.FromEnvironmentUsing(&net.Dialer{Timeout: irc.Timeout})
irc.socket, err = dialer.Dial("tcp", irc.Server)
if err != nil {
return err
}
if irc.UseTLS {
irc.socket = tls.Client(irc.socket, irc.TLSConfig)
}
if irc.Encoding == nil {
irc.Encoding = encoding.Nop
}
irc.stopped = false
irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr())
irc.pwrite = make(chan string, 10)
irc.Error = make(chan error, 10)
irc.Add(3)
go irc.readLoop()
go irc.writeLoop()
go irc.pingLoop()
if len(irc.WebIRC) > 0 {
irc.pwrite <- fmt.Sprintf("WEBIRC %s\r\n", irc.WebIRC)
}
if len(irc.Password) > 0 {
irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password)
}
err = irc.negotiateCaps()
if err != nil {
return err
}
realname := irc.user
if irc.RealName != "" {
realname = irc.RealName
}
irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick)
irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, realname)
return nil
}
// Negotiate IRCv3 capabilities
func (irc *Connection) negotiateCaps() error {
irc.RequestCaps = nil
irc.AcknowledgedCaps = nil
var negotiationCallbacks []CallbackID
defer func() {
for _, callback := range negotiationCallbacks {
irc.RemoveCallback(callback.EventCode, callback.ID)
}
}()
saslResChan := make(chan *SASLResult)
if irc.UseSASL {
irc.RequestCaps = append(irc.RequestCaps, "sasl")
negotiationCallbacks = irc.setupSASLCallbacks(saslResChan)
}
if len(irc.RequestCaps) == 0 {
return nil
}
cap_chan := make(chan bool, len(irc.RequestCaps))
id := irc.AddCallback("CAP", func(e *Event) {
if len(e.Arguments) != 3 {
return
}
command := e.Arguments[1]
if command == "LS" {
missing_caps := len(irc.RequestCaps)
for _, cap_name := range strings.Split(e.Arguments[2], " ") {
for _, req_cap := range irc.RequestCaps {
if cap_name == req_cap {
irc.pwrite <- fmt.Sprintf("CAP REQ :%s\r\n", cap_name)
missing_caps--
}
}
}
for i := 0; i < missing_caps; i++ {
cap_chan <- true
}
} else if command == "ACK" || command == "NAK" {
for _, cap_name := range strings.Split(strings.TrimSpace(e.Arguments[2]), " ") {
if cap_name == "" {
continue
}
if command == "ACK" {
irc.AcknowledgedCaps = append(irc.AcknowledgedCaps, cap_name)
}
cap_chan <- true
}
}
})
negotiationCallbacks = append(negotiationCallbacks, CallbackID{"CAP", id})
irc.pwrite <- "CAP LS\r\n"
if irc.UseSASL {
select {
case res := <-saslResChan:
if res.Failed {
return res.Err
}
case <-time.After(CAP_TIMEOUT):
// Raise an error if we can't authenticate with SASL.
return errors.New("SASL setup timed out. Does the server support SASL?")
}
}
remaining_caps := len(irc.RequestCaps)
select {
case <-cap_chan:
remaining_caps--
case <-time.After(CAP_TIMEOUT):
// The server probably doesn't implement CAP LS, which is "normal".
return nil
}
// Wait for all capabilities to be ACKed or NAKed before ending negotiation
for remaining_caps > 0 {
<-cap_chan
remaining_caps--
}
irc.pwrite <- fmt.Sprintf("CAP END\r\n")
return nil
}
// Create a connection with the (publicly visible) nickname and username.
// The nickname is later used to address the user. Returns nil if nick
// or user are empty.
func IRC(nick, user string) *Connection {
// catch invalid values
if len(nick) == 0 {
return nil
}
if len(user) == 0 {
return nil
}
irc := &Connection{
nick: nick,
nickcurrent: nick,
user: user,
Log: log.New(os.Stdout, "", log.LstdFlags),
end: make(chan struct{}),
Version: VERSION,
KeepAlive: 4 * time.Minute,
Timeout: 1 * time.Minute,
PingFreq: 15 * time.Minute,
SASLMech: "PLAIN",
QuitMessage: "",
}
irc.setupCallbacks()
return irc
}

287
vendor/github.com/thoj/go-ircevent/irc_callback.go generated vendored Normal file
View File

@@ -0,0 +1,287 @@
package irc
import (
"context"
"reflect"
"runtime"
"strconv"
"strings"
"time"
)
// Tuple type for uniquely identifying callbacks
type CallbackID struct {
EventCode string
ID int
}
// Register a callback to a connection and event code. A callback is a function
// which takes only an Event pointer as parameter. Valid event codes are all
// IRC/CTCP commands and error/response codes. To register a callback for all
// events pass "*" as the event code. This function returns the ID of the
// registered callback for later management.
func (irc *Connection) AddCallback(eventcode string, callback func(*Event)) int {
eventcode = strings.ToUpper(eventcode)
irc.eventsMutex.Lock()
_, ok := irc.events[eventcode]
if !ok {
irc.events[eventcode] = make(map[int]func(*Event))
}
id := irc.idCounter
irc.idCounter++
irc.events[eventcode][id] = callback
irc.eventsMutex.Unlock()
return id
}
// Remove callback i (ID) from the given event code. This functions returns
// true upon success, false if any error occurs.
func (irc *Connection) RemoveCallback(eventcode string, i int) bool {
eventcode = strings.ToUpper(eventcode)
irc.eventsMutex.Lock()
event, ok := irc.events[eventcode]
if ok {
if _, ok := event[i]; ok {
delete(irc.events[eventcode], i)
irc.eventsMutex.Unlock()
return true
}
irc.Log.Printf("Event found, but no callback found at id %d\n", i)
irc.eventsMutex.Unlock()
return false
}
irc.eventsMutex.Unlock()
irc.Log.Println("Event not found")
return false
}
// Remove all callbacks from a given event code. It returns true
// if given event code is found and cleared.
func (irc *Connection) ClearCallback(eventcode string) bool {
eventcode = strings.ToUpper(eventcode)
irc.eventsMutex.Lock()
_, ok := irc.events[eventcode]
if ok {
irc.events[eventcode] = make(map[int]func(*Event))
irc.eventsMutex.Unlock()
return true
}
irc.eventsMutex.Unlock()
irc.Log.Println("Event not found")
return false
}
// Replace callback i (ID) associated with a given event code with a new callback function.
func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*Event)) {
eventcode = strings.ToUpper(eventcode)
irc.eventsMutex.Lock()
event, ok := irc.events[eventcode]
irc.eventsMutex.Unlock()
if ok {
if _, ok := event[i]; ok {
event[i] = callback
return
}
irc.Log.Printf("Event found, but no callback found at id %d\n", i)
}
irc.Log.Printf("Event not found. Use AddCallBack\n")
}
// Execute all callbacks associated with a given event.
func (irc *Connection) RunCallbacks(event *Event) {
msg := event.Message()
if event.Code == "PRIVMSG" && len(msg) > 2 && msg[0] == '\x01' {
event.Code = "CTCP" //Unknown CTCP
if i := strings.LastIndex(msg, "\x01"); i > 0 {
msg = msg[1:i]
} else {
irc.Log.Printf("Invalid CTCP Message: %s\n", strconv.Quote(msg))
return
}
if msg == "VERSION" {
event.Code = "CTCP_VERSION"
} else if msg == "TIME" {
event.Code = "CTCP_TIME"
} else if strings.HasPrefix(msg, "PING") {
event.Code = "CTCP_PING"
} else if msg == "USERINFO" {
event.Code = "CTCP_USERINFO"
} else if msg == "CLIENTINFO" {
event.Code = "CTCP_CLIENTINFO"
} else if strings.HasPrefix(msg, "ACTION") {
event.Code = "CTCP_ACTION"
if len(msg) > 6 {
msg = msg[7:]
} else {
msg = ""
}
}
event.Arguments[len(event.Arguments)-1] = msg
}
irc.eventsMutex.Lock()
callbacks := make(map[int]func(*Event))
eventCallbacks, ok := irc.events[event.Code]
id := 0
if ok {
for _, callback := range eventCallbacks {
callbacks[id] = callback
id++
}
}
allCallbacks, ok := irc.events["*"]
if ok {
for _, callback := range allCallbacks {
callbacks[id] = callback
id++
}
}
irc.eventsMutex.Unlock()
if irc.VerboseCallbackHandler {
irc.Log.Printf("%v (%v) >> %#v\n", event.Code, len(callbacks), event)
}
event.Ctx = context.Background()
if irc.CallbackTimeout != 0 {
event.Ctx, _ = context.WithTimeout(event.Ctx, irc.CallbackTimeout)
}
done := make(chan int)
for id, callback := range callbacks {
go func(id int, done chan<- int, cb func(*Event), event *Event) {
start := time.Now()
cb(event)
select {
case done <- id:
case <-event.Ctx.Done(): // If we timed out, report how long until we eventually finished
irc.Log.Printf("Canceled callback %s finished in %s >> %#v\n",
getFunctionName(cb),
time.Since(start),
event,
)
}
}(id, done, callback, event)
}
for len(callbacks) > 0 {
select {
case jobID := <-done:
delete(callbacks, jobID)
case <-event.Ctx.Done(): // context timed out!
timedOutCallbacks := []string{}
for _, cb := range callbacks { // Everything left here did not finish
timedOutCallbacks = append(timedOutCallbacks, getFunctionName(cb))
}
irc.Log.Printf("Timeout while waiting for %d callback(s) to finish (%s)\n",
len(callbacks),
strings.Join(timedOutCallbacks, ", "),
)
return
}
}
}
func getFunctionName(f func(*Event)) string {
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}
// Set up some initial callbacks to handle the IRC/CTCP protocol.
func (irc *Connection) setupCallbacks() {
irc.events = make(map[string]map[int]func(*Event))
//Handle ping events
irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) })
//Version handler
irc.AddCallback("CTCP_VERSION", func(e *Event) {
irc.SendRawf("NOTICE %s :\x01VERSION %s\x01", e.Nick, irc.Version)
})
irc.AddCallback("CTCP_USERINFO", func(e *Event) {
irc.SendRawf("NOTICE %s :\x01USERINFO %s\x01", e.Nick, irc.user)
})
irc.AddCallback("CTCP_CLIENTINFO", func(e *Event) {
irc.SendRawf("NOTICE %s :\x01CLIENTINFO PING VERSION TIME USERINFO CLIENTINFO\x01", e.Nick)
})
irc.AddCallback("CTCP_TIME", func(e *Event) {
ltime := time.Now()
irc.SendRawf("NOTICE %s :\x01TIME %s\x01", e.Nick, ltime.String())
})
irc.AddCallback("CTCP_PING", func(e *Event) { irc.SendRawf("NOTICE %s :\x01%s\x01", e.Nick, e.Message()) })
// 437: ERR_UNAVAILRESOURCE "<nick/channel> :Nick/channel is temporarily unavailable"
// Add a _ to current nick. If irc.nickcurrent is empty this cannot
// work. It has to be set somewhere first in case the nick is already
// taken or unavailable from the beginning.
irc.AddCallback("437", func(e *Event) {
// If irc.nickcurrent hasn't been set yet, set to irc.nick
if irc.nickcurrent == "" {
irc.nickcurrent = irc.nick
}
if len(irc.nickcurrent) > 8 {
irc.nickcurrent = "_" + irc.nickcurrent
} else {
irc.nickcurrent = irc.nickcurrent + "_"
}
irc.SendRawf("NICK %s", irc.nickcurrent)
})
// 433: ERR_NICKNAMEINUSE "<nick> :Nickname is already in use"
// Add a _ to current nick.
irc.AddCallback("433", func(e *Event) {
// If irc.nickcurrent hasn't been set yet, set to irc.nick
if irc.nickcurrent == "" {
irc.nickcurrent = irc.nick
}
if len(irc.nickcurrent) > 8 {
irc.nickcurrent = "_" + irc.nickcurrent
} else {
irc.nickcurrent = irc.nickcurrent + "_"
}
irc.SendRawf("NICK %s", irc.nickcurrent)
})
irc.AddCallback("PONG", func(e *Event) {
ns, _ := strconv.ParseInt(e.Message(), 10, 64)
delta := time.Duration(time.Now().UnixNano() - ns)
if irc.Debug {
irc.Log.Printf("Lag: %.3f s\n", delta.Seconds())
}
})
// NICK Define a nickname.
// Set irc.nickcurrent to the new nick actually used in this connection.
irc.AddCallback("NICK", func(e *Event) {
if e.Nick == irc.nick {
irc.nickcurrent = e.Message()
}
})
// 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>"
// Set irc.nickcurrent to the actually used nick in this connection.
irc.AddCallback("001", func(e *Event) {
irc.Lock()
irc.nickcurrent = e.Arguments[0]
irc.Unlock()
})
}

80
vendor/github.com/thoj/go-ircevent/irc_sasl.go generated vendored Normal file
View File

@@ -0,0 +1,80 @@
package irc
import (
"encoding/base64"
"errors"
"fmt"
"strings"
)
type SASLResult struct {
Failed bool
Err error
}
// Check if a space-separated list of arguments contains a value.
func listContains(list string, value string) bool {
for _, arg_name := range strings.Split(strings.TrimSpace(list), " ") {
if arg_name == value {
return true
}
}
return false
}
func (irc *Connection) setupSASLCallbacks(result chan<- *SASLResult) (callbacks []CallbackID) {
id := irc.AddCallback("CAP", func(e *Event) {
if len(e.Arguments) == 3 {
if e.Arguments[1] == "LS" {
if !listContains(e.Arguments[2], "sasl") {
result <- &SASLResult{true, errors.New("no SASL capability " + e.Arguments[2])}
}
}
if e.Arguments[1] == "ACK" && listContains(e.Arguments[2], "sasl") {
if irc.SASLMech != "PLAIN" && irc.SASLMech != "EXTERNAL" {
result <- &SASLResult{true, errors.New("only PLAIN and EXTERNAL supported")}
}
irc.SendRaw("AUTHENTICATE " + irc.SASLMech)
}
}
})
callbacks = append(callbacks, CallbackID{"CAP", id})
id = irc.AddCallback("AUTHENTICATE", func(e *Event) {
if irc.SASLMech == "EXTERNAL" {
irc.SendRaw("AUTHENTICATE +")
return
}
str := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s\x00%s\x00%s", irc.SASLLogin, irc.SASLLogin, irc.SASLPassword)))
irc.SendRaw("AUTHENTICATE " + str)
})
callbacks = append(callbacks, CallbackID{"AUTHENTICATE", id})
id = irc.AddCallback("901", func(e *Event) {
irc.SendRaw("CAP END")
irc.SendRaw("QUIT")
result <- &SASLResult{true, errors.New(e.Arguments[1])}
})
callbacks = append(callbacks, CallbackID{"901", id})
id = irc.AddCallback("902", func(e *Event) {
irc.SendRaw("CAP END")
irc.SendRaw("QUIT")
result <- &SASLResult{true, errors.New(e.Arguments[1])}
})
callbacks = append(callbacks, CallbackID{"902", id})
id = irc.AddCallback("903", func(e *Event) {
result <- &SASLResult{false, nil}
})
callbacks = append(callbacks, CallbackID{"903", id})
id = irc.AddCallback("904", func(e *Event) {
irc.SendRaw("CAP END")
irc.SendRaw("QUIT")
result <- &SASLResult{true, errors.New(e.Arguments[1])}
})
callbacks = append(callbacks, CallbackID{"904", id})
return
}

105
vendor/github.com/thoj/go-ircevent/irc_struct.go generated vendored Normal file
View File

@@ -0,0 +1,105 @@
// Copyright 2009 Thomas Jager <mail@jager.no> All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package irc
import (
"context"
"crypto/tls"
"log"
"net"
"regexp"
"sync"
"time"
"golang.org/x/text/encoding"
)
type Connection struct {
sync.Mutex
sync.WaitGroup
Debug bool
Error chan error
WebIRC string
Password string
UseTLS bool
UseSASL bool
RequestCaps []string
AcknowledgedCaps []string
SASLLogin string
SASLPassword string
SASLMech string
TLSConfig *tls.Config
Version string
Timeout time.Duration
CallbackTimeout time.Duration
PingFreq time.Duration
KeepAlive time.Duration
Server string
Encoding encoding.Encoding
RealName string // The real name we want to display.
// If zero-value defaults to the user.
socket net.Conn
pwrite chan string
end chan struct{}
nick string //The nickname we want.
nickcurrent string //The nickname we currently have.
user string
registered bool
events map[string]map[int]func(*Event)
eventsMutex sync.Mutex
QuitMessage string
lastMessage time.Time
lastMessageMutex sync.Mutex
VerboseCallbackHandler bool
Log *log.Logger
stopped bool
quit bool //User called Quit, do not reconnect.
idCounter int // assign unique IDs to callbacks
}
// A struct to represent an event.
type Event struct {
Code string
Raw string
Nick string //<nick>
Host string //<nick>!<usr>@<host>
Source string //<host>
User string //<usr>
Arguments []string
Tags map[string]string
Connection *Connection
Ctx context.Context
}
// Retrieve the last message from Event arguments.
// This function leaves the arguments untouched and
// returns an empty string if there are none.
func (e *Event) Message() string {
if len(e.Arguments) == 0 {
return ""
}
return e.Arguments[len(e.Arguments)-1]
}
// https://stackoverflow.com/a/10567935/6754440
// Regex of IRC formatting.
var ircFormat = regexp.MustCompile(`[\x02\x1F\x0F\x16\x1D\x1E]|\x03(\d\d?(,\d\d?)?)?`)
// Retrieve the last message from Event arguments, but without IRC formatting (color.
// This function leaves the arguments untouched and
// returns an empty string if there are none.
func (e *Event) MessageWithoutFormat() string {
if len(e.Arguments) == 0 {
return ""
}
return ircFormat.ReplaceAllString(e.Arguments[len(e.Arguments)-1], "")
}

14
vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
// +build gofuzz
package irc
func Fuzz(data []byte) int {
b := bytes.NewBuffer(data)
event, err := parseToEvent(b.String())
if err == nil {
irc := IRC("go-eventirc", "go-eventirc")
irc.RunCallbacks(event)
return 1
}
return 0
}