Connect to an IRC server with Golang
Tags: go, learning, beginners, tutorial
Published: 2020-07-21 13:53:35

---
title: Connect to an IRC server with Golang
published: true
date: 2020-07-21 00:00:00 UTC
tags: Go, learning,beginners,tutorial
canonical_url: https://mnlwldr.com/connect-to-irc-golang/
---

In the last time, I really interested in learn [Golang](https://golang.org/).I like to learn while I code things and don’t read hundreds of blogs or books. So, here I show you, how I created a really simple IRC botin [Golang](https://golang.org/).

### IRC

Long before Discord, Slack or something else, we had IRC, and the best thing is, it’s still here. If you want to read more about IRC, [here is the Wikipedia article](https://en.wikipedia.org/wiki/Internet_Relay_Chat).

> IRC is an application layer protocol that facilitates communication in the form of text. The chat process works on a client/server networking model.

### Lets start

In this blog, I don’t explain everything. The most things are really easy to teach it to yourself from the official [documentation](https://golang.org/doc/).

How the most projects, I started with the Golang skeleton:

```golang
package main

func main() {

}

```

#### Connect

The first thing that comes to mind is to connect to the server. In Golang, you can simple import a package called `net`.

```golang
import (
    "net"
)

```

> Package `net` provides a portable interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain sockets.

The Dial function connects to a server, for example: `net.Dial("tcp", "your_server:your_port")`.

You can read more information about `net` and `net.Dial` here: [golang.org/pkg/net/](https://golang.org/pkg/net/)

Now I added a new function and name it … `connect()` … wow :D.

```golang
func connect() net.Conn {
    conn, err := net.Dial("tcp", "your_server:your_port")
    if err != nil {
        panic(err)
    }
    return conn
}

```

Maybe, I can try to explain a bit here

`func connect() net.Conn {`

That’s just a function, `net.Conn` is the return type.

`conn, err := net.Dial("tcp", "your_server:your_port")`

In Golang, we can return more than one values. So the first returned value is `conn` and the second returned value is `err` here.

```golang
if err != nil {
    panic(err)
}

```

Simple error “handling”. `panic(err)` abort and exit with a non-zero exit status.

When no error happened, we return the connection

`return conn`

#### Disconnect

Normally, when you connect to something you want also to disconnect. So I add another function and name it … `disconnect()` … wow, again :).

```golang
func disconnect(conn net.Conn) {
    conn.close()
}

```

I pass `conn` into the function here and call simple `conn.Close()`, to close the connection, obviously.

Now we can connect and disconnect to the server. Our code looks now like this:

```golang
package main

import (
	"net"
)

func connect() net.Conn {
	conn, err := net.Dial("tcp", "your_server:your_port")
	if err != nil {
		panic(err)
	}
	return conn
}

func disconnect(conn net.Conn) {
	conn.Close()
}

func main() {
    conn := connect()
    disconnect(conn)
}

```

#### Read server messages

Somewhere between `connect()` and `disconnect()` we need to receive and sent messages. Like a smalltalk with people you don’t like. To read messages, we import three new packages: `fmt`, `bufio`, and `net/textproto`.

> Package fmt implements formatted I/O with functions analogous to C’s printf and scanf.
> 
> Package bufio implements buffered I/O.
> 
> Package textproto implements generic support for text-based request/response protocols in the style of HTTP, NNTP, and SMTP.

So we put all together and add the following code between `connect()` and `disconnect()` in our `main()` function.

```golang
tp := textproto.NewReader(bufio.NewReader(conn))

for {
    status, err := tp.ReadLine()
    if err != nil {
        panic(err)
    }
    fmt.Println(status)
}

```

`for {}` is like an endless loop, read every line from `conn` and print it out to our screen.  
Now, we generate following output (I try to connect to [Freenode](https://freenode.net/) in this example)

```golang
:hitchcock.freenode.net NOTICE * :*** Looking up your hostname...
:hitchcock.freenode.net NOTICE * :*** Checking Ident
:hitchcock.freenode.net NOTICE * :*** Couldn't look up your hostname
:hitchcock.freenode.net NOTICE * :*** No Ident response

```

So far so good, we can receive messages.

#### Talk with the server

After we can connect, read the server messages and disconnect, we need to “talk” with the server. In the real world, when we meet someone new we say our name … mostly. Same with our connection to the IRC server. The IRC protocol has two commands for this. `USER` and `NICK`.

So we need a function to say something like “Hi Server, I’m TheManWithTheIceCreamVan” or something else.We used already the package `fmt` to print stuff to the screen, but we can use `fmt` also, to send a message to the server. We need the function `Fprintf` to do this.

For example: `fmt.Fprintf(os.Stdout, "%s is %d years old.\n", name, age)`

The first parameter of Fprintf can be our `conn` variable, and the rest is just the string to “write”.

For example, we can create a function and calling it `logon()`.

```golang
func logon(conn net.Conn) {
    fmt.Fprintf(conn, "USER TheManWithTheIceCreamVan 8 * :Someone\r\n")
    fmt.Fprintf(conn, "NICK TheManWithTheIceCreamVan\r\n")
}

```

At this point, we should directly create a helper function to send data to the server. We create a functionand name it `sendData()`.

```
func sendData(conn net.Conn, message string) {
    fmt.Fprintf(conn, "%s\r\n", message)
}

```

We simple pass the connection `conn`, and the message to send to this function, the function itself calling `fmt.Fprintf` then.

Back to our `logon()` method, we use this new created function.

```golang
func logon(conn net.Conn) {
    sendData(conn, "USER TheManWithTheIceCreamVan 8 * :Someone")
    sendData(conn, "NICK TheManWithTheIceCreamVan")
}

```

After the `connect()` line in our `main()` function, we can call `login(conn)` now. Our code looks now like this:

```golang
package main

import (
	"net"
	"fmt"
	"net/textproto"
	"bufio"
)

func connect() net.Conn {
	conn, err := net.Dial("tcp", "irc.freenode.net:6667")
	if err != nil {
		panic(err)
	}
	return conn
}

func disconnect(conn net.Conn) {
	conn.Close()
}

func logon(conn net.Conn) {
    sendData(conn, "USER TheManWithTheIceCreamVan 8 * :Someone")
    sendData(conn, "NICK TheManWithTheIceCreamVan")
}

func main() {
    conn := connect()
	 logon(conn)

	 tp := textproto.NewReader(bufio.NewReader(conn))

	for {
		status, err := tp.ReadLine()
		if err != nil {
			panic(err)
		}
		fmt.Println(status)
	}

    disconnect(conn)
}

func sendData(conn net.Conn, message string) {
    fmt.Fprintf(conn, "%s\r\n", message)
}

```

Now we got a lot of messages (MOTD, Stats, etc.) from the [Freenode](https://freenode.net/) Server.

```
:tolkien.freenode.net NOTICE * :*** Looking up your hostname...
:tolkien.freenode.net NOTICE * :*** Checking Ident
:tolkien.freenode.net NOTICE * :*** No Ident response
:tolkien.freenode.net NOTICE * :*** Couldn't look up your hostname
:tolkien.freenode.net 001 TheManWithTheIce :Welcome to the freenode Internet Relay Chat Network TheManWithTheIce
:tolkien.freenode.net 002 TheManWithTheIce :Your host is tolkien.freenode.net[204.225.96.251/6667], running version ircd-seven-1.1.9
:tolkien.freenode.net 003 TheManWithTheIce :This server was created Fri Apr 24 2020 at 22:19:21 UTC

...

:tolkien.freenode.net 376 TheManWithTheIce :End of /MOTD command.
:TheManWithTheIce MODE TheManWithTheIce :+i

```

The last line means, we got `USERMODE` +i from the IRC server. That’s because we requested this with the “8” in our `USER` command:

`sendData(conn, "USER TheManWithTheIceCreamVan 8 * :Someone")`

Our Bot is connected to the server. When you `/whois TheManWithTheIceCreamVan` from your regular IRC client you should get something like this

```
21:04 -!- TheManWithTheIce [~TheManWit@1.1.1.1]
21:04 -!- ircname : Someone
21:04 -!- server : hitchcock.freenode.net [Sofia, BG, EU]
21:04 -!- End of WHOIS

```

#### PONG

At regular intervals, the IRC server send you a “PING” message and when you don’t answer this message with a “PONG” message, the server will close the connection after a few seconds.

```
:TheManWithTheIce MODE TheManWithTheIce :+i
PING :hitchcock.freenode.net
:TheManWithTheIce!~TheManWit@37.228.165.230 QUIT :Ping timeout: 258 seconds

```

All we need is to check if we receive a message that start with “PING”. There’re different possibilities to do this, some people use regular expressions for this, we just use `HasPrefix()` function from the `strings` packages. For example:

```golang
if strings.HasPrefix("FOOBAR", "FOO") {
    // FOOBAR start with FOO
} else {
    // FOOBAR don't start with FOO
}

```

First, we could create a new method and name it … `pong()` :)

```golang
func pong(conn net.Conn) {
	sendData(conn, "PONG")
}

```

It’s just like the `logon()` function. Everything we need to do is to send a simple “PONG” as message to the server.Now we can check the incoming messages and answer with our `pong()` function:

```golang
func main() {
    conn := connect()
    logon(conn)

    tp := textproto.NewReader(bufio.NewReader(conn))

    for {
        status, err := tp.ReadLine()
        if err != nil {
            panic(err)
        }

        fmt.Println(status)

        if strings.HasPrefix(status, "PING") {
            pong(conn)
        }
    }

    disconnect(conn)
}

```

#### SIGTERM

You kill the bot mostly with an SIGTERM, like CTRL+C. To catch this and give the bot the possibility to for a clean `disconnect()`we can implement the following:

```golang
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
    <-c
    disconnect(conn)
}()

```

… and add this to our `main()` function

```golang
func main() {
    conn := connect()

    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        disconnect(conn)
    }()

    logon(conn)

    tp := textproto.NewReader(bufio.NewReader(conn))

    for {
        status, err := tp.ReadLine()
        if err != nil {
            panic(err)
        }

        fmt.Println(status)

        if strings.HasPrefix(status, "PING") {
            pong(conn)
        }
    }

    disconnect(conn)
}

```

#### QUIT

It’s a good manner to say goodbye politely. So before we hard `disconnect()` from the server, we send the QUIT command

```golang
func disconnect(conn net.Conn) {
    sendData(conn, "QUIT Bye")
    conn.Close()
}

```

#### Full code

```golang
package main

import (
	"bufio"
	"fmt"
	"net"
	"net/textproto"
	"os"
	"os/signal"
	"strings"
	"syscall"
)

func connect() net.Conn {
	conn, err := net.Dial("tcp", "irc.freenode.net:6667")
	if err != nil {
		panic(err)
	}
	return conn
}

func disconnect(conn net.Conn) {
	sendData(conn, "QUIT Bye")
	conn.Close()
}

func login(conn net.Conn) {
	sendData(conn, "USER TheManWithTheIceCreamVan 8 * :Someone")
	sendData(conn, "NICK TheManWithTheIceCreamVan")
}

func pong(conn net.Conn) {
	sendData(conn, "PONG")
}

func main() {

	conn := connect()

	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		disconnect(conn)
	}()

	login(conn)

	tp := textproto.NewReader(bufio.NewReader(conn))

	for {
		status, err := tp.ReadLine()
		if err != nil {
			panic(err)
		}

		fmt.Println(status)

		if strings.HasPrefix(status, "PING") {
			pong(conn)
		}
	}
}

func sendData(conn net.Conn, message string) {
	fmt.Fprintf(conn, "%s\r\n", message)
}

```

Maybe, I will update this post sometime to show, how to join a channel and “talk” with other people :)