-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a51c1a1
Showing
10 changed files
with
779 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.out | ||
*.deb | ||
deb/snakk/usr | ||
snakk |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
|
||
} |
Oops, something went wrong.