From 655938526142fab7313a428740c8d1b0badfb0c1 Mon Sep 17 00:00:00 2001 From: Cadey Dodrill Date: Sun, 29 May 2016 11:44:03 -0700 Subject: [PATCH] Add code --- .gitignore | 3 + src/xeserv.us/cmd/tbotd/main.go | 307 +++++++++++++++++++++++++++++++ src/xeserv.us/cmd/tbotd/types.go | 170 +++++++++++++++++ src/xeserv.us/r1459/line.go | 80 ++++++++ var/.gitkeep | 0 5 files changed, 560 insertions(+) create mode 100644 .gitignore create mode 100644 src/xeserv.us/cmd/tbotd/main.go create mode 100644 src/xeserv.us/cmd/tbotd/types.go create mode 100644 src/xeserv.us/r1459/line.go create mode 100644 var/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0149473 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +pkg +var/*.db +bin \ No newline at end of file diff --git a/src/xeserv.us/cmd/tbotd/main.go b/src/xeserv.us/cmd/tbotd/main.go new file mode 100644 index 0000000..60f96e2 --- /dev/null +++ b/src/xeserv.us/cmd/tbotd/main.go @@ -0,0 +1,307 @@ +package main + +import ( + "bufio" + "errors" + "flag" + "fmt" + "io" + "log" + "net" + "net/textproto" + "strconv" + + "github.com/asdine/storm" + + "gopkg.in/telegram-bot-api.v4" + "xeserv.us/r1459" +) + +var ( + vhost = flag.String("vhost", "telegram.org", "vhost to use for things") + port = flag.Int("port", 3540, "port to listen on") + store = flag.String("store", "var/", "where to store things") + + rooms map[string]int64 +) + +type Client struct { + net.Conn + limitReader *io.LimitedReader + tpReader *textproto.Reader + sentNick bool + sentUser bool + nagged bool + + username string + Nick string + Password *string + DB *storm.DB + TGBot *tgbotapi.BotAPI +} + +func (c *Client) KillClient(err error) { + c.SendLine(&r1459.RawLine{ + Source: "xeserv.us", + Verb: "ERROR", + Args: []string{ + "***", + "Closing link: " + err.Error(), + }, + }) + + c.Close() + + c = nil +} + +func (c *Client) SendLine(line *r1459.RawLine) error { + log.Printf("%s <- %s", c.Nick, line) + _, err := fmt.Fprintf(c, "%s\r\n", line) + return err +} + +func (c *Client) Join(shortname string, roomID int64) { + room, err := c.MakeRoom(shortname, roomID) + if err != nil { + c.KillClient(err) + + log.Fatal(err) + } + + for _, line := range room.Burst() { + c.SendLine(line) + } +} + +func Mask(nick string) string { + return fmt.Sprintf("%s!telegram@%s", nick, *vhost) +} + +func main() { + flag.Parse() + + server, err := net.Listen("tcp", ":"+strconv.Itoa(*port)) + if server == nil { + panic("couldn't start listening: " + err.Error()) + } + + for { + client, err := server.Accept() + if err != nil { + log.Printf("couldn't accept: %s", err.Error()) + continue + } + + log.Printf("Accepted connection from %s", client.RemoteAddr()) + + lr := &io.LimitedReader{ + R: client, + N: 65536, + } + + tpr := textproto.NewReader(bufio.NewReader(lr)) + + c := &Client{ + Conn: client, + limitReader: lr, + tpReader: tpr, + } + + go handleClient(c) + } +} + +func handleClient(c *Client) { + for { + line, err := c.tpReader.ReadLine() + if err != nil { + _ = c.Close() + log.Printf("%s: %s", c.RemoteAddr(), err) + + return + } + + log.Printf("%s -> %s", c.RemoteAddr(), line) + + ircLine := r1459.NewRawLine(line) + + switch ircLine.Verb { + case "NICK": + if len(ircLine.Args) != 1 { + c.Close() + } + + c.sentNick = true + c.Nick = ircLine.Args[0] + + if c.DB == nil { + db, err := storm.Open("var/" + c.Nick + ".db") + if err != nil { + c.KillClient(err) + + return + } + + c.DB = db + } + case "PASS": + if len(ircLine.Args) != 1 { + c.KillClient(errors.New("Invalid format")) + + return + } + + c.Password = &ircLine.Args[0] + + bot, err := tgbotapi.NewBotAPI(*c.Password) + if err != nil { + c.KillClient(err) + + return + } + + me, err := bot.GetMe() + if err != nil { + c.KillClient(err) + + return + } + + c.SendLine(&r1459.RawLine{ + Source: "xeserv.us", + Verb: "NOTICE", + Args: []string{ + "***", + fmt.Sprintf("You are logged in as %s (@%s)", me.FirstName, me.UserName), + }, + }) + + c.TGBot = bot + + u := tgbotapi.NewUpdate(0) + u.Timeout = 60 + + updates, err := bot.GetUpdatesChan(u) + if err != nil { + c.KillClient(err) + } + + go func(c *Client) { + for update := range updates { + id := update.Message.Chat.ID + r := &Room{} + + c.DB.One("RoomID", id, r) + + r.client = c + + if update.Message.Text != "" { + r.RecieveMessage(update) + } + } + }(c) + + case "USER": + if len(ircLine.Args) != 4 { + c.Close() + } + + c.sentUser = true + + case "PING": + ircLine.Verb = "PONG" + c.SendLine(ircLine) + + case "PONG", "MODE", "JOIN", "WHO": + continue + + case "ASSOCIATE": + if len(ircLine.Args) != 2 { + c.SendLine(&r1459.RawLine{ + Verb: "NOTICE", + Source: "xeserv.us", + Args: []string{ + c.Nick, "JOIN ", + }, + }) + + continue + } + + n, _ := strconv.Atoi(ircLine.Args[1]) + c.Join(ircLine.Args[0], int64(n)) + + case "PRIVMSG": + room := &Room{} + + err := c.DB.One("Shortname", ircLine.Args[0][1:], room) + if err != nil { + panic(err) + } + + room.client = c + + room.SendMessage(ircLine) + + default: + c.SendLine(&r1459.RawLine{ + Source: "xeserv.us", + Verb: "421", + Args: []string{ + c.Nick, + "unknown command " + ircLine.Verb, + }, + }) + } + + if c.TGBot != nil && + c.Password != nil && + c.sentNick && + c.sentUser && + !c.nagged { + c.nagged = true + + c.SendLine(&r1459.RawLine{ + Source: "xeserv.us", + Verb: "001", + Args: []string{ + c.Nick, + "Welcome to the Internet Relay Chat Network: " + c.Nick, + }, + }) + + c.SendLine(&r1459.RawLine{ + Source: "xeserv.us", + Verb: "002", + Args: []string{ + c.Nick, + fmt.Sprintf("Your host is xeserv.us[%s], running version tbotd HEAD DEVEL", c.LocalAddr()), + }, + }) + + c.SendLine(&r1459.RawLine{ + Source: "xeserv.us", + Verb: "005", + Args: []string{ + c.Nick, + "CHANTYPES=!#", "CHANMODES=b,,,ns", "PREFIX=(ya)~&", "NETWORK=Telegram", + "are supported by this server", + }, + }) + + rooms := []Room{} + + err := c.DB.All(&rooms) + if err != nil { + c.KillClient(err) + + return + } + + for _, room := range rooms { + c.Join(room.Shortname, room.RoomID) + } + } + } +} diff --git a/src/xeserv.us/cmd/tbotd/types.go b/src/xeserv.us/cmd/tbotd/types.go new file mode 100644 index 0000000..f636518 --- /dev/null +++ b/src/xeserv.us/cmd/tbotd/types.go @@ -0,0 +1,170 @@ +package main + +import ( + "fmt" + "log" + "math/rand" + "strings" + "time" + + "xeserv.us/r1459" + + "gopkg.in/telegram-bot-api.v4" +) + +type Room struct { + ID int + RoomID int64 `storm:"index" storm:"unique"` + Title string + Shortname string `storm:"index" storm:"unique"` + CreationDate time.Time + + Members []string + + client *Client +} + +func (c *Client) MakeRoom(shortname string, roomID int64) (*Room, error) { + room := &Room{} + err := c.DB.One("RoomID", roomID, room) + if err != nil { + chat, err := c.TGBot.GetChat(tgbotapi.ChatConfig{ + ChatID: roomID, + }) + if err != nil { + return nil, err + } + + room = &Room{ + ID: rand.Int(), + RoomID: roomID, + Title: chat.Title, + Shortname: shortname, + CreationDate: time.Now(), + Members: []string{}, + } + + admins, err := c.TGBot.GetChatAdministrators(tgbotapi.ChatConfig{ + ChatID: roomID, + }) + + var nicks []string + + for _, admin := range admins { + var status string + if admin.IsCreator() { + status = "~" + } else { + status = "&" + } + nicks = append(nicks, status+admin.User.UserName) + } + + nicks = append(nicks, c.Nick) + + room.Members = append(room.Members, nicks...) + + c.DB.Save(room) + } + + room.client = c + + return room, nil +} + +func (r *Room) Burst() []*r1459.RawLine { + ret := []*r1459.RawLine{} + c := r.client + + ret = append(ret, &r1459.RawLine{ + Verb: "JOIN", + Source: Mask(c.Nick), + Args: []string{ + fmt.Sprintf("#%v", r.Shortname), + }, + }) + + ret = append(ret, &r1459.RawLine{ + Verb: "MODE", + Source: "xeserv.us", + Args: []string{ + fmt.Sprintf("#%v", r.Shortname), + "+nt", + }, + }) + + ret = append(ret, &r1459.RawLine{ + Verb: "TOPIC", + Source: "xeserv.us", + Args: []string{ + fmt.Sprintf("#%v", r.Shortname), + r.Title, + }, + }) + + ret = append(ret, &r1459.RawLine{ + Verb: "353", + Source: "xeserv.us", + Args: []string{ + c.Nick, "=", fmt.Sprintf("#%v", r.Shortname), + strings.Join(r.Members, " "), + }, + }) + + ret = append(ret, &r1459.RawLine{ + Verb: "366", + Source: "xeserv.us", + Args: []string{ + c.Nick, fmt.Sprintf("#%v", r.Shortname), + "End of /NAMES list", + }, + }) + + return ret +} + +func (r Room) RecieveMessage(update tgbotapi.Update) { + c := r.client + + log.Printf("[#%v] %s %s", r.Shortname, update.Message.From.UserName, update.Message.Text) + + found := false + for _, member := range r.Members { + if member == "" { + continue + } + if update.Message.From.UserName == member[1:] || update.Message.From.UserName == member { + found = true + } + } + + if !found { + r.Members = append(r.Members, update.Message.From.UserName) + c.DB.Save(&r) + + c.SendLine(&r1459.RawLine{ + Verb: "JOIN", + Source: Mask(update.Message.From.UserName), + Args: []string{ + "#" + r.Shortname, + }, + }) + } + + for _, msgLine := range strings.Split(update.Message.Text, " ") { + c.SendLine(&r1459.RawLine{ + Verb: "PRIVMSG", + Source: Mask(update.Message.From.UserName), + Args: []string{ + "#" + r.Shortname, + msgLine, + }, + }) + } +} + +func (r Room) SendMessage(l *r1459.RawLine) error { + msg := tgbotapi.NewMessage(r.RoomID, l.Args[1]) + _, err := r.client.TGBot.Send(msg) + return err +} diff --git a/src/xeserv.us/r1459/line.go b/src/xeserv.us/r1459/line.go new file mode 100644 index 0000000..c058dc2 --- /dev/null +++ b/src/xeserv.us/r1459/line.go @@ -0,0 +1,80 @@ +// Package r1459 implements a base structure to scrape out and utilize an RFC 1459 +// frame in high level Go code. +package r1459 + +import ( + "fmt" + "strings" +) + +// RawLine represents an IRC line. +type RawLine struct { + Source string `json:"source"` + Verb string `json:"verb"` + Args []string `json:"args"` + Tags map[string]string `json:"tags"` + Raw string `json:"-"` // Deprecated +} + +// NewRawLine creates a new line and split out an RFC 1459 frame to a RawLine. This will +// not return an error if it fails. +func NewRawLine(input string) (line *RawLine) { + line = &RawLine{ + Raw: input, + } + + split := strings.Split(input, " ") + + if split[0][0] == ':' { + line.Source = split[0][1:] + line.Verb = split[1] + split = split[2:] + } else { + line.Source = "" + line.Verb = split[0] + split = split[1:] + } + + argstring := strings.Join(split, " ") + extparam := strings.Split(argstring, " :") + + if len(extparam) > 1 { + ext := strings.Join(extparam[1:], " :") + args := strings.Split(extparam[0], " ") + + line.Args = append(args, ext) + } else { + line.Args = split + } + + if len(line.Args) == 0 { + line.Args = []string{""} + } else if line.Args[0][0] == ':' { + line.Args[0] = strings.TrimPrefix(line.Args[0], ":") + } + + line.Verb = strings.ToUpper(line.Verb) + + return +} + +// String returns the serialized form of a RawLine as an RFC 1459 frame. +func (r *RawLine) String() (res string) { + if r.Source != "" { + res = res + fmt.Sprintf(":%s ", r.Source) + } + + res = res + fmt.Sprintf("%s", r.Verb) + + for i, arg := range r.Args { + res = res + " " + + if i == len(r.Args)-1 { // Make the last part of the line an extparam + res = res + ":" + } + + res = res + arg + } + + return +} diff --git a/var/.gitkeep b/var/.gitkeep new file mode 100644 index 0000000..e69de29