remove old irc bindings
This commit is contained in:
parent
616e71825c
commit
d12499bcdd
|
@ -1,27 +0,0 @@
|
|||
// 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.
|
|
@ -1,65 +0,0 @@
|
|||
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 test/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
|
||||
});
|
||||
|
||||
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")
|
||||
ircobj.Privmsgf(<nickname | #channel>, "<formatstring>", ...)
|
||||
ircobj.Notice("<nickname | #channel>", "msg")
|
||||
ircobj.Noticef("<nickname | #channel>", "<formatstring>", ...)
|
|
@ -1,469 +0,0 @@
|
|||
// 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"
|
||||
)
|
||||
|
||||
const (
|
||||
VERSION = "go-ircevent v2.1"
|
||||
)
|
||||
|
||||
var ErrDisconnected = errors.New("Disconnect Called")
|
||||
|
||||
// Read data from a connection. To be used as a goroutine.
|
||||
func (irc *Connection) readLoop() {
|
||||
defer irc.Done()
|
||||
br := bufio.NewReaderSize(irc.socket, 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
|
||||
break
|
||||
}
|
||||
|
||||
if irc.Debug {
|
||||
irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg))
|
||||
}
|
||||
|
||||
irc.lastMessage = time.Now()
|
||||
event, err := parseToEvent(msg)
|
||||
event.Connection = irc
|
||||
if err == nil {
|
||||
/* XXX: len(args) == 0: args should be empty */
|
||||
irc.RunCallbacks(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//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] == ':' {
|
||||
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()
|
||||
errChan := irc.ErrorChan()
|
||||
for {
|
||||
select {
|
||||
case <-irc.end:
|
||||
return
|
||||
default:
|
||||
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 := irc.socket.Write([]byte(b))
|
||||
|
||||
// Past blocking write, bin timeout
|
||||
var zero time.Time
|
||||
irc.socket.SetWriteDeadline(zero)
|
||||
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
if time.Since(irc.lastMessage) >= irc.KeepAlive {
|
||||
irc.SendRawf("PING %d", time.Now().UnixNano())
|
||||
}
|
||||
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.
|
||||
if irc.nick != irc.nickcurrent {
|
||||
irc.nickcurrent = irc.nick
|
||||
irc.SendRawf("NICK %s", irc.nick)
|
||||
}
|
||||
case <-irc.end:
|
||||
ticker.Stop()
|
||||
ticker2.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main loop to control the connection.
|
||||
func (irc *Connection) Loop() {
|
||||
errChan := irc.ErrorChan()
|
||||
for !irc.stopped {
|
||||
err := <-errChan
|
||||
if irc.stopped {
|
||||
break
|
||||
}
|
||||
irc.Log.Printf("Error, disconnected: %s\n", err)
|
||||
for !irc.stopped {
|
||||
if err = irc.Reconnect(); err != nil {
|
||||
irc.Log.Printf("Error while reconnecting: %s\n", err)
|
||||
time.Sleep(1 * time.Second)
|
||||
} else {
|
||||
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() {
|
||||
irc.SendRaw("QUIT")
|
||||
irc.stopped = true
|
||||
}
|
||||
|
||||
// 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() {
|
||||
for event := range irc.events {
|
||||
irc.ClearCallback(event)
|
||||
}
|
||||
if irc.end != nil {
|
||||
close(irc.end)
|
||||
}
|
||||
|
||||
irc.end = nil
|
||||
|
||||
if irc.pwrite != nil {
|
||||
close(irc.pwrite)
|
||||
}
|
||||
|
||||
irc.Wait()
|
||||
if irc.socket != nil {
|
||||
irc.socket.Close()
|
||||
}
|
||||
irc.socket = nil
|
||||
irc.ErrorChan() <- ErrDisconnected
|
||||
}
|
||||
|
||||
// Reconnect to a server using the current connection.
|
||||
func (irc *Connection) Reconnect() error {
|
||||
if irc.end != nil {
|
||||
close(irc.end)
|
||||
}
|
||||
|
||||
irc.end = nil
|
||||
|
||||
irc.Wait() //make sure that wait group is cleared ensuring that all spawned goroutines have completed
|
||||
|
||||
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.Count(irc.Server, ":") != 1 {
|
||||
return errors.New("wrong number of ':' in address")
|
||||
}
|
||||
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")
|
||||
}
|
||||
// check for valid range
|
||||
ports := strings.Split(irc.Server, ":")[1]
|
||||
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'")
|
||||
}
|
||||
|
||||
if irc.UseTLS {
|
||||
dialer := &net.Dialer{Timeout: irc.Timeout}
|
||||
irc.socket, err = tls.DialWithDialer(dialer, "tcp", irc.Server, irc.TLSConfig)
|
||||
} else {
|
||||
irc.socket, err = net.DialTimeout("tcp", irc.Server, irc.Timeout)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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, 2)
|
||||
irc.Add(3)
|
||||
go irc.readLoop()
|
||||
go irc.writeLoop()
|
||||
go irc.pingLoop()
|
||||
if len(irc.Password) > 0 {
|
||||
irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password)
|
||||
}
|
||||
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, irc.user)
|
||||
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,
|
||||
}
|
||||
irc.setupCallbacks()
|
||||
return irc
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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. This function returns the ID of
|
||||
// the registered callback for later management.
|
||||
func (irc *Connection) AddCallback(eventcode string, callback func(*Event)) string {
|
||||
eventcode = strings.ToUpper(eventcode)
|
||||
|
||||
if _, ok := irc.events[eventcode]; !ok {
|
||||
irc.events[eventcode] = make(map[string]func(*Event))
|
||||
}
|
||||
h := sha1.New()
|
||||
rawId := []byte(fmt.Sprintf("%v%d", reflect.ValueOf(callback).Pointer(), rand.Int63()))
|
||||
h.Write(rawId)
|
||||
id := fmt.Sprintf("%x", h.Sum(nil))
|
||||
irc.events[eventcode][id] = callback
|
||||
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 string) bool {
|
||||
eventcode = strings.ToUpper(eventcode)
|
||||
|
||||
if event, ok := irc.events[eventcode]; ok {
|
||||
if _, ok := event[i]; ok {
|
||||
delete(irc.events[eventcode], i)
|
||||
return true
|
||||
}
|
||||
irc.Log.Printf("Event found, but no callback found at id %s\n", i)
|
||||
return false
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if _, ok := irc.events[eventcode]; ok {
|
||||
irc.events[eventcode] = make(map[string]func(*Event))
|
||||
return true
|
||||
}
|
||||
|
||||
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 string, callback func(*Event)) {
|
||||
eventcode = strings.ToUpper(eventcode)
|
||||
|
||||
if event, ok := irc.events[eventcode]; ok {
|
||||
if _, ok := event[i]; ok {
|
||||
event[i] = callback
|
||||
return
|
||||
}
|
||||
irc.Log.Printf("Event found, but no callback found at id %s\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
|
||||
}
|
||||
|
||||
if callbacks, ok := irc.events[event.Code]; ok {
|
||||
if irc.VerboseCallbackHandler {
|
||||
irc.Log.Printf("%v (%v) >> %#v\n", event.Code, len(callbacks), event)
|
||||
}
|
||||
|
||||
for _, callback := range callbacks {
|
||||
go callback(event)
|
||||
}
|
||||
} else if irc.VerboseCallbackHandler {
|
||||
irc.Log.Printf("%v (0) >> %#v\n", event.Code, event)
|
||||
}
|
||||
|
||||
if callbacks, ok := irc.events["*"]; ok {
|
||||
if irc.VerboseCallbackHandler {
|
||||
irc.Log.Printf("Wildcard %v (%v) >> %#v\n", event.Code, len(callbacks), event)
|
||||
}
|
||||
|
||||
for _, callback := range callbacks {
|
||||
go callback(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up some initial callbacks to handle the IRC/CTCP protocol.
|
||||
func (irc *Connection) setupCallbacks() {
|
||||
irc.events = make(map[string]map[string]func(*Event))
|
||||
|
||||
//Handle error events
|
||||
irc.AddCallback("ERROR", func(e *Event) { irc.Disconnect() })
|
||||
|
||||
//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: %vs\n", delta)
|
||||
}
|
||||
})
|
||||
|
||||
// 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.nickcurrent = e.Arguments[0]
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
// 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 (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Connection struct {
|
||||
sync.WaitGroup
|
||||
Debug bool
|
||||
Error chan error
|
||||
Password string
|
||||
UseTLS bool
|
||||
TLSConfig *tls.Config
|
||||
Version string
|
||||
Timeout time.Duration
|
||||
PingFreq time.Duration
|
||||
KeepAlive time.Duration
|
||||
Server string
|
||||
|
||||
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[string]func(*Event)
|
||||
|
||||
lastMessage time.Time
|
||||
|
||||
VerboseCallbackHandler bool
|
||||
Log *log.Logger
|
||||
|
||||
stopped bool
|
||||
}
|
||||
|
||||
// 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
|
||||
Connection *Connection
|
||||
}
|
||||
|
||||
// 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]
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConnectionEmtpyServer(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
err := irccon.Connect("")
|
||||
if err == nil {
|
||||
t.Fatal("emtpy server string not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionDoubleColon(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
err := irccon.Connect("::")
|
||||
if err == nil {
|
||||
t.Fatal("wrong number of ':' not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionMissingHost(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
err := irccon.Connect(":6667")
|
||||
if err == nil {
|
||||
t.Fatal("missing host not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionMissingPort(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
err := irccon.Connect("chat.freenode.net:")
|
||||
if err == nil {
|
||||
t.Fatal("missing port not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionNegativePort(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
err := irccon.Connect("chat.freenode.net:-1")
|
||||
if err == nil {
|
||||
t.Fatal("negative port number not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionTooLargePort(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
err := irccon.Connect("chat.freenode.net:65536")
|
||||
if err == nil {
|
||||
t.Fatal("too large port number not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionMissingLog(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
irccon.Log = nil
|
||||
err := irccon.Connect("chat.freenode.net:6667")
|
||||
if err == nil {
|
||||
t.Fatal("missing 'Log' not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionEmptyUser(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
// user may be changed after creation
|
||||
irccon.user = ""
|
||||
err := irccon.Connect("chat.freenode.net:6667")
|
||||
if err == nil {
|
||||
t.Fatal("empty 'user' not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionEmptyNick(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
// nick may be changed after creation
|
||||
irccon.nick = ""
|
||||
err := irccon.Connect("chat.freenode.net:6667")
|
||||
if err == nil {
|
||||
t.Fatal("empty 'nick' not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveCallback(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
irccon.VerboseCallbackHandler = true
|
||||
irccon.Debug = true
|
||||
|
||||
done := make(chan int, 10)
|
||||
|
||||
irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
|
||||
id := irccon.AddCallback("TEST", func(e *Event) { done <- 2 })
|
||||
irccon.AddCallback("TEST", func(e *Event) { done <- 3 })
|
||||
|
||||
// Should remove callback at index 1
|
||||
irccon.RemoveCallback("TEST", id)
|
||||
|
||||
irccon.RunCallbacks(&Event{
|
||||
Code: "TEST",
|
||||
})
|
||||
|
||||
var results []int
|
||||
|
||||
results = append(results, <-done)
|
||||
results = append(results, <-done)
|
||||
|
||||
if len(results) != 2 || results[0] == 2 || results[1] == 2 {
|
||||
t.Error("Callback 2 not removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardCallback(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
irccon.VerboseCallbackHandler = true
|
||||
irccon.Debug = true
|
||||
|
||||
done := make(chan int, 10)
|
||||
|
||||
irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
|
||||
irccon.AddCallback("*", func(e *Event) { done <- 2 })
|
||||
|
||||
irccon.RunCallbacks(&Event{
|
||||
Code: "TEST",
|
||||
})
|
||||
|
||||
var results []int
|
||||
|
||||
results = append(results, <-done)
|
||||
results = append(results, <-done)
|
||||
|
||||
if len(results) != 2 || !(results[0] == 1 && results[1] == 2) {
|
||||
t.Error("Wildcard callback not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearCallback(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
irccon.VerboseCallbackHandler = true
|
||||
irccon.Debug = true
|
||||
|
||||
done := make(chan int, 10)
|
||||
|
||||
irccon.AddCallback("TEST", func(e *Event) { done <- 0 })
|
||||
irccon.AddCallback("TEST", func(e *Event) { done <- 1 })
|
||||
irccon.ClearCallback("TEST")
|
||||
irccon.AddCallback("TEST", func(e *Event) { done <- 2 })
|
||||
irccon.AddCallback("TEST", func(e *Event) { done <- 3 })
|
||||
|
||||
irccon.RunCallbacks(&Event{
|
||||
Code: "TEST",
|
||||
})
|
||||
|
||||
var results []int
|
||||
|
||||
results = append(results, <-done)
|
||||
results = append(results, <-done)
|
||||
|
||||
if len(results) != 2 || !(results[0] == 2 && results[1] == 3) {
|
||||
t.Error("Callbacks not cleared")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIRCemptyNick(t *testing.T) {
|
||||
irccon := IRC("", "go-eventirc")
|
||||
irccon = nil
|
||||
if irccon != nil {
|
||||
t.Error("empty nick didn't result in error")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIRCemptyUser(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "")
|
||||
if irccon != nil {
|
||||
t.Error("empty user didn't result in error")
|
||||
}
|
||||
}
|
||||
func TestConnection(t *testing.T) {
|
||||
irccon1 := IRC("go-eventirc1", "go-eventirc1")
|
||||
irccon1.VerboseCallbackHandler = true
|
||||
irccon1.Debug = true
|
||||
irccon2 := IRC("go-eventirc2", "go-eventirc2")
|
||||
irccon2.VerboseCallbackHandler = true
|
||||
irccon2.Debug = true
|
||||
err := irccon1.Connect("irc.freenode.net:6667")
|
||||
if err != nil {
|
||||
t.Log(err.Error())
|
||||
t.Fatal("Can't connect to freenode.")
|
||||
}
|
||||
err = irccon2.Connect("irc.freenode.net:6667")
|
||||
if err != nil {
|
||||
t.Log(err.Error())
|
||||
t.Fatal("Can't connect to freenode.")
|
||||
}
|
||||
irccon1.AddCallback("001", func(e *Event) { irccon1.Join("#go-eventirc") })
|
||||
irccon2.AddCallback("001", func(e *Event) { irccon2.Join("#go-eventirc") })
|
||||
con2ok := false
|
||||
irccon1.AddCallback("366", func(e *Event) {
|
||||
t := time.NewTicker(1 * time.Second)
|
||||
i := 10
|
||||
for {
|
||||
<-t.C
|
||||
irccon1.Privmsgf("#go-eventirc", "Test Message%d\n", i)
|
||||
if con2ok {
|
||||
i -= 1
|
||||
}
|
||||
if i == 0 {
|
||||
t.Stop()
|
||||
irccon1.Quit()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
irccon2.AddCallback("366", func(e *Event) {
|
||||
irccon2.Privmsg("#go-eventirc", "Test Message\n")
|
||||
con2ok = true
|
||||
irccon2.Nick("go-eventnewnick")
|
||||
})
|
||||
|
||||
irccon2.AddCallback("PRIVMSG", func(e *Event) {
|
||||
t.Log(e.Message())
|
||||
if e.Message() == "Test Message5" {
|
||||
irccon2.Quit()
|
||||
}
|
||||
})
|
||||
|
||||
irccon2.AddCallback("NICK", func(e *Event) {
|
||||
if irccon2.nickcurrent == "go-eventnewnick" {
|
||||
t.Fatal("Nick change did not work!")
|
||||
}
|
||||
})
|
||||
go irccon2.Loop()
|
||||
irccon1.Loop()
|
||||
}
|
||||
|
||||
func TestConnectionSSL(t *testing.T) {
|
||||
irccon := IRC("go-eventirc", "go-eventirc")
|
||||
irccon.VerboseCallbackHandler = true
|
||||
irccon.Debug = true
|
||||
irccon.UseTLS = true
|
||||
irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
err := irccon.Connect("irc.freenode.net:7000")
|
||||
if err != nil {
|
||||
t.Log(err.Error())
|
||||
t.Fatal("Can't connect to freenode.")
|
||||
}
|
||||
irccon.AddCallback("001", func(e *Event) { irccon.Join("#go-eventirc") })
|
||||
|
||||
irccon.AddCallback("366", func(e *Event) {
|
||||
irccon.Privmsg("#go-eventirc", "Test Message\n")
|
||||
time.Sleep(2 * time.Second)
|
||||
irccon.Quit()
|
||||
})
|
||||
|
||||
irccon.Loop()
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// +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
|
||||
}
|
Loading…
Reference in New Issue