Split client into its own repo
This commit is contained in:
parent
30eaf147f1
commit
9303e6f8e5
|
@ -0,0 +1 @@
|
||||||
|
icon-*.png
|
|
@ -0,0 +1,56 @@
|
||||||
|
Copyright (c) 2015 Neale Pickett
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
The software is provided "as is", without warranty of any kind, express or
|
||||||
|
implied, including but not limited to the warranties of merchantability,
|
||||||
|
fitness for a particular purpose and noninfringement. In no event shall
|
||||||
|
the authors or copyright holders be liable for any claim, damages or
|
||||||
|
other liability, whether in an action of contract, tort or otherwise,
|
||||||
|
arising from, out of or in connection with the Software or the use or
|
||||||
|
other dealings in the Software.
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
|
||||||
|
Parts of this program are from circ,
|
||||||
|
which was obtained under the following license:
|
||||||
|
|
||||||
|
|
||||||
|
Copyright 2012, Google Inc.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
Makefile
28
Makefile
|
@ -1,24 +1,18 @@
|
||||||
ICONS += app/icon-16.png
|
ICONS += icon-16.png
|
||||||
ICONS += app/icon-32.png
|
ICONS += icon-32.png
|
||||||
ICONS += app/icon-48.png
|
ICONS += icon-48.png
|
||||||
ICONS += app/icon-128.png
|
ICONS += icon-128.png
|
||||||
ICONS += app/icon-256.png
|
ICONS += icon-256.png
|
||||||
|
|
||||||
all: icons serverside
|
all: icons
|
||||||
|
|
||||||
serverside: spongy spongy.cgi
|
|
||||||
|
|
||||||
spongy: src/spongy/spongy.go
|
|
||||||
GOPATH=$(CURDIR) go build -v $@
|
|
||||||
|
|
||||||
spongy.cgi: src/spongy.cgi/spongy.cgi.go
|
|
||||||
GOPATH=$(CURDIR) go build -v $@
|
|
||||||
chmod +s $@
|
|
||||||
|
|
||||||
icons: $(ICONS)
|
icons: $(ICONS)
|
||||||
|
|
||||||
app/icon-%.png: chat.svg
|
icon-%.png: icon.svg
|
||||||
inkscape --export-png=$@ --export-width=$* $<
|
inkscape --export-png=$@ --export-width=$* $<
|
||||||
|
|
||||||
package: icons
|
package: icons
|
||||||
cd app && zip -ru ../package.zip .
|
git ls-files | zip -ru -@ /tmp/spongy-client-chrome.zip
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(ICONS)
|
||||||
|
|
37
README.md
37
README.md
|
@ -1,30 +1,13 @@
|
||||||
Spongy IRC
|
Spongy IRC Client for Chrome
|
||||||
=========
|
===================
|
||||||
|
|
||||||
This is a sort of bouncer for clients with transient network connections,
|
This is a Chrome client for the
|
||||||
like cell phones and laptops.
|
[Spongy IRC Bouncer Thing](https://github.com/nealey/spongy).
|
||||||
It's a lot like [tapchat](https://github.com/tapchat/tapchat) but is a whole lot simpler
|
|
||||||
while being (at time of writing) much more feature-complete.
|
|
||||||
|
|
||||||
It supports (currently) an JavaScript browser-based client,
|
The user interface is based heavily on
|
||||||
and can also be worked from the command-line using Unix tools like "tail" and "echo".
|
[circ](https://github.com/flackr/circ).
|
||||||
|
And when I say "heavily" here,
|
||||||
|
I mean that I lifted it almost verbatim and touched only what was required to have it work with my JavaScript.
|
||||||
|
|
||||||
Ironically, it doesn't currently work with any existing IRC clients,
|
You probably want to install this from the Chrome Store.
|
||||||
although we are kicking around ideas for such a thing.
|
I'll put a link to that when I have one.
|
||||||
Honestly, though, if you want a bouncer for a traditional IRC client,
|
|
||||||
you are better off using something like znc.
|
|
||||||
|
|
||||||
We have an [architectural diagram](https://docs.google.com/drawings/d/1am_RTUh89kul-318GoYK73AbjOE_jMYi4vI4NyEgKrY/edit?usp=sharing) if you care about such things.
|
|
||||||
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
* Gracefully handles clients with transient networking, such as cell phones and laptops.
|
|
||||||
|
|
||||||
|
|
||||||
Other Documentation
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* [Installation Instructions](INSTALL.md)
|
|
||||||
* [Todo list](TODO.md)
|
|
||||||
|
|
109
doc/INSTALL.md
109
doc/INSTALL.md
|
@ -1,109 +0,0 @@
|
||||||
Spongy Server Installation
|
|
||||||
==========================
|
|
||||||
|
|
||||||
You gotta make a base directory with an `authtok` file,
|
|
||||||
and a subdirectory for every server you want to connect to.
|
|
||||||
|
|
||||||
BASE_DIRECTORY
|
|
||||||
+-- slashnet
|
|
||||||
| +-- handler
|
|
||||||
| +-- config/
|
|
||||||
| | +-- server
|
|
||||||
| | +-- gecos
|
|
||||||
| | +-- nick
|
|
||||||
| +-- log/
|
|
||||||
| | +-- 2015-01-29T19:56:27Z.log
|
|
||||||
| | +-- 2015-01-29T20:01:15Z.log
|
|
||||||
| | +-- 2015-01-29T20:41:40Z.log
|
|
||||||
| | +-- 2015-01-29T20:41:48Z.log
|
|
||||||
| | +-- 2015-01-29T20:41:56Z.log
|
|
||||||
| | +-- 2015-01-29T20:42:44Z.log
|
|
||||||
| +-- outq/
|
|
||||||
+-- oftc
|
|
||||||
+-- server3
|
|
||||||
+-- server4
|
|
||||||
|
|
||||||
|
|
||||||
`config` directory
|
|
||||||
------------------
|
|
||||||
|
|
||||||
The `config` directory in a server directory must have certain files:
|
|
||||||
|
|
||||||
* `server` is a list of servers to try and connect to, in the form `hostname:port`
|
|
||||||
* `gecos` is your "Real Name"
|
|
||||||
* `nick` is a list of nicknames you'd like to use
|
|
||||||
|
|
||||||
The lists are gone through starting with the first entry until one sticks.
|
|
||||||
|
|
||||||
|
|
||||||
`outq` directory
|
|
||||||
----------------
|
|
||||||
|
|
||||||
The `outq` directory is monitored by spongy.
|
|
||||||
When a new file shows up, its contents are spit out verbatim
|
|
||||||
over the server connection.
|
|
||||||
|
|
||||||
So if you want to send a message to a channel,
|
|
||||||
do something like this:
|
|
||||||
|
|
||||||
$ echo 'PRIVMSG #channel :hello world' > outq/$$.$(date +%s)
|
|
||||||
|
|
||||||
|
|
||||||
Starting up
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Pretty easy:
|
|
||||||
|
|
||||||
$ cd BASE_DIRECTORY; /path/to/spongy
|
|
||||||
|
|
||||||
Spongy will go off and connect to every configured server in BASE_DIRECTORY.
|
|
||||||
|
|
||||||
|
|
||||||
Spongy CGI Configuration
|
|
||||||
========================
|
|
||||||
|
|
||||||
If you'd like to run `spongy.cgi`,
|
|
||||||
that's fine,
|
|
||||||
but you have to create a file
|
|
||||||
called `spongy.basedir`
|
|
||||||
in the same directory as the CGI.
|
|
||||||
You can do it like this:
|
|
||||||
|
|
||||||
$ echo '/home/neale/BASE_DIRECTORY' > spongy.basedir
|
|
||||||
|
|
||||||
And then,
|
|
||||||
in `BASE_DIRECTORY`,
|
|
||||||
you need a file called `auth`
|
|
||||||
with a sha256 checksum of the authorization token
|
|
||||||
you want to use in the client.
|
|
||||||
|
|
||||||
You can make it like this:
|
|
||||||
|
|
||||||
$ printf 'my fabulous token' | sha256sum | cut -d\ -f1 > BASE_DIRECTORY
|
|
||||||
|
|
||||||
|
|
||||||
Permissions
|
|
||||||
-----------
|
|
||||||
|
|
||||||
There are a lot of different ways to set up permissions.
|
|
||||||
Here's what I suggest:
|
|
||||||
make `spongy.cgi` setuid to you.
|
|
||||||
|
|
||||||
$ chmod +s spongy.cgi
|
|
||||||
|
|
||||||
If it's setuid,
|
|
||||||
you don't need to make your config file
|
|
||||||
(or any other files)
|
|
||||||
readable by the user that
|
|
||||||
runs the web server.
|
|
||||||
|
|
||||||
Sadly,
|
|
||||||
Apache has a whole bunch of weirdness in place
|
|
||||||
which prevents setuid CGI from working
|
|
||||||
without a lot of configuration twiddling.
|
|
||||||
But it also has its own mechanism for running CGI
|
|
||||||
as the user who owns it.
|
|
||||||
So if you're using Apache,
|
|
||||||
please send me a recipe for your solution,
|
|
||||||
and I'll add it to the distribution :)
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Neale Pickett
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
wirc protocol
|
|
||||||
===========
|
|
||||||
|
|
||||||
This document attempts to describe the wirc protocol.
|
|
||||||
The source code will always be authoritative,
|
|
||||||
but this protocol has been around for a while now so should be changed often, if ever.
|
|
||||||
|
|
||||||
|
|
||||||
Out Queue
|
|
||||||
---------
|
|
||||||
|
|
||||||
|
|
||||||
Any files that appear in the directory outq/ are written verbatim to the IRC server.
|
|
||||||
|
|
||||||
You may put multiple lines in a single file.
|
|
||||||
|
|
||||||
Filenames beginning with "." are ignored.
|
|
||||||
|
|
||||||
You are advised to create files beginning with ".",
|
|
||||||
then rename them on completion of the write,
|
|
||||||
to avoid race conditions.
|
|
||||||
|
|
||||||
|
|
||||||
Log
|
|
||||||
---
|
|
||||||
|
|
||||||
### Log Filenames
|
|
||||||
|
|
||||||
TBD
|
|
||||||
|
|
||||||
|
|
||||||
### Log Messages
|
|
||||||
|
|
||||||
IRC messages are written to the log, one message per line.
|
|
||||||
Messages are translated to an easier-to-parse format:
|
|
||||||
|
|
||||||
timestamp fullname command sender forum [args...] :text
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
* `timestamp` is in Unix epoch time.
|
|
||||||
* `fullname` is the full name of the message origin (typically `nick!user@host.name`)
|
|
||||||
* `command` is the IRC command
|
|
||||||
* `sender` is the IRC name of the entity that sent the message
|
|
||||||
* `forum` is the IRC name of the audience of the message
|
|
||||||
* `args` are any additional arguments not otherwise covered
|
|
||||||
* `text` is the text of the message
|
|
||||||
|
|
||||||
`sender` and `forum` are provided in every message, for the convenience of the client.
|
|
||||||
A PRIVMSG to `sender` will make it back to whomever sent the message,
|
|
||||||
a PRIVMSG to `forum` will be seen by everyone in the audience.
|
|
||||||
|
|
||||||
For example, a "private message" will have `sender` equal to `forum`.
|
|
||||||
But a "channel message" will have `forum` set to the channel.
|
|
||||||
|
|
||||||
See `wirc.go` for details of each message type.
|
|
||||||
|
|
||||||
|
|
||||||
### Initial Messages
|
|
||||||
|
|
||||||
Each log file will contain the following initial messages,
|
|
||||||
to facilitate stateful clients:
|
|
||||||
|
|
||||||
TBD
|
|
|
@ -1,7 +0,0 @@
|
||||||
Todo list
|
|
||||||
=========
|
|
||||||
|
|
||||||
* One server instance for all networks
|
|
||||||
* One cgi for all networks?
|
|
||||||
* Make README suck less
|
|
||||||
* nickname name= attribute set to uhost
|
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
@ -1,87 +0,0 @@
|
||||||
package logfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logfile struct {
|
|
||||||
file *os.File
|
|
||||||
name string
|
|
||||||
nlines int
|
|
||||||
maxlines int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogfile(maxlines int) (*Logfile) {
|
|
||||||
return &Logfile{nil, "", 0, maxlines}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *Logfile) Close() {
|
|
||||||
if lf.file != nil {
|
|
||||||
lf.writeln("EXIT")
|
|
||||||
lf.file.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *Logfile) writeln(s string) error {
|
|
||||||
_, err := fmt.Fprintf(lf.file, "%d %s\n", time.Now().Unix(), s)
|
|
||||||
if err == nil {
|
|
||||||
lf.nlines += 1
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *Logfile) rotate() error {
|
|
||||||
fn := fmt.Sprintf("%s.log", time.Now().UTC().Format(time.RFC3339))
|
|
||||||
newf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if lf.file == nil {
|
|
||||||
// Set lf.file just so we can write out NEXTLOG.
|
|
||||||
// If this fails, that's okay
|
|
||||||
lf.file, _ = os.OpenFile("current", os.O_WRONLY|os.O_APPEND, 0666)
|
|
||||||
}
|
|
||||||
|
|
||||||
if lf.file != nil {
|
|
||||||
// Note location of new log
|
|
||||||
logmsg := fmt.Sprintf(". NEXTLOG %s", fn)
|
|
||||||
lf.writeln(logmsg)
|
|
||||||
|
|
||||||
// All done with the current log
|
|
||||||
lf.file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point to new log file
|
|
||||||
lf.file = newf
|
|
||||||
|
|
||||||
// Record symlink to new log
|
|
||||||
os.Remove("current")
|
|
||||||
os.Symlink(fn, "current")
|
|
||||||
|
|
||||||
logmsg := fmt.Sprintf(". PREVLOG %s", lf.name)
|
|
||||||
lf.writeln(logmsg)
|
|
||||||
|
|
||||||
lf.name = fn
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *Logfile) Log(s string) error {
|
|
||||||
if lf.file == nil {
|
|
||||||
lf.rotate()
|
|
||||||
}
|
|
||||||
|
|
||||||
err := lf.writeln(s)
|
|
||||||
if err == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if lf.nlines >= lf.maxlines {
|
|
||||||
return lf.rotate()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,169 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/go-fsnotify/fsnotify"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"bufio"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cgi"
|
|
||||||
"time"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
cgi.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
var NetworkDir string
|
|
||||||
|
|
||||||
func ReadString(fn string) string {
|
|
||||||
octets, err := ioutil.ReadFile(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(octets))
|
|
||||||
}
|
|
||||||
|
|
||||||
func tail(w http.ResponseWriter, filename string, pos int64) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
currentfn := path.Join(NetworkDir, "current")
|
|
||||||
if filename == "" {
|
|
||||||
filename, err = os.Readlink(currentfn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filepath := path.Join(NetworkDir, filename)
|
|
||||||
|
|
||||||
f, err := os.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer watcher.Close()
|
|
||||||
watcher.Add(filepath)
|
|
||||||
|
|
||||||
for {
|
|
||||||
printid := false
|
|
||||||
|
|
||||||
newpos, err := f.Seek(pos, 0)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if newpos != pos {
|
|
||||||
log.Fatal("Lost my position in the log, somehow (log truncated?)")
|
|
||||||
}
|
|
||||||
|
|
||||||
bf := bufio.NewScanner(f)
|
|
||||||
for bf.Scan() {
|
|
||||||
t := bf.Text()
|
|
||||||
pos += int64(len(t)) + 1 // XXX: this breaks if we ever see \r\n
|
|
||||||
|
|
||||||
parts := strings.Split(t, " ")
|
|
||||||
if (len(parts) >= 4) && (parts[2] == "NEXTLOG") {
|
|
||||||
watcher.Remove(filepath)
|
|
||||||
filename = parts[3]
|
|
||||||
filepath = path.Join(NetworkDir, filename)
|
|
||||||
f.Close()
|
|
||||||
f, err = os.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
watcher.Add(filepath)
|
|
||||||
pos = 0
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "data: %s\n", t)
|
|
||||||
printid = true
|
|
||||||
}
|
|
||||||
if printid {
|
|
||||||
_, err = fmt.Fprintf(w, "id: %s/%d\n\n", filename, pos)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case _ = <-watcher.Events:
|
|
||||||
// Somethin' happened!
|
|
||||||
case err := <-watcher.Errors:
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleCommand(w http.ResponseWriter, text string, target string) {
|
|
||||||
fn := path.Join(NetworkDir, fmt.Sprintf("outq/cgi.%d", time.Now().Unix()))
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(w, "NO: Cannot create outq file")
|
|
||||||
fmt.Fprintln(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(text, "/quote "):
|
|
||||||
fmt.Fprintln(f, text[7:])
|
|
||||||
case strings.HasPrefix(text, "/me "):
|
|
||||||
fmt.Fprintf(f, "PRIVMSG %s :\001ACTION %s\001\n", target, text[4:])
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(f, "PRIVMSG %s :%s\n", target, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(w, "OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
BaseDir := "networks"
|
|
||||||
DefaultDir := path.Join(BaseDir, "default")
|
|
||||||
NetworkDir = path.Join(BaseDir, r.FormValue("network"))
|
|
||||||
|
|
||||||
if path.Dir(DefaultDir) != path.Dir(NetworkDir) {
|
|
||||||
NetworkDir = DefaultDir
|
|
||||||
}
|
|
||||||
|
|
||||||
authtok := ReadString(path.Join(NetworkDir, "authtok"))
|
|
||||||
if r.FormValue("auth") != authtok {
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
fmt.Fprintln(w, "NO: Invalid authtok")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch r.FormValue("type") {
|
|
||||||
case "command":
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
handleCommand(w, r.Form.Get("text"), r.FormValue("target"))
|
|
||||||
default:
|
|
||||||
w.Header().Set("Content-Type", "text/event-stream")
|
|
||||||
parts := strings.Split(os.Getenv("HTTP_LAST_EVENT_ID"), "/")
|
|
||||||
if len(parts) == 2 {
|
|
||||||
filename := path.Base(parts[0])
|
|
||||||
pos, _ := strconv.ParseInt(parts[1], 0, 64)
|
|
||||||
tail(w, filename, pos)
|
|
||||||
} else {
|
|
||||||
tail(w, "", 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
h := Handler{}
|
|
||||||
if err := cgi.Serve(h); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
282
spongy/spongy.go
282
spongy/spongy.go
|
@ -1,282 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/tls"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"logfile"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Command string
|
|
||||||
FullSender string
|
|
||||||
Sender string
|
|
||||||
Forum string
|
|
||||||
Args []string
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
var running bool = true
|
|
||||||
var nick string
|
|
||||||
var gecos string
|
|
||||||
var maxlogsize uint
|
|
||||||
var logq chan Message
|
|
||||||
|
|
||||||
func isChannel(s string) bool {
|
|
||||||
if (s == "") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s[0] {
|
|
||||||
case '#', '&', '!', '+', '.', '-':
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Message) String() string {
|
|
||||||
args := strings.Join(m.Args, " ")
|
|
||||||
return fmt.Sprintf("%s %s %s %s %s :%s", m.FullSender, m.Command, m.Sender, m.Forum, args, m.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logLoop() {
|
|
||||||
logf := logfile.NewLogfile(int(maxlogsize))
|
|
||||||
defer logf.Close()
|
|
||||||
|
|
||||||
for m := range logq {
|
|
||||||
logf.Log(m.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nuhost(s string) (string, string, string) {
|
|
||||||
var parts []string
|
|
||||||
|
|
||||||
parts = strings.SplitN(s, "!", 2)
|
|
||||||
if len(parts) == 1 {
|
|
||||||
return s, "", ""
|
|
||||||
}
|
|
||||||
n := parts[0]
|
|
||||||
parts = strings.SplitN(parts[1], "@", 2)
|
|
||||||
if len(parts) == 1 {
|
|
||||||
return s, "", ""
|
|
||||||
}
|
|
||||||
return n, parts[0], parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect(host string, dotls bool) (net.Conn, error) {
|
|
||||||
if dotls {
|
|
||||||
config := &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
return tls.Dial("tcp", host, config)
|
|
||||||
} else {
|
|
||||||
return net.Dial("tcp", host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readLoop(conn net.Conn, inq chan<- string) {
|
|
||||||
scanner := bufio.NewScanner(conn)
|
|
||||||
for scanner.Scan() {
|
|
||||||
inq <- scanner.Text()
|
|
||||||
}
|
|
||||||
close(inq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeLoop(conn net.Conn, outq <-chan string) {
|
|
||||||
for v := range outq {
|
|
||||||
m, _ := parse(v)
|
|
||||||
logq <- m
|
|
||||||
fmt.Fprintln(conn, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(v string) (Message, error) {
|
|
||||||
var m Message
|
|
||||||
var parts []string
|
|
||||||
var lhs string
|
|
||||||
|
|
||||||
parts = strings.SplitN(v, " :", 2)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
lhs = parts[0]
|
|
||||||
m.Text = parts[1]
|
|
||||||
} else {
|
|
||||||
lhs = v
|
|
||||||
m.Text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
m.FullSender = "."
|
|
||||||
m.Forum = "."
|
|
||||||
m.Sender = "."
|
|
||||||
|
|
||||||
parts = strings.Split(lhs, " ")
|
|
||||||
if parts[0][0] == ':' {
|
|
||||||
m.FullSender = parts[0][1:]
|
|
||||||
parts = parts[1:]
|
|
||||||
|
|
||||||
n, u, _ := nuhost(m.FullSender)
|
|
||||||
if u != "" {
|
|
||||||
m.Sender = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Command = strings.ToUpper(parts[0])
|
|
||||||
switch m.Command {
|
|
||||||
case "PRIVMSG", "NOTICE":
|
|
||||||
switch {
|
|
||||||
case isChannel(parts[1]):
|
|
||||||
m.Forum = parts[1]
|
|
||||||
case m.FullSender == ".":
|
|
||||||
m.Forum = parts[1]
|
|
||||||
default:
|
|
||||||
m.Forum = m.Sender
|
|
||||||
}
|
|
||||||
case "PART", "MODE", "TOPIC", "KICK":
|
|
||||||
m.Forum = parts[1]
|
|
||||||
m.Args = parts[2:]
|
|
||||||
case "JOIN":
|
|
||||||
if len(parts) == 1 {
|
|
||||||
m.Forum = m.Text
|
|
||||||
m.Text = ""
|
|
||||||
} else {
|
|
||||||
m.Forum = parts[1]
|
|
||||||
}
|
|
||||||
case "INVITE":
|
|
||||||
if m.Text != "" {
|
|
||||||
m.Forum = m.Text
|
|
||||||
m.Text = ""
|
|
||||||
} else {
|
|
||||||
m.Forum = parts[2]
|
|
||||||
}
|
|
||||||
case "NICK":
|
|
||||||
if len(parts) > 1 {
|
|
||||||
m.Sender = parts[1]
|
|
||||||
m.Args = parts[2:]
|
|
||||||
} else {
|
|
||||||
m.Sender = m.Text
|
|
||||||
m.Text = ""
|
|
||||||
m.Args = parts[1:]
|
|
||||||
}
|
|
||||||
m.Forum = m.Sender
|
|
||||||
case "353":
|
|
||||||
m.Forum = parts[3]
|
|
||||||
default:
|
|
||||||
numeric, _ := strconv.Atoi(m.Command)
|
|
||||||
if numeric >= 300 {
|
|
||||||
if len(parts) > 2 {
|
|
||||||
m.Forum = parts[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.Args = parts[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dispatch(outq chan<- string, m Message) {
|
|
||||||
logq <- m
|
|
||||||
switch m.Command {
|
|
||||||
case "PING":
|
|
||||||
outq <- "PONG :" + m.Text
|
|
||||||
case "433":
|
|
||||||
nick = nick + "_"
|
|
||||||
outq <- fmt.Sprintf("NICK %s", nick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleInfile(path string, outq chan<- string) {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
os.Remove(path)
|
|
||||||
inf := bufio.NewScanner(f)
|
|
||||||
for inf.Scan() {
|
|
||||||
txt := inf.Text()
|
|
||||||
outq <- txt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func monitorDirectory(dirname string, dir *os.File, outq chan<- string) {
|
|
||||||
latest := time.Unix(0, 0)
|
|
||||||
for running {
|
|
||||||
fi, err := dir.Stat()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
current := fi.ModTime()
|
|
||||||
if current.After(latest) {
|
|
||||||
latest = current
|
|
||||||
dn, _ := dir.Readdirnames(0)
|
|
||||||
for _, fn := range dn {
|
|
||||||
path := dirname + string(os.PathSeparator) + fn
|
|
||||||
handleInfile(path, outq)
|
|
||||||
}
|
|
||||||
_, _ = dir.Seek(0, 0)
|
|
||||||
}
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] HOST:PORT\n", os.Args[0])
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
dotls := flag.Bool("notls", true, "Disable TLS security")
|
|
||||||
outqdir := flag.String("outq", "outq", "Output queue directory")
|
|
||||||
flag.UintVar(&maxlogsize, "logsize", 1000, "Log entries before rotating")
|
|
||||||
flag.StringVar(&gecos, "gecos", "Bob The Merry Slug", "Gecos entry (full name)")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
if flag.NArg() != 2 {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: must specify nickname and host")
|
|
||||||
os.Exit(69)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := os.Open(*outqdir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer dir.Close()
|
|
||||||
|
|
||||||
nick := flag.Arg(0)
|
|
||||||
host := flag.Arg(1)
|
|
||||||
|
|
||||||
conn, err := connect(host, *dotls)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
inq := make(chan string)
|
|
||||||
outq := make(chan string)
|
|
||||||
logq = make(chan Message)
|
|
||||||
go logLoop()
|
|
||||||
go readLoop(conn, inq)
|
|
||||||
go writeLoop(conn, outq)
|
|
||||||
go monitorDirectory(*outqdir, dir, outq)
|
|
||||||
|
|
||||||
outq <- fmt.Sprintf("NICK %s", nick)
|
|
||||||
outq <- fmt.Sprintf("USER %s %s %s: %s", nick, nick, nick, gecos)
|
|
||||||
for v := range inq {
|
|
||||||
p, err := parse(v)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dispatch(outq, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
running = false
|
|
||||||
|
|
||||||
close(outq)
|
|
||||||
close(logq)
|
|
||||||
}
|
|
Loading…
Reference in New Issue