Compare commits

...

12 Commits

20 changed files with 331 additions and 81 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
raven.mono
TheRaven-*.pkg.tar.zst
**.pkg.tar.zst
pkg/
src/
.config
wiki/

58
LICENSE
View File

@ -1,27 +1,31 @@
# http://www.wtfpl.net/about/
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
ANINIX ADDENDUM
Trademark Pending 2017 (https://aninix.net/irc/)
The "AniNIX" name and |> logo is trademark-pending as of 2017. All
AniNIX materials can be reproduced and re-used, though you must
contact the admins of the network to get written permission to use
the AniNIX name.
Attribution is appreciated for other materials but not legally
required or necessary.
# http://www.wtfpl.net/about/
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
ANINIX ADDENDUM
Trademark 2017 (https://aninix.net/)
The "AniNIX" name and |> logo are trademarked as of 2017/11/21.
AniNIX materials may be reproduced and re-used (though you must
contact the admins of the network to get written permission to use
the AniNIX name or logo) so long as such reproduction or re-use
does not inhibit the original AniNIX use of the same.
Attribution is appreciated for other materials but not legally
required or necessary.
"AniNIX" trademark serial: 87177883
|> Logo trademark serial: 87177887

View File

@ -1,17 +1,14 @@
CONFDIR = ${pkgdir}/usr/local/etc/TheRaven
INSTALLDIR = ${pkgdir}/opt/aninix/TheRaven
SCRIPTS != ls -1 *.bash
compile: clean ./chatbot-support.bash ./math-support.bash
(mcs -out:raven.mono /opt/aninix/Uniglot/CSharp/*.csharp *.csharp Raven.csharp 2>&1 | grep -v CS2002); printf ""
clean:
for i in raven.mono; do if [ -f $$i ]; then rm $$i; fi; done
test: compile
cd ./sample-confs; mono ../raven.mono -c sample.conf -v -h
install: compile
mkdir -p ${pkgdir}/opt
cp raven.mono ${pkgdir}/opt/raven.mono
source $$PWD/installscript && pre_install || true
install -o raven -g raven -m 0750 -d ${INSTALLDIR}
for script in ${SCRIPTS} raven.mono; do install -o raven -g raven -m 0640 $$script ${INSTALLDIR}; done
if [ ! -d ${CONFDIR} ]; then mkdir -p ${CONFDIR}; cp sample-confs/* ${CONFDIR}; fi
# Hook to deprivilege bot
make checkperm
@ -19,15 +16,26 @@ install: compile
mkdir -p ${pkgdir}/usr/lib/systemd/system/
cp ./raven.service ${pkgdir}/usr/lib/systemd/system/raven.service
reverse: ${pkgdir}/usr/lib/systemd/system/raven.service
cp ${pkgdir}/usr/lib/systemd/system/raven.service .
clean:
for i in raven.mono; do if [ -f $$i ]; then rm $$i; fi; done
checkperm: ${pkgdir}/opt/raven.mono
if ! id raven; then sudo useradd raven; echo User raven added; fi
chown -R raven:raven ${pkgdir}/opt/raven.mono ${CONFDIR}*
chmod 0600 ${pkgdir}/opt/raven.mono ${CONFDIR}*/*
chmod 0700 ${CONFDIR}*
uninstall:
rm -Rf ${INSTALLDIR} ${CONFDIR}
systemctl disable --now raven.service
rm /usr/lib/systemd/system/raven.service
test: compile
cd ./sample-confs; mono ../raven.mono -c sample.conf -v -h
checkperm: ${INSTALLDIR}/raven.mono
chown -R raven:raven ${INSTALLDIR} ${CONFDIR}
chmod 0600 ${INSTALLDIR}/* ${CONFDIR}/*
chmod 0700 ${CONFDIR} ${INSTALLDIR}
diff:
diff:
diff ./raven.service ${pkgdir}/usr/lib/systemd/system/raven.service
diff ./sample.conf ${pkgdir}/usr/local/etc/TheRaven/raven.conf
reverse: /usr/lib/systemd/system/raven.service /usr/local/etc/TheRaven
cp /usr/lib/systemd/system/raven.service .
cp ${INSTALLDIR}/*.bash .

View File

@ -17,10 +17,10 @@ license=('custom')
groups=()
provides=("${pkgname}")
conflicts=()
replaces=("${pkgname,,}", "aninix-${pkgname,,}")
replaces=("${pkgname,,}" "aninix-${pkgname,,}")
backup=()
options=()
install=
install=installscript
changelog=
source=()
noextract=()

View File

@ -1,16 +1,37 @@
This is a simple IRCbot for the AniNIX's operations.
This is a simple IRC bot for the AniNIX's operations.
# Etymology
The Raven is a named after the [common raven](https://en.wikipedia.org/wiki/Raven) by [DarkFeather](/DarkFeather). There's a lot of history there, but it's effectively that user's namesake & avatar.
# Relevant Files and Software
The Raven's source code can be compiled into a deployable agent. To enable this bot, just install the package from [the AniNIX repository](https://maat.aninix.net) or run the following:
# Usage
To enable this bot, just install the package from [the AniNIX repository](https://maat.aninix.net) or run the following:
```
make
sudo make install
sudo systemctl start raven.service
sudo systemctl enable raven.service
sudo systemctl enable --now raven.service
```
The configuration lives in `/usr/local/etc/TheRaven`. Unique files are provided for logins, search functions, help text, CrowFacts and magic8 functions, etc.
TheRaven is dependent on /bin/bash and [wget](https://wiki.archlinux.org/index.php/Wget) for the TinyURL features. It is dependent on the [Mono](https://wiki.archlinux.org/index.php/Mono) package for compiling on Linux.
TheRaven also expects an OS script, `api-keys`, that will return a TinyURL API key when run with the `tinyurl` parameter.
# Available Clients
There are no direct clients -- connect to [IRC](https://irc.aninix.net) or our Discord bridge. Use `r.help` in any channel where TheRaven is present to find out what it can do, or PM it directly.
# Equivalents or Competition
[Sopel](https://sopel.chat/) is an equivalent, maintained IRC bot. We maintain our own for the use case of testing code development.
Various Discord bots also perform the same function.
# Functionality
This IRCbot has some simple commands that can be found by most users with `r.help` in whatever channel the bot has joined.
This IRC bot has some simple commands that can be found by most users with `r.help` in whatever channel the bot has joined.
Administrative functions are controlled by the access lists and can be found with `r.adminhelp`.

View File

@ -26,7 +26,7 @@ namespace AniNIX.TheRaven {
public List<String> channels; //This is the list of channels to join
public List<String> whitelist; //This is the list of admin users.
public List<String> blacklist; // This is the list of blocked people.
public String helpText = "Available commands are r.d <dice test>, r.heartbeat, r.magic8, r.math <math problem>, r.msg <memo for admin>, r.raven, r.searches, r.tinyurl <url>, r.wikidiff \"one\" \"other\", and r.uptime";
public String helpText = "Available commands are r.d <dice test>, r.ip, r.magic8, r.math <math problem>, r.msg <memo for admin>, r.raven, r.searches, r.tinyurl <url>, r.wikidiff \"one\" \"other\", and r.uptime";
// This is the text to send when people ask for help -- this is configurable to allow for skinning
public List<String> searches; //These are the searches
public String searchesIndex; //This is the helptext for the searches
@ -75,11 +75,12 @@ namespace AniNIX.TheRaven {
ReportMessage.Log(Verbosity.Verbose,"Reading login info");
try {
Dictionary<String,String> loginDefaults = conf.ReadSection("Login");
Dictionary<String,String> apiDefaults = conf.ReadSection("API");
this.Host = loginDefaults["host"];
this.Port = Int32.Parse(loginDefaults["port"]);
this.Nick = loginDefaults["username"];
this._nickServPass = loginDefaults["password"];
this._netListener = new RavenNetListener(loginDefaults["password"]);
this._netListener = new RavenNetListener(apiDefaults["password"],Int32.Parse(apiDefaults["port"]));
channels=new List<String>();
foreach (String channel in conf.ReadSectionLines("Rooms")) {
@ -268,7 +269,7 @@ namespace AniNIX.TheRaven {
/// Read from the connection, and for each message act appropriately.
/// </summary>
public void LoopOnTraffic() {
ReportMessage.Log(Verbosity.Verbose,"Looping on trafffic now! We're useful!");
ReportMessage.Log(Verbosity.Verbose,"Looping on traffic now! We're useful!");
// Start a network listener to allow relaying traffic via ncat into IRCd.
this._netListener.NetListener(this._connection);
// Loop on main connect to ircd
@ -282,7 +283,7 @@ namespace AniNIX.TheRaven {
if (response.msgCode.Equals("PRIVMSG") && !String.IsNullOrWhiteSpace(response.message) && (response.target.Equals(Nick) || response.message.StartsWith(String.Format("{0}:",Nick)) || response.message.EndsWith(String.Format("{0}!",Nick)) || response.message.EndsWith(String.Format("{0}?",Nick)) || response.message.EndsWith(String.Format("{0}.",Nick)) || response.message.EndsWith(String.Format("{0}",Nick)))) {
IRCClientMessage send = new IRCClientMessage();
try {
String aliceResponse = ExecuteCommand.Run(String.Format("bash ./chatbot-support.bash \"{0}\" {1}",response.message.Replace("'","").Replace("\"","").Split('\n')[0].Trim(),Nick)).Trim();
String aliceResponse = ExecuteCommand.Run(String.Format("bash /opt/aninix/TheRaven/chatbot-support.bash \"{0}\" {1}",response.message.Replace("'","").Replace("\"","").Split('\n')[0].Trim(),Nick)).Trim();
if (String.IsNullOrWhiteSpace(aliceResponse)) throw new Exception("No response from ALICE chatbot service");
send.PrivMsg(aliceResponse,(response.target.Equals(Nick))?response.user:response.target);
} catch (Exception e) {

View File

@ -84,7 +84,7 @@ namespace AniNIX.TheRaven {
if (bySpace.Length < 2) {
} else {
send.PrivMsg(ExecuteCommand.Run(String.Format("/bin/bash /usr/local/src/TheRaven/math-support.bash \"{0}\"",incoming.message.Replace("r.math ","").Replace("'","").Replace("\"",""))),(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target);
send.PrivMsg(ExecuteCommand.Run(String.Format("/bin/bash /opt/aninix/TheRaven/math-support.bash \"{0}\"",incoming.message.Replace("r.math ","").Replace("'","").Replace("\"",""))),(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target);
}
connection.Write(send);
return;
@ -126,17 +126,9 @@ namespace AniNIX.TheRaven {
}
connection.Write(send);
return;
case "r.heartbeat":
try {
String[] byLine = ExecuteCommand.Run("heartbeat-client").Split('\n');
for (int i = 0; i < byLine.Length; i++) {
send.PrivMsg(byLine[i],incoming.user);
connection.Write(send);
}
} catch (Exception e) {
e.ToString();
send.PrivMsg("Can't get heartbeat",incoming.user);
}
case "r.ip":
send.PrivMsg(ExecuteCommand.Run("/bin/bash /opt/aninix/TheRaven/ip.bash"),(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target);
connection.Write(send);
return;
case "r.searches":
send.PrivMsg(theRaven.searchesIndex,(incoming.target.Equals(theRaven.Nick))?incoming.user:incoming.target);

View File

@ -13,6 +13,7 @@ namespace AniNIX.TheRaven {
public class RavenNetListener {
private String _key;
private int _port;
private Connection _ircdConnection;
private void _RavenSend(Connection ircdConnection, String channel, String message) {
@ -26,9 +27,9 @@ namespace AniNIX.TheRaven {
while (true) {
try
{
// Open a new listener on localhost port 9501
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
TcpListener listener = new TcpListener(ipAddress, 9501);
// Open a new listener on port
IPAddress ipAddress = IPAddress.Parse("0.0.0.0");
TcpListener listener = new TcpListener(ipAddress, this._port );
listener.Start();
// Accept all connections
while (true)
@ -40,6 +41,7 @@ namespace AniNIX.TheRaven {
string received = "";
for (int i = 0; i < size; i++)
received += Convert.ToChar(data[i]);
ReportMessage.Log(Verbosity.Verbose,String.Format("RavenNetListener received: [{0}]",received));
String[] bySpace = received.Split(' ');
// If the key matches, ...
if (this._key.Equals(bySpace[0])) {
@ -73,8 +75,9 @@ namespace AniNIX.TheRaven {
ReportMessage.Log(Verbosity.Verbose,"Started.");
}
public RavenNetListener(String key) {
public RavenNetListener(String key, int port) {
this._key = key;
this._port = port;
}
}
}

8
installscript Normal file
View File

@ -0,0 +1,8 @@
pre_install() {
groupadd raven &>/dev/null
useradd -g raven -s /bin/false raven &> /dev/null
}
pre_remove() {
getent passwd raven &>/dev/null && userdel raven &> /dev/null
}

View File

@ -1,2 +1,2 @@
#!/bin/bash
lynx -connect_timeout=3 -read_timeout=3 --dump 'http://m.wolframalpha.com/input/?i='"$(echo $1 | sed 's/+/%2B/g' | tr ' ' '+')"'&x=0&y=0' | cat -n | egrep '^ 21' | head -n 1 | xargs | sed 's/^21 //'
lynx -connect_timeout=3 -read_timeout=3 --dump 'http://m.wolframalpha.com/input/?i='"$(echo $1 | sed 's/+/%2B/g' | tr ' ' '+')"'&x=0&y=0' | cat -n | grep -E '^ 21' | head -n 1 | xargs | sed 's/^21 //'

View File

@ -4,7 +4,7 @@ After=network.target
[Service]
WorkingDirectory=/usr/local/etc/TheRaven
ExecStart=/usr/bin/mono /opt/raven.mono -c raven.conf
ExecStart=/usr/bin/mono /opt/aninix/TheRaven/raven.mono -c raven.conf
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always

33
ravensend/Makefile Normal file
View File

@ -0,0 +1,33 @@
CONFDIR = ${pkgdir}/usr/local/etc/TheRaven
LIST = ravensend ravensend-daemon
compile:
@echo Nothing to do.
clean:
rm -Rf pkg src
test: compile
./ravensend -h
install: compile
mkdir -p ${pkgdir}/usr/local/bin
for i in ${LIST}; do install -m 0755 -o raven -g raven $$i ${pkgdir}/usr/local/bin; done
# Hook for Systemd
mkdir -p ${pkgdir}/usr/lib/systemd/system/
install -m 0644 -o raven -g raven ./ravensend-daemon.service ${pkgdir}/usr/lib/systemd/system/
mkdir -p ${pkgdir}/usr/local/etc/
install -m 0640 -o raven -g raven ./ravensend-daemon.yml ${pkgdir}/usr/local/etc/
diff:
for i in ${LIST}; do diff /usr/local/bin/$$i $$i; done
diff ${pkgdir}/usr/lib/systemd/system/ravensend-daemon.service ./ravensend-daemon.service
reverse: ${pkgdir}/usr/lib/systemd/system/ravensend-daemon.service
cp ${pkgdir}/usr/lib/systemd/system/ravensend-daemon.service .
for i in ${LIST}; do cp ${pkgdir}/usr/local/bin/$$i .; done
checkperm: ${pkgdir}/opt/raven.mono
for i in ${LIST}; do chown -R raven: ${pkgdir}/usr/local/bin/$$i; chmod 0755 ${pkgdir}/usr/local/bin/$$i; done
chown raven: ${pkgdir}/usr/lib/systemd/system/ravensend-daemon.service
chmod 0644 ${pkgdir}/usr/lib/systemd/system/ravensend-daemon.service

47
ravensend/PKGBUILD Normal file
View File

@ -0,0 +1,47 @@
# Maintainer: DarkFeather <ircs://aninix.net:6697/darkfeather>
depends=('python' 'python-yaml' 'python-flask' 'bash' 'nmap>=7.91-1' 'TheRaven')
makedepends=('make>=4.2' 'Uniglot')
checkdepends=()
optdepends=()
pkgname="ravensend"
pkgver="$(git describe --tag --abbrev=0)"."$(git rev-parse --short HEAD)"
pkgrel=1
pkgrel() {
echo $(( `git log "$(git describe --tag --abbrev=0)"..HEAD | grep -c commit` + 1 ))
}
epoch="$(git log | grep -c commit)"
pkgdesc="$(head -n 1 README.md)"
arch=("x86_64")
url="$(git config remote.origin.url | sed 's/.git$//')"
license=('custom')
groups=()
provides=("${pkgname}")
conflicts=()
replaces=("${pkgname,,}" "aninix-${pkgname,,}")
backup=()
options=()
install=
changelog=
source=()
noextract=()
md5sums=()
validpgpkeys=()
prepare() {
git pull
}
build() {
make -C ..
}
check() {
chmod -R u+r ../pkg
make -C .. test
}
package() {
export pkgdir="${pkgdir}"
make -C .. install
install -D -m644 ../../LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}

15
ravensend/README.md Normal file
View File

@ -0,0 +1,15 @@
This package provides a couple files that will allow you to send messages via TheRaven to an IRC daemon.
# ravensend
This CLI allows you to send a message to a channel using TheRaven. Simplest invocation is as below:
```
ravensend -c '#thechannel' -f ./the.conf -m 'some message'
```
Assuming TheRaven sits in #thechannel and the user has the rights to read the.conf, the message should go through.
# ravensend-daemon
This daemon allows you to translate webhook inputs from services like Graylog to be sent by TheRaven.

View File

@ -1,13 +1,20 @@
#!/bin/bash
# TODO Add comments
# File: ravensend
#
# Description: This file sends a message to the Raven API
#
# Package: AniNIX/ravensend
# Copyright: WTFPL
#
# Author: DarkFeather <ircs://aninix.net:6697/DarkFeather>
# Default variables
unset channel
unset message
unset passphrase
export file="/usr/local/etc/TheRaven/raven.conf"
export port="8373"
unset port
export host="localhost"
# Show help
@ -17,8 +24,15 @@ function usage() {
echo "Passphrase is pulled from Raven config, set to ${file}"
}
function getAPIConfigValue() {
# Get the api config value
# param attr: the attribute to look for
# returns: the attribute's value
grep -A 10 -E '^\[ API \]$' "$file" | grep -m 1 -E '^'"$1"'=' | sed 's/^'"$1"'=//'
}
# Parse arguments
while getopts 'c:hm:p:P:t:v' OPTION; do
while getopts 'c:f:hm:p:t:v' OPTION; do
case "$OPTION" in
c) channel="${OPTARG}" ;;
f) file="${OPTARG}" ;;
@ -30,6 +44,9 @@ while getopts 'c:hm:p:P:t:v' OPTION; do
*) usage; exit 1 ;;
esac
done
if [ -z "$port" ]; then
port="$(getAPIConfigValue port)"
fi
# Check inputs.
if [ -z "$channel" ] || [ -z "$message" ] || [ -z "$file" ] || [ -z "$port" ] || [ -z "$host" ]; then
@ -38,11 +55,12 @@ if [ -z "$channel" ] || [ -z "$message" ] || [ -z "$file" ] || [ -z "$port" ] ||
fi
# Try to get the passphrase.
passphrase="$(egrep -m 1 '^password=' "$file" | sed 's/^password=//')"
passphrase="$(getAPIConfigValue password)"
if [ -z "$passphrase" ]; then
echo Couldn\'t identify passphrase.
exit 2;
fi
# Send format to socket
printf "%s %s %s" "$passphrase" "$channel" "$message" | ncat "$host" "$port"

41
ravensend/ravensend-daemon Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# File: ravensend-daemon
#
# Description: This daemon proxies Graylog-style webhooks to TheRaven
#
# Package: AniNIX/ravensend
# Copyright: WTFPL
#
# Author: DarkFeather <ircs://aninix.net:6697/DarkFeather>
# Thanks to https://towardsdatascience.com/intro-to-webhooks-and-how-to-receive-them-with-python-d5f6dd634476 for the tutorial
import socket
import yaml
from flask import Flask,request,json
app = Flask(__name__)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
configvars = []
@app.route('/',methods=['POST'])
def hello():
data = request.json
notification = data["event"]["message"] + ' at ' + data["event"]["timestamp"] + ' -- ' + data["event"]["key"]
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.connect((configvars["server"],int(configvars["port"])))
s.settimeout(1)
mesg = configvars["password"] + ' #' + configvars["channel"] + ' ' + notification
s.send(mesg.encode())
except:
print('Could not send to TheRaven')
return data
if __name__ == '__main__':
config = open('./ravensend-daemon.yml','r')
configvars = yaml.safe_load(config)
print(configvars)
config.close()
app.run(host='0.0.0.0',port=configvars["webhookport"],debug=False)

View File

@ -0,0 +1,15 @@
[Unit]
Description=AniNIX/ravensend Daemon
After=network.target raven.service
[Service]
ExecStart=/usr/local/bin/ravensend-daemon
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always
User=raven
Group=raven
WorkingDirectory=/usr/local/etc/
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,5 @@
channel: test123
password: sample
port: 9902
server: 127.0.0.1
webhookport: 8374

34
ravensend/sample.conf Normal file
View File

@ -0,0 +1,34 @@
[ Login ]
host=localhost
port=6667
username=TheRavenTest
password=dumb
[ API ]
port=9902
password=sample
[ Whitelist ]
DarkFeather
[ Blacklist ]
[ Notifications ]
[ Rooms ]
test123
[ Searches ]
r.google|http://google.com/search?q=|+|Google
r.image|http://images.google.com/search?tbm=isch&q=|+|Google Images
r.wiki|http://en.wikipedia.org/wiki/|_|Wikipedia
r.sound|http://www.soundcloud.com/search?q=|%20|Soundcloud
r.dict|http://www.merriam-webster.com/dictionary/|+|Dictionary
r.tropes|http://tvtropes.org/pmwiki/search_result.php?cx=partner-pub-6610802604051523%3Aamzitfn8e7v&cof=FORID%3A10&ie=ISO-8859-1&siteurl=&ref=&ss=&siteurl=tvtropes.org%2F&ref=www.google.com%2F&ss=5135j1581675j28&q=|+|Tropes
r.yt|https://www.youtube.com/results?search_query=|+|YouTube
r.urban|http://www.urbandictionary.com/define.php?term=|+|Urban Dictionary
r.man|http://www.die.net/search/?q=|+|Man-page
r.hoogle|https://www.haskell.org/hoogle/?hoogle=|+|Hoogle
r.so|http://stackoverflow.com/search?q=|+|Stack Overflow
r.aninix|https://aninix.net/|_|AniNIX
r.map|https://www.google.com/maps/search/|+|Google Maps

View File

@ -1,18 +1,22 @@
[ Login ]
host=localhost
port=6667
username=TheRaven
username=TheRavenTest
password=password
[ API ]
port=9902
password=sample
[ Whitelist ]
Admin
DarkFeather
[ Blacklist ]
[ Notifications ]
[ Rooms ]
TheRaven
test123
[ Searches ]
r.google|http://google.com/search?q=|+|Google