Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
boutros committed May 25, 2014
0 parents commit a51c1a1
Show file tree
Hide file tree
Showing 10 changed files with 779 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.out
*.deb
deb/snakk/usr
snakk
25 changes: 25 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
all: test

deps:
@go get -d -v ./...
@go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs -n1 go get -d

build: deps
@export GOBIN=$(shell pwd)
@go build

deb: clean build
@mkdir -p deb/snakk/usr/share/snakk
@cp -r config.json data ./snakk deb/snakk/usr/share/snakk/
@dpkg-deb --build deb/snakk

test: deps
@go test

cover:
@go test -coverprofile=coverage.out
@go tool cover -html=coverage.out

clean:
@go clean
@rm -f *.out
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Snakk

Chat-server with an IRC-like web frontend.

### Install

```bash
wget https://github.com/boutros/snakk/release.deb
sudo dpkg -i snakk0.7.deb
sudo service snakk start
```

This will set it up as an upstart service. The application will be installed to `/usr/share/snakk`. You'll find a config file there. You must restart the service for any configuration changes to take effect.

To uninstall, run `sudo dpkg -r snakk`
9 changes: 9 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"ServePort": 9999,
"LogLevel": "info",
"Logfile": "/usr/share/snakk/server.log",
"ChatHistoryNumLines": 100,
"UseBasicAuth": false,
"BasicAuthUsername": "frisk",
"BasicAuthPassword": "fisk"
}
Empty file added data/app.js
Empty file.
Binary file added data/irc.ico
Binary file not shown.
189 changes: 189 additions & 0 deletions data/page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>snakk!</title>
<style>
html { height: 100%; overflow: hidden; }
* { font-family: monospace; font-size: 13px;}
body { margin: 0; padding: 0; }
ul { list-style: none; padding: 4px; margin: 0}
#container { width: 100%}
#chatRoom { position: absolute; top: 0; bottom: 24px; left: 0; right: 154px; overflow-y: scroll; border-bottom: 1px solid #aaa; border-right: 1px solid #aaa; word-wrap: break-word;}
#usersBar { position: absolute; top: 0; right: 4px; bottom: 24px; width: 148px; border: 1px solid #ccc; background: #eee}
#inputBar { position: absolute; bottom: 0px; border: 0px; width: 100%; height:20px; padding-left: 6px; }
#userList { font-weight: bold; }
#userList li { margin: 4px; padding: 2px}
.timeStamp { min-width: 80px;}
.agent { display: inline-block; width: 80px; text-align: right; padding:0 6px; right; border-right: 1px solid #ccc;}
.message { margin-left:6px; }
.grey { color: #666 }
.green { color: green }
.red { color: #b00 }
</style>
</head>

<body>
<div id="container">
<div id="chatRoom">
<ul id="chatLines"></ul>
</div>
<div id="usersBar">
<ul id="userList">
{{range .Users}}
<li id="user-{{.ID}}">{{.Nick}}</li>
{{end}}
</ul>
</div>
<input type="text" placeholder="say something" id="inputBar" />
</div>
<script>
var hist = [""];
var i = hist.length - 1;
var ws;

var joined = /has joined the chat/;
var newName = /is now known as/;

function parseTime(s) {
//2014-05-25T09:25:28.192473475+02:00
return s.slice(11, 16);
}

function linkify(text) {
if (text) {
text = text.replace(
/((https?\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi,
function(url){
var full_url = url;
if (!full_url.match('^https?:\/\/')) {
full_url = 'http://' + full_url;
}
return '<a target="_blank" href="' + full_url + '">' + url + '</a>';
}
);
}
return text;
}

function keyup(e) {
e = e || window.event;
key = e.keyCode || e.wich || e.charCode;

var v = this.value;
var c = true; // ?

switch(key) {
case 13: // post message to chat room
if (v) {
i = hist.length -1;
hist[i++] = v;
hist[i] = "";
ws.send(v);
}
case 27: // clear input field
this.value = "";
default: // just typing
c = false;
break;
case 38: // up, back in history
if (i) {
i--;
}
break
case 40: // down, forward in history
if (i < hist.length - 1) {
i++;
break
}
}

if(c) this.value = hist[i];

}

document.addEventListener('DOMContentLoaded', function(){
var lines = document.getElementById("chatLines");
var chatRoom = document.getElementById("chatRoom");
var userList = document.getElementById("userList");
chatRoom.scrollTop = chatRoom.scrollHeight;

// focus on input field
var input = document.getElementById("inputBar");
input.focus();
input.value = "/nick ";

// listen for messages
input.addEventListener("keyup", keyup, false);

// connect & handle ws messages
ws = new WebSocket("ws://{{.Host}}/ws");
ws.onopen = function(e) {
console.log("ws connected");
}
ws.onclose = function(e) {
var li = document.createElement("li");
if (li.classList) {
li.classList.add("red");
} else {
li.className += " red";
}
li.innerHTML = "ERROR: Server unavailable. Refresh page to retry";
lines.appendChild(li);
chatRoom.scrollTop = chatRoom.scrollHeight;
input.setAttribute("disabled", true);

}
ws.onerror = function(e) {
console.log("ws error: " + e.data);
}
ws.onmessage = function(e) {
console.log(e.data);
var line = JSON.parse(e.data);
var li = document.createElement("li");
if (line.Color != "") {
if (li.classList) {
li.classList.add(line.Color);
} else {
li.className += ' ' + line.Color;
}

}
var c = "<span class='timeStamp'>[" + parseTime(line.TimeStamp) + "]</span>" +
"<span class='agent'>" + line.Author + "</span>" +
"<span class='message'>" + linkify(line.Message) + "</span>";
li.innerHTML = c;
lines.appendChild(li);
// scroll to bottom
chatRoom.scrollTop = chatRoom.scrollHeight;

if (line.UserNew) {
var li = document.createElement("li");
li.setAttribute("id", "user-"+line.UserNew);
li.innerHTML = line.UserNick;
userList.appendChild(li);
}

if (line.UserChange) {
var old = document.getElementById("user-"+line.UserChange);
if ( old) {
userList.removeChild(old);
}
var li = document.createElement("li");
li.setAttribute("id", "user-"+line.UserChange);
li.innerHTML = line.UserNick;
userList.appendChild(li);
}

if (line.UserLeft) {
var old = document.getElementById("user-"+line.UserLeft);
if ( old) {
userList.removeChild(old);
}
}
}
});
</script>
</body>
</html>
60 changes: 60 additions & 0 deletions fifo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

// FIFO is queue of chatLines implemented as a ring buffer, so
// that it always keep the latest >size> messages.
type FIFO struct {
items []chatLine
oldest int // oldest item in buffer
next int // next write mark
size int // current size of items
full bool // true when buffer is full
}

// newFIFO creates a new FIFO queue of max size chatlines.
func newFIFO(size int) *FIFO {
return &FIFO{items: make([]chatLine, size)}
}

// Push an chatline intem to the queue.
func (q *FIFO) Push(l chatLine) {
// assign item
q.items[q.next] = l

// increase size if still below max size
if !q.full {
q.size = q.size + 1
}

if q.full {
// move read marker ahead
if q.oldest < len(q.items)-1 {
q.oldest = q.oldest + 1
} else {
// or back to start if end is reached
q.oldest = 0
}
}

// move write mark
if q.next < len(q.items)-1 {
q.next = q.next + 1
} else {
// back to start of buffer
q.next = 0
q.full = true
}

}

// All returns all chatLine items, in cronological order.
func (q *FIFO) All() []chatLine {
all := make([]chatLine, q.size)

if q.next >= q.next && !q.full {
copy(all, q.items[q.oldest:q.size])
} else {
copy(all, q.items[q.oldest:q.size])
copy(all[len(q.items[q.oldest:q.size]):q.size], q.items[0:q.next])
}
return all
}
82 changes: 82 additions & 0 deletions fifo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import "testing"

func TestFIFO(t *testing.T) {
q := newFIFO(3)

a := chatLine{Message: "a"}
b := chatLine{Message: "b"}
c := chatLine{Message: "c"}
d := chatLine{Message: "d"}
e := chatLine{Message: "e"}
f := chatLine{Message: "f"}
g := chatLine{Message: "g"}

if len(q.items) != 3 {
t.Errorf("len(newFIFO(3)) => %d; want 3", len(q.items))
}

if q.size != 0 {
t.Errorf("empty queue should have size 0")
}

q.Push(a)
if q.size != 1 {
t.Errorf("queue with one item should have size 1")
}

q.Push(b)

all := q.All()
if len(all) != 2 {
t.Errorf("len(q.All()) => %d; want 2", len(all))
}

if all[0].Message != "a" {
t.Errorf("q.All() returns items in wrong order")
}

if all[1].Message != "b" {
t.Errorf("q.All() returns items in wrong order")
}

q.Push(c)
q.Push(d)

all = q.All()
if len(all) != 3 {
t.Errorf("len(q.All()) => %d; want 3", len(q.All()))
}

if all[0].Message != "b" {
t.Errorf("old items not overwritten by new item")
}

if all[1].Message != "c" {
t.Errorf("old items not overwritten by new item")
}

if all[2].Message != "d" {
t.Errorf("old items not overwritten by new item")
}

q.Push(e)
q.Push(f)
q.Push(g)

all = q.All()

if all[0].Message != "e" {
t.Errorf("old items not overwritten by new item")
}

if all[1].Message != "f" {
t.Errorf("old items not overwritten by new item")
}

if all[2].Message != "g" {
t.Errorf("old items not overwritten by new item")
}

}
Loading

0 comments on commit a51c1a1

Please sign in to comment.