banchogo

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2023 License: GPL-3.0 Imports: 17 Imported by: 0

README

banchogo

This module is currently WIP

Inspired by banchojs

TODO

  • Multiplayer support
  • Better Reconnect mechanism
  • Tests
  • Examples
  • Documentation
  • readme

Bot Account

// TODO:

Please DON'T create a separate account for your bot. This will be considered multi-accounting and may restrict both of your accounts

Like banchojs, this package uses rate limits for normal user accounts by default

Getting Started

Install module with go get github.com/robloxxa/banchogo

package main

import (
	"fmt"
	"github.com/robloxxa/banchogo"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	client := banchogo.NewBanchoClient(banchogo.ClientOptions{
		Username: "robloxxa",
		Password: "irc_password",
	})

	client.OnceConnect(func() {
		fmt.Println("Connected!")
	})

	client.OnMessage(func(msg *banchogo.PrivateMessage) {
		fmt.Println(msg.User.Name() + ": " + msg.Content())
	})

	err := client.Connect()
	if err != nil {
		panic(err)
	}
    
	// Banchogo launch their own goroutines and doesn't block main one.
	// We can block main goroutine by waiting for a CTRL-C input
	// OR
	// We can use <-client.Done channel which will be closed on Disconnect
	fmt.Println("Bot is now running. Press CTRL-C to exit.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
	<-sc
    // Gracefully shutting down, stopping all goroutines and properly disconnect from IRC
	client.Disconnect()
}

Event Problem

Since this module aimed to be easy and similar to banchojs, a banchogo Event system is synchronous like Node.js.

This can be problematic if you use methods like user.Where(), user.Whois(), user.Stats(), etc., which block reading goroutine until it hits timeout while you wait response from channel.

client.OnMessage(func(m *PrivateMessage) {
	data := <-m.User.Where()
	// ...it will hang here until timeout
	// this is because response is ahead and can't be obtained in same goroutine where event emitted
}) 

But it is nothing to worry about, just run these methods in separate goroutine

client.OnMessage(func(m *PrivateMessage) {
	go func() {
        data := <-m.User.Where()
		fmt.Println("data: ", data)
	}()
}) 

Basically, if you see that method returns a chan, you should consider calling it in separate from emitted event goroutine

Compatability

This package uses go generics with was introduced in go 1.19.

All versions below 1.19 are incompatible.

Special thanks

ThePooN and his community - For creating banchojs, which inspired me to make this package, and for saving my time understanding how bancho works

TheHowl - For go osu api wrapper go-osuapi

Documentation

Index

Constants

View Source
const (
	WHOIS_USER_ID = iota
	WHOIS_CHANNELS
	WHOIS_END
)
View Source
const (
	DEFAULT_BUFFER_SIZE = 1024
)

Variables

View Source
var (
	ErrChannelAlreadyJoined = errors.New("already joined")
	ErrChannelNotJoined     = errors.New("you are not joined to this channel")
)
View Source
var (
	ErrMissingCredentials = errors.New("username or password field is empty string, please provide credentials")
	ErrBadAuthentication  = errors.New("bancho authentication failed")

	ErrMessageTimeout    = errors.New("Message timeout")
	ErrConnectionClosed  = errors.New("connection closed")
	ErrConnectionTimeout = errors.New("connection timed out")

	ErrUserOffline  = errors.New("user offline")
	ErrUserNotFound = errors.New("user not found")

	ErrChannelNotFound = errors.New("no such channel")
)
View Source
var IrcHandlers = map[string]func(*Client, []string){
	"001":     handleWelcomeCommand,
	"311":     handleWhoisUserCommand,
	"319":     handleWhoisChannelsCommand,
	"318":     handleWhoisEndCommand,
	"332":     handleChannelTopicCommand,
	"353":     handleNamesCommand,
	"403":     handleChannelNotFoundCommand,
	"464":     handleBadAuthCommand,
	"PRIVMSG": handlePrivmsgCommand,
	"MODE":    handleModeCommand,
	"JOIN":    handleJoinCommand,
	"PART":    handlePartCommand,
	"QUIT":    handleQuitCommand,
}

Functions

func SendBanchoBotCommand added in v1.0.0

func SendBanchoBotCommand[R interface{}](ctx context.Context, client *Client, command string, f func(*PrivateMessage) (*R, error)) (r *R, err error)

func SendBanchoBotWhere added in v1.0.0

func SendBanchoBotWhere(client *Client, u *User) (string, error)

func TruncateString

func TruncateString(str string, length int) string

Types

type BanchoBotStats added in v1.0.0

type BanchoBotStats struct {
	UserId      int
	Username    string
	RankedScore int64
	Rank        int
	Level       int
	PlayCount   int
	Accuracy    float64
	Status      string
	Online      bool
}

func SendBanchoBotStats added in v1.0.0

func SendBanchoBotStats(client *Client, u *User) (*BanchoBotStats, error)

SendBanchoBotStats sends !stats command to banchobot and returns SendBanchoBotStats FIXME: Make it concurrent safe

type BaseMessage added in v1.0.0

type BaseMessage struct {
	Date    time.Time
	Sender  Recipient
	User    *User
	Self    bool
	Message string
	// contains filtered or unexported fields
}

func (*BaseMessage) Action added in v1.0.0

func (b *BaseMessage) Action() string

func (*BaseMessage) Author added in v1.0.0

func (b *BaseMessage) Author() *User

func (*BaseMessage) Content added in v1.0.0

func (b *BaseMessage) Content() string

func (*BaseMessage) From added in v1.0.0

func (b *BaseMessage) From() Recipient

func (*BaseMessage) Reply added in v1.0.0

func (b *BaseMessage) Reply(content string) error

func (*BaseMessage) ReplyAction added in v1.0.0

func (b *BaseMessage) ReplyAction(content string) error

func (*BaseMessage) String added in v1.0.0

func (b *BaseMessage) String() string

type Channel

type Channel struct {
	// contains filtered or unexported fields
}

func SendBanchoBotMpMake added in v1.0.0

func SendBanchoBotMpMake(c *Client, name string, private bool) (*Channel, error)

SendBanchoBotMpMake sends !mp make *name* to BanchoBot Join and return a #mp_* channel on success Returns error if banchoBot didn't respond after 20 seconds or couldn't create mp lobby

func (*Channel) GetMember added in v1.0.0

func (c *Channel) GetMember(username string) (*ChannelMember, bool)

func (*Channel) IsClient added in v1.0.0

func (c *Channel) IsClient() bool

func (*Channel) Join

func (c *Channel) Join() error

func (*Channel) Joined

func (c *Channel) Joined() bool

func (*Channel) Name

func (c *Channel) Name() string

func (*Channel) Part added in v1.0.0

func (c *Channel) Part() error

func (*Channel) Send added in v1.0.0

func (c *Channel) Send(message string) error

func (*Channel) SendAction

func (c *Channel) SendAction(message string) error

func (*Channel) Type

func (c *Channel) Type() RecipientType

type ChannelMember

type ChannelMember struct {
	Channel *Channel
	User    *User
	Mode    ChannelMemberMode
}

type ChannelMemberMode

type ChannelMemberMode string
const (
	None         ChannelMemberMode = ""
	IRCUser      ChannelMemberMode = "v"
	IRCModerator ChannelMemberMode = "o"
)

func (ChannelMemberMode) ToSymbol

func (c ChannelMemberMode) ToSymbol() string

type ChannelMessage

type ChannelMessage struct {
	*BaseMessage

	Channel *Channel
}

type Client

type Client struct {
	Username   string
	Password   string
	Host       string
	Port       string
	BotAccount bool

	//CacheUsers   bool // TODO:
	UpdateBuffer int

	Users    *xsync.MapOf[string, *User]
	Channels *xsync.MapOf[string, *Channel]

	RateLimiter ratelimit.Limiter
	// contains filtered or unexported fields
}

func NewClient added in v1.0.0

func NewClient(opt ClientOptions) *Client

func (*Client) Connect

func (c *Client) Connect() (err error)

func (*Client) GetChannel

func (c *Client) GetChannel(channelName string) (channel *Channel, err error)

func (*Client) GetSelf

func (c *Client) GetSelf() *User

func (*Client) GetUpdateChan added in v1.0.0

func (c *Client) GetUpdateChan(listenTypes []UpdateType, bufferSize ...int) chan *Update

func (*Client) GetUser

func (c *Client) GetUser(username string) (user *User)

func (*Client) IsConnected

func (c *Client) IsConnected() bool

func (*Client) IsConnecting

func (c *Client) IsConnecting() bool

func (*Client) IsDisconnected

func (c *Client) IsDisconnected() bool

func (*Client) IsReconnecting

func (c *Client) IsReconnecting() bool

func (*Client) JoinChannel added in v1.0.0

func (c *Client) JoinChannel(channelName string) (channel *Channel, err error)

func (*Client) Quit added in v1.0.0

func (c *Client) Quit() <-chan struct{}

TODO: rename

func (*Client) SendRaw added in v1.0.0

func (c *Client) SendRaw(format string, a ...any) error

func (*Client) Shutdown added in v1.0.0

func (c *Client) Shutdown()

type ClientOptions

type ClientOptions struct {
	Username string
	Password string
	Host     string
	Port     string

	BotAccount bool

	ApiKey string

	RateLimiter ratelimit.Limiter
}

type ConnectState

type ConnectState uint32
const (
	Disconnected ConnectState = iota
	Reconnecting
	Connecting
	Connected
)

func (ConnectState) String added in v1.0.0

func (s ConnectState) String() string

type Message

type Message interface {
	From() Recipient
	Author() *User
	Content() string
	Reply(string) error
	ReplyAction(string) error
	Action() string
	String() string
}

type OutgoingMessage

type OutgoingMessage struct {
	Recipient Recipient
	Content   string
	C         chan error
	// contains filtered or unexported fields
}

func (*OutgoingMessage) Send

func (o *OutgoingMessage) Send() error

type PrivateMessage

type PrivateMessage struct {
	*BaseMessage

	Recipient *User
}

type Recipient added in v1.0.0

type Recipient interface {
	Name() string
	Send(string) error
	SendAction(string) error
	Type() RecipientType
	IsClient() bool
}

type RecipientType added in v1.0.0

type RecipientType uint8
const (
	USER RecipientType = iota
	CHANNEL
)

type Update added in v1.0.0

type Update struct {
	Type UpdateType

	Error error
	Raw   string

	ConnectState *ConnectState

	RejectedMessage Message
	Message         Message
	PrivateMessage  *PrivateMessage
	ChannelMessage  *ChannelMessage

	Whois *WhoisUpdate

	UserNotFound    string
	ChannelNotFound string

	Join *ChannelMember
	Part *ChannelMember
	Quit *User
}

type UpdateType added in v1.0.0

type UpdateType uint8
const (
	ERROR UpdateType = iota

	RAW
	CONNECT_STATE
	REJECTED_MESSAGE

	MESSAGE
	PRIVATE_MESSAGE
	CHANNEL_MESSAGE
	WHOIS

	USER_NOT_FOUND
	CHANNEL_NOT_FOUND

	JOIN
	PART
	QUIT
)

type User

type User struct {
	UserData
	// contains filtered or unexported fields
}

func (*User) IsClient

func (u *User) IsClient() bool

func (*User) Name

func (u *User) Name() string

func (*User) Send added in v1.0.0

func (u *User) Send(message string) error

func (*User) SendAction

func (u *User) SendAction(message string) error

func (*User) Stats

func (u *User) Stats() (*BanchoBotStats, error)

func (*User) Type

func (u *User) Type() RecipientType

func (*User) Where

func (u *User) Where() (string, error)

func (*User) Whois

func (u *User) Whois() (*WhoisData, error)

type UserData added in v1.0.0

type UserData struct {
	Id          int
	Username    string
	RankedScore int64
	Rank        int
	PlayCount   int
	Accuracy    float64
}

type WhoisData added in v1.0.0

type WhoisData struct {
	UserId   int
	Channels []*Channel
}

type WhoisUpdate added in v1.0.0

type WhoisUpdate struct {
	Type WhoisUpdateType

	User     *User
	UserId   int
	Channels []*Channel
}

type WhoisUpdateType added in v1.0.0

type WhoisUpdateType uint8

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL