Compare commits
16 commits
master
...
pkg-ircbot
Author | SHA1 | Date | |
---|---|---|---|
43d852f11d | |||
7a6f7b5cc0 | |||
6982def2c7 | |||
95ee759f75 | |||
253fd2f2f0 | |||
cbf4a3b256 | |||
049f2f669a | |||
a061b6e51a | |||
|
24c2712e71 | ||
|
d54bcec082 | ||
|
dff97b97a5 | ||
|
5e787f49ce | ||
|
9c8abab1e3 | ||
|
a1d2ed677c | ||
|
9c0917027b | ||
|
2343364eac |
17 changed files with 581 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
result
|
result
|
||||||
result-*
|
result-*
|
||||||
**/.direnv/
|
**/.direnv/
|
||||||
|
.data/
|
16
flake.lock
16
flake.lock
|
@ -396,6 +396,21 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nix-filter": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1661201956,
|
||||||
|
"narHash": "sha256-RizGJH/buaw9A2+fiBf9WnXYw4LZABB5kMAZIEE5/T8=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "nix-filter",
|
||||||
|
"rev": "3b821578685d661a10b563cba30b1861eec05748",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "nix-filter",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nix-super": {
|
"nix-super": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"lowdown-src": "lowdown-src",
|
"lowdown-src": "lowdown-src",
|
||||||
|
@ -559,6 +574,7 @@
|
||||||
"hercules-ci-effects": "hercules-ci-effects",
|
"hercules-ci-effects": "hercules-ci-effects",
|
||||||
"mms": "mms",
|
"mms": "mms",
|
||||||
"nar-serve": "nar-serve",
|
"nar-serve": "nar-serve",
|
||||||
|
"nix-filter": "nix-filter",
|
||||||
"nix-super": "nix-super",
|
"nix-super": "nix-super",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"repin-flake-compat": "repin-flake-compat",
|
"repin-flake-compat": "repin-flake-compat",
|
||||||
|
|
|
@ -167,6 +167,8 @@
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nix-filter.url = "github:numtide/nix-filter";
|
||||||
|
|
||||||
repin-flake-compat = {
|
repin-flake-compat = {
|
||||||
url = "github:edolstra/flake-compat";
|
url = "github:edolstra/flake-compat";
|
||||||
flake = false;
|
flake = false;
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
./modules/devshell.nix
|
./modules/devshell.nix
|
||||||
./build-support
|
./build-support
|
||||||
|
|
||||||
|
./servers/ircbot/project.nix
|
||||||
./websites/landing/project.nix
|
./websites/landing/project.nix
|
||||||
./websites/stop-using-nix-env/project.nix
|
./websites/stop-using-nix-env/project.nix
|
||||||
];
|
];
|
||||||
|
|
2
packages/servers/ircbot/.envrc
Normal file
2
packages/servers/ircbot/.envrc
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
source ../../build-support/activate-shell
|
||||||
|
nix_direnv_watch_file project.nix
|
1
packages/servers/ircbot/.gitignore
vendored
Normal file
1
packages/servers/ircbot/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/__pycache__
|
19
packages/servers/ircbot/LICENSE
Normal file
19
packages/servers/ircbot/LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2022 Tiago Carvalho
|
||||||
|
|
||||||
|
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.
|
4
packages/servers/ircbot/README
Normal file
4
packages/servers/ircbot/README
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
private-void-bot
|
||||||
|
================
|
||||||
|
|
||||||
|
IRC bot for the Private Void IRC network.
|
5
packages/servers/ircbot/config.json
Normal file
5
packages/servers/ircbot/config.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"port": 6668,
|
||||||
|
"tls": false
|
||||||
|
}
|
1
packages/servers/ircbot/hooks/.gitignore
vendored
Normal file
1
packages/servers/ircbot/hooks/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/__pycache__
|
7
packages/servers/ircbot/hooks/fistbump.py
Normal file
7
packages/servers/ircbot/hooks/fistbump.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import main
|
||||||
|
|
||||||
|
class EventHandler(main.EventHandler):
|
||||||
|
def on_message(bot, e):
|
||||||
|
if e.message == '.fistbump':
|
||||||
|
msg = f'vroooooooooooo fiiiist, {e.sender}! :^)'
|
||||||
|
bot.send_message(e.channel, msg)
|
7
packages/servers/ircbot/hooks/quit.py
Normal file
7
packages/servers/ircbot/hooks/quit.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import main
|
||||||
|
|
||||||
|
class EventHandler(main.EventHandler):
|
||||||
|
def on_message(bot, e):
|
||||||
|
if e.message == '.quit':
|
||||||
|
bot.send_message(e.channel, 'exiting...')
|
||||||
|
bot.emit('quit')
|
7
packages/servers/ircbot/hooks/reload.py
Normal file
7
packages/servers/ircbot/hooks/reload.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import main
|
||||||
|
|
||||||
|
class EventHandler(main.EventHandler):
|
||||||
|
def on_message(bot, e):
|
||||||
|
if e.message == '.reload':
|
||||||
|
bot.send_message(e.channel, 'reloading hooks...')
|
||||||
|
bot.emit('reload-hooks')
|
8
packages/servers/ircbot/hooks/roll.py
Normal file
8
packages/servers/ircbot/hooks/roll.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import main
|
||||||
|
import random
|
||||||
|
|
||||||
|
class EventHandler(main.EventHandler):
|
||||||
|
def on_message(bot, e):
|
||||||
|
if e.message == '.roll':
|
||||||
|
msg = f'{e.sender}: rolled a {random.randint(1, 6)}'
|
||||||
|
bot.send_message(e.channel, msg)
|
329
packages/servers/ircbot/justirc.py
Normal file
329
packages/servers/ircbot/justirc.py
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
# Copyright (c) 2015 Gökberk Yaltıraklı
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
|
_IRCPacket = namedtuple("IRCPacket", "prefix command arguments")
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_irc_packet(packet):
|
||||||
|
prefix = ""
|
||||||
|
command = ""
|
||||||
|
arguments = []
|
||||||
|
|
||||||
|
if packet.startswith(":"):
|
||||||
|
prefix = packet[1:].split(" ")[0]
|
||||||
|
packet = packet.split(" ", 1)[1]
|
||||||
|
|
||||||
|
if " " in packet:
|
||||||
|
if " :" in packet:
|
||||||
|
last_argument = packet.split(" :")[1]
|
||||||
|
packet = packet.split(" :")[0]
|
||||||
|
for splitted in packet.split(" "):
|
||||||
|
if not command:
|
||||||
|
command = splitted
|
||||||
|
else:
|
||||||
|
arguments.append(splitted)
|
||||||
|
arguments.append(last_argument)
|
||||||
|
else:
|
||||||
|
for splitted in packet.split(" "):
|
||||||
|
if not command:
|
||||||
|
command = splitted
|
||||||
|
else:
|
||||||
|
arguments.append(splitted)
|
||||||
|
else:
|
||||||
|
command = packet
|
||||||
|
|
||||||
|
return _IRCPacket(prefix, command, arguments)
|
||||||
|
|
||||||
|
|
||||||
|
_IRCPacket.parse = _parse_irc_packet
|
||||||
|
|
||||||
|
|
||||||
|
class EventEmitter:
|
||||||
|
def __init__(self):
|
||||||
|
self.handlers = defaultdict(lambda: [])
|
||||||
|
|
||||||
|
def add_listener(self, name, handler):
|
||||||
|
self.handlers[name].append(handler)
|
||||||
|
|
||||||
|
def remove_listener(self, name, handler):
|
||||||
|
self.handlers[name].remove(handler)
|
||||||
|
|
||||||
|
def emit(self, name, data=None):
|
||||||
|
"""Emit an event
|
||||||
|
|
||||||
|
This function emits an event to all listeners registered to it.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name : str
|
||||||
|
Event name. Case sensitive.
|
||||||
|
data
|
||||||
|
Event data. Can be any type and passed directly to the event
|
||||||
|
handlers.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for handler in list(self.handlers[name]):
|
||||||
|
handler(data)
|
||||||
|
|
||||||
|
def on(self, name):
|
||||||
|
"""
|
||||||
|
Decorate a function as an event handler.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name : str
|
||||||
|
The event name to handle
|
||||||
|
"""
|
||||||
|
|
||||||
|
def inner(func):
|
||||||
|
self.add_listener(name, func)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
# Event data types
|
||||||
|
_IRCEvent = namedtuple("IRCEvent", "bot")
|
||||||
|
_PacketEvent = namedtuple("PacketEvent", "bot packet")
|
||||||
|
_MessageEvent = namedtuple("MessageEvent", "bot channel sender message")
|
||||||
|
_JoinEvent = namedtuple("JoinEvent", "bot channel nick")
|
||||||
|
_PartEvent = namedtuple("PartEvent", "bot channel nick")
|
||||||
|
|
||||||
|
|
||||||
|
class IRCConnection(EventEmitter):
|
||||||
|
def __init__(self):
|
||||||
|
"""Create an IRC connection
|
||||||
|
|
||||||
|
After creating the object and adding all the event handlers, you need to
|
||||||
|
call .connect on it to actually connect to a server.
|
||||||
|
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.socket = None
|
||||||
|
|
||||||
|
self.nick = ""
|
||||||
|
|
||||||
|
def run_once(self):
|
||||||
|
"""Run one iteration of the IRC client.
|
||||||
|
|
||||||
|
This function is called in a loop by the run_loop function. It can be
|
||||||
|
called separately, but most of the time there is no need to do this.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
line = next(self.lines)
|
||||||
|
packet = _IRCPacket.parse(line)
|
||||||
|
sender = packet.prefix.split("!")[0]
|
||||||
|
|
||||||
|
ev = _PacketEvent(self, packet)
|
||||||
|
self.emit("packet", ev)
|
||||||
|
self.emit(f"packet_{packet.command}", ev)
|
||||||
|
|
||||||
|
if packet.command == "PRIVMSG":
|
||||||
|
channel = packet.arguments[0]
|
||||||
|
message = packet.arguments[1]
|
||||||
|
ev = _MessageEvent(self, channel, sender, message)
|
||||||
|
self.emit("message", ev)
|
||||||
|
self.emit(f"message_{channel}", ev)
|
||||||
|
self.emit(f"message_{sender}", ev)
|
||||||
|
|
||||||
|
if channel[0] == "#":
|
||||||
|
self.emit("message#", ev)
|
||||||
|
else:
|
||||||
|
self.emit("pm", ev)
|
||||||
|
elif packet.command == "PING":
|
||||||
|
# Handle a PING message
|
||||||
|
self.send_line("PONG :{}".format(packet.arguments[0]))
|
||||||
|
self.emit("ping", _IRCEvent(self))
|
||||||
|
elif packet.command == "433" or packet.command == "437":
|
||||||
|
# Command 433 is "Nick in use"
|
||||||
|
# Add underscore to the nick
|
||||||
|
|
||||||
|
self.set_nick("{}_".format(self.nick))
|
||||||
|
elif packet.command == "001":
|
||||||
|
self.emit("welcome", _IRCEvent(self))
|
||||||
|
elif packet.command == "JOIN":
|
||||||
|
ev = _JoinEvent(self, packet.arguments[0], sender)
|
||||||
|
self.emit("join", ev)
|
||||||
|
elif packet.command == "PART":
|
||||||
|
ev = _PartEvent(self, packet.arguments[0], sender)
|
||||||
|
self.emit("part", ev)
|
||||||
|
|
||||||
|
def run_loop(self):
|
||||||
|
"""Runs the main loop of the client
|
||||||
|
|
||||||
|
This function is usually called after you add all the callbacks and
|
||||||
|
connect to the server. It will block until the connection to the server
|
||||||
|
is broken.
|
||||||
|
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
self.run_once()
|
||||||
|
|
||||||
|
def _read_lines(self):
|
||||||
|
buff = ""
|
||||||
|
while True:
|
||||||
|
buff += self.socket.recv(1024).decode("utf-8", "replace")
|
||||||
|
while "\n" in buff:
|
||||||
|
line, buff = buff.split("\n", 1)
|
||||||
|
line = line.replace("\r", "")
|
||||||
|
yield line
|
||||||
|
|
||||||
|
def connect(self, server, port=6667, tls=False):
|
||||||
|
"""Connects to the IRC server
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
server : str
|
||||||
|
The server IP or domain to connect to
|
||||||
|
port : int
|
||||||
|
The server port to connect to
|
||||||
|
tls : bool
|
||||||
|
Enable the use of TLS
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.socket = socket.create_connection((server, port))
|
||||||
|
|
||||||
|
if tls:
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
|
context.check_hostname = False
|
||||||
|
context.verify_mode = ssl.CERT_NONE
|
||||||
|
context.minimum_version = ssl.TLSVersion.TLSv1_3
|
||||||
|
context.maximum_version = ssl.TLSVersion.TLSv1_3
|
||||||
|
self.socket = context.wrap_socket(self.socket)
|
||||||
|
|
||||||
|
self.lines = self._read_lines()
|
||||||
|
self.emit("connect", _IRCEvent(self))
|
||||||
|
|
||||||
|
def send_line(self, line):
|
||||||
|
"""Sends a line directly to the server.
|
||||||
|
|
||||||
|
This is a low-level function that can be used to implement functionality
|
||||||
|
that's not covered by this library. Almost all of the time, you should
|
||||||
|
have no need to use this function.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
line : str
|
||||||
|
The line to send to the server
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.socket.send(f"{line}\r\n".encode("utf-8"))
|
||||||
|
|
||||||
|
def send_message(self, to, message):
|
||||||
|
"""Sends a message to a user or a channel
|
||||||
|
|
||||||
|
This is the main method of interaction as an IRC bot or client. This
|
||||||
|
function results in a PRIVMSG packet to the server.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
to : str
|
||||||
|
The target of the message
|
||||||
|
message : str
|
||||||
|
The message content
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.send_line(f"PRIVMSG {to} :{message}")
|
||||||
|
|
||||||
|
def send_notice(self, to, message):
|
||||||
|
"""Send a notice message
|
||||||
|
|
||||||
|
Notice messages usually have special formatting on clients.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
to : str
|
||||||
|
The target of the message
|
||||||
|
message : str
|
||||||
|
The message content
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.send_line(f"NOTICE {to} :{message}")
|
||||||
|
|
||||||
|
def send_action_message(self, to, action):
|
||||||
|
"""Send an action message to a channel or user.
|
||||||
|
|
||||||
|
Action messages can have special formatting on clients and are usually
|
||||||
|
send like /me is happy
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
to : str
|
||||||
|
The target of the message. Can be a channel or a user.
|
||||||
|
action : str
|
||||||
|
The message content
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.send_message(to, f"\x01ACTION {action}\x01")
|
||||||
|
|
||||||
|
def join_channel(self, channel):
|
||||||
|
"""Join a channel
|
||||||
|
|
||||||
|
This function joins a given channel. After the channel is joined, the
|
||||||
|
"join" event is emitted with your nick.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
channel : str
|
||||||
|
The channel to join
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.send_line(f"JOIN {channel}")
|
||||||
|
|
||||||
|
def set_nick(self, nick):
|
||||||
|
"""Sets or changes your nick
|
||||||
|
|
||||||
|
This should be called before joining channels, but can be called at any
|
||||||
|
time afterwards. If the requested nick is not available, the library
|
||||||
|
will keep adding underscores until an available nick is found.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
nick : str
|
||||||
|
The nickname to use
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.nick = nick
|
||||||
|
self.send_line(f"NICK {nick}")
|
||||||
|
|
||||||
|
def send_user_packet(self, username):
|
||||||
|
"""Send a user packet
|
||||||
|
|
||||||
|
This should be sent after your nickname. It is displayed on the clients
|
||||||
|
when they view your details and look at "Real Name".
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
username : str
|
||||||
|
The name to set
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.send_line(f"USER {username} 0 * :{username}")
|
89
packages/servers/ircbot/main.py
Normal file
89
packages/servers/ircbot/main.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import justirc
|
||||||
|
import json
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
class EventHandler(object):
|
||||||
|
def on_message(bot, event):
|
||||||
|
...
|
||||||
|
|
||||||
|
def on_reload(bot):
|
||||||
|
...
|
||||||
|
|
||||||
|
def main():
|
||||||
|
default_config = dict(
|
||||||
|
debug=False,
|
||||||
|
nick='smith',
|
||||||
|
channel='#general',
|
||||||
|
server='irc.privatevoid.net',
|
||||||
|
port=6697,
|
||||||
|
tls=True,
|
||||||
|
)
|
||||||
|
config = dict()
|
||||||
|
if (config_file := os.getenv("IRCBOT_CONFIG")) and config_file != "":
|
||||||
|
with open(config_file) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
run_bot(default_config | config)
|
||||||
|
|
||||||
|
def shutdown_bot_hooks(bot):
|
||||||
|
for name, hook in bot.hooks:
|
||||||
|
try:
|
||||||
|
hook.EventHandler.on_reload(bot)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'exception running hook {name}: {e}')
|
||||||
|
|
||||||
|
def run_bot(c):
|
||||||
|
bot = justirc.IRCConnection()
|
||||||
|
|
||||||
|
bot.db = () # TODO: store a database handle here
|
||||||
|
bot.hooks = [] # storage for all bot hooks
|
||||||
|
|
||||||
|
if c['debug']:
|
||||||
|
@bot.on('packet')
|
||||||
|
def new_packet(e):
|
||||||
|
print(e.packet)
|
||||||
|
|
||||||
|
@bot.on('reload-hooks')
|
||||||
|
def reload_hooks(e):
|
||||||
|
shutdown_bot_hooks(bot)
|
||||||
|
bot.hooks.clear()
|
||||||
|
|
||||||
|
for path in filter(lambda h: h[-3:] == '.py', os.listdir('hooks')):
|
||||||
|
name = '.'.join(['hooks', path[:-3]])
|
||||||
|
if name in sys.modules.keys():
|
||||||
|
del sys.modules[name]
|
||||||
|
try:
|
||||||
|
mod = importlib.import_module(name, package=name)
|
||||||
|
bot.hooks.append((name, mod))
|
||||||
|
except Exception as e:
|
||||||
|
print(f'failed to load hook {name}: {e}')
|
||||||
|
|
||||||
|
@bot.on('quit')
|
||||||
|
def quit(e):
|
||||||
|
shutdown_bot_hooks(bot)
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
@bot.on('connect')
|
||||||
|
def connect(e):
|
||||||
|
bot.send_line(f'NICK {c["nick"]}')
|
||||||
|
bot.send_line(f'USER {c["nick"]} 8 * {c["nick"]}')
|
||||||
|
bot.emit('reload-hooks')
|
||||||
|
|
||||||
|
@bot.on('welcome')
|
||||||
|
def welcome(e):
|
||||||
|
bot.join_channel(c['channel'])
|
||||||
|
|
||||||
|
@bot.on('message')
|
||||||
|
def message(e):
|
||||||
|
for name, hook in bot.hooks:
|
||||||
|
try:
|
||||||
|
hook.EventHandler.on_message(bot, e)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'exception running hook {name}: {e}')
|
||||||
|
|
||||||
|
bot.connect(c['server'], port=c['port'], tls=c['tls'])
|
||||||
|
bot.run_loop()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
82
packages/servers/ircbot/project.nix
Normal file
82
packages/servers/ircbot/project.nix
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
{ inputs, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
python = pkgs.python3;
|
||||||
|
procfile = pkgs.writeText "Procfile" ''
|
||||||
|
ircd: ${pkgs.ngircd}/bin/ngircd --config ${ircdConfig} --nodaemon
|
||||||
|
bot: ${python.interpreter} main.py
|
||||||
|
'';
|
||||||
|
|
||||||
|
ircdConfig = pkgs.writeText "ngircd.conf" ''
|
||||||
|
[Global]
|
||||||
|
Name = ircbot-dev.local
|
||||||
|
Info = IRC Bot Development
|
||||||
|
Network = IRCBotDev
|
||||||
|
Listen = 127.0.0.1
|
||||||
|
Ports = 6668
|
||||||
|
|
||||||
|
[Options]
|
||||||
|
Ident = no
|
||||||
|
PAM = no
|
||||||
|
AllowedChannelTypes = #
|
||||||
|
OperCanUseMode = yes
|
||||||
|
OperChanPAutoOp = yes
|
||||||
|
|
||||||
|
[Channel]
|
||||||
|
Name = #general
|
||||||
|
Topic = General discussions
|
||||||
|
|
||||||
|
[Operator]
|
||||||
|
Name = op
|
||||||
|
Password = op
|
||||||
|
'';
|
||||||
|
|
||||||
|
botConfig = pkgs.writeText "ircbot-config.json" (builtins.toJSON {
|
||||||
|
server = "127.0.0.1";
|
||||||
|
port = 6668;
|
||||||
|
tls = false;
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
projectShells.ircbot = {
|
||||||
|
commands = {
|
||||||
|
irssi-dev = {
|
||||||
|
help = "Irssi for development";
|
||||||
|
command = "exec ${pkgs.irssi}/bin/irssi --config=$PRJ_DATA_DIR/irssi-config --home=$PRJ_DATA_DIR/irssi-home -c 127.0.0.1 -p 6668 \"$@\"";
|
||||||
|
};
|
||||||
|
svc = {
|
||||||
|
help = "goreman with development services";
|
||||||
|
command = "exec ${pkgs.goreman}/bin/goreman -f ${procfile} -set-ports=false \"$@\"";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
tools = [
|
||||||
|
python
|
||||||
|
];
|
||||||
|
env.IRCBOT_CONFIG.value = botConfig;
|
||||||
|
};
|
||||||
|
packages.ircbot = with pkgs; stdenvNoCC.mkDerivation {
|
||||||
|
pname = "ircbot";
|
||||||
|
version = "0.0.0";
|
||||||
|
|
||||||
|
src = with inputs.nix-filter.lib; filter {
|
||||||
|
root = ./.;
|
||||||
|
include = [
|
||||||
|
(matchExt "py")
|
||||||
|
(inDirectory "hooks")
|
||||||
|
];
|
||||||
|
};
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin $out/lib
|
||||||
|
|
||||||
|
cp -r $src/ $out/lib/ircbot
|
||||||
|
|
||||||
|
cat <<EOF >$out/bin/ircbot
|
||||||
|
#!${runtimeShell}
|
||||||
|
export PYTHONNOUSERSITE=true
|
||||||
|
export PYTHONPATH="$out/lib/ircbot"
|
||||||
|
exec ${python.interpreter} $out/lib/ircbot/main.py "$@"
|
||||||
|
EOF
|
||||||
|
chmod +x $out/bin/ircbot
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue