This commit is contained in:
Cadey Ratio 2016-05-29 11:44:03 -07:00
parent 6b17c2f576
commit 6559385261
5 changed files with 560 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
pkg
var/*.db
bin

View File

@ -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 <shortname> <id>",
},
})
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)
}
}
}
}

View File

@ -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
}

View File

@ -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
}

0
var/.gitkeep Normal file
View File