summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Yu <andrew@andrewyu.org>2022-08-11 20:09:33 +0800
committerAndrew Yu <andrew@andrewyu.org>2022-08-11 20:09:33 +0800
commitfed93dcd209b4c147a87dbb59770024767e3da99 (patch)
tree977e306312eb0c5089cb69af6792849635e386c9
parenta870b052c1a1aeb68758fe097abb56826201a53c (diff)
downloadirc-mod-bot-fed93dcd209b4c147a87dbb59770024767e3da99.tar.gz
irc-mod-bot-fed93dcd209b4c147a87dbb59770024767e3da99.zip
Now able to match hostmasks (ft ProgVal)
-rw-r--r--LICENSE37
-rw-r--r--bot.py96
2 files changed, 109 insertions, 24 deletions
diff --git a/LICENSE b/LICENSE
index be3f7b2..38223be 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,5 @@
+AGPLv3
+
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
@@ -659,3 +661,38 @@ specific requirements.
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
+
+--
+
+3BSD
+
+Copyright (c) 2002-2009 Jeremiah Fincher and others
+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 the author of this software nor the name of
+ contributors to this software 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.
+
+Portions of the included source code are copyright by its original author(s)
+and remain subject to its associated license.
+
+--
diff --git a/bot.py b/bot.py
index 899a5dd..84b1775 100644
--- a/bot.py
+++ b/bot.py
@@ -4,19 +4,29 @@
# Copyright (C) 2022 Andrew Yu <https://www.andrewyu.org/>
# Copyright (C) 2022 luk3yx <https://luk3yx.github.io/>
#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
#
# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <https://www.gnu.org/licenses/>.
+# along with this program in a file called LICENSE, under the heading "AGPLv3"
+# until a single line with two dashes ("--") and no more. If not, see
+# <https://www.gnu.org/licenses/>.
#
+# The function match_hostmask is under the license described from the heading
+# "3BSD" until a single line two dashes and no more in the LICENSE file, with
+# the following copyright notice:
+#
+# Copyright (c) 2022 Andrew Yu <https://www.andrewyu.org/>
+# Copyright (c) 2002-2009 Jeremiah Fincher and others
+#
+
from __future__ import annotations
from dataclasses import dataclass, field
@@ -24,6 +34,7 @@ from typing import Optional, Callable, Iterator
import socket
import sys
import base64
+import re
from config import SERVER, PORT, NICK, IDENT, GECOS, CHANNELS, PREFIX, LOGIN, PASSWORD
@@ -39,6 +50,7 @@ class User:
on_server: Optional[bytes] = None
in_channels: list[Channel] = field(default_factory=list)
+
@dataclass
class State:
initiated: bool = False
@@ -49,9 +61,9 @@ class State:
@dataclass
class Channel:
name: bytes
- joined: Optional[
- bool
- ] = None # None: didn't even talk about this to the server, False: not joined, True: joined
+ # joined = None: didn't even talk about this to the server, False: not joined, True: joined
+ joined: Optional[bool] = None
+ authorized: list[tuple[bytes, bytes, bytes]] = field(default_factory=list)
users: list[User] = field(default_factory=list)
ops: list[User] = field(default_factory=list)
voices: list[User] = field(default_factory=list)
@@ -70,6 +82,28 @@ class Message:
class ProtocolViolation(Exception):
pass
+def match_hostmask(hostmask: bytes, pattern: bytes) -> bool:
+ regexp = b""
+ for i in list(pattern):
+ c = i.to_bytes(1, "big")
+ if c == b'*':
+ regexp += b'.*'
+ elif c == b'?':
+ regexp += b'.'
+ elif c in b'[{':
+ regexp += b'[\\[{]'
+ elif c in b'}]':
+ regexp += b'[}\\]]'
+ elif c in b'|\\':
+ regexp += b'[|\\\\]'
+ elif c in b'^~':
+ regexp += b'[~^]'
+ else:
+ regexp += re.escape(c)
+ regexp += b'$'
+ return bool(re.compile(regexp, re.I).match(hostmask))
+
+
def parse_nih(nih: bytes) -> tuple[Optional[bytes], Optional[bytes], Optional[bytes]]:
"Parse a nick!username@host into tuples"
@@ -182,7 +216,7 @@ def handle_ping(
raise ProtocolViolation("PING without cookie") from None
-@register_irc_callback(b"422") # MOTD end: Ready to join channels
+@register_irc_callback(b"376") # MOTD end: Ready to join channels
def handle_376(
s: socket.socket,
state: State,
@@ -194,6 +228,7 @@ def handle_376(
for channel in channels.values():
send(s, b"JOIN", channel.name)
+
@register_irc_callback(b"422") # No MOTD: Ready to join channels
def handle_422(
s: socket.socket,
@@ -229,6 +264,7 @@ def handle_join(
msg.source.in_channels.append(channels[msg.args[0]])
channels[msg.args[0]].users.append(msg.source)
+
@register_irc_callback(b"PART")
def handle_part(
s: socket.socket,
@@ -245,7 +281,9 @@ def handle_part(
channels[msg.args[0]].joined = False
except KeyError:
# channels[msg.args[0]] = Channel(joined=False, name=msg.args[0])
- raise ProtocolViolation("Received self-PART from channel that hasn't been JOINed")
+ raise ProtocolViolation(
+ "Received self-PART from channel that hasn't been JOINed"
+ )
except IndexError:
raise ProtocolViolation("PART without channel name") from None
else:
@@ -312,14 +350,16 @@ def handle_353(
elif nick.startswith(b"+"):
channel.voices.append(user)
+
@register_irc_callback(b"CAP")
def handle_cap(
- s: socket.socket,
- state: State,
- me: User,
- users: dict[bytes, User],
- channels: dict[bytes, Channel],
- msg: Message) -> None:
+ s: socket.socket,
+ state: State,
+ me: User,
+ users: dict[bytes, User],
+ channels: dict[bytes, Channel],
+ msg: Message,
+) -> None:
if msg.args[0] == b"*" and msg.args[1] == b"LS":
capspecs = msg.args[2].split(b" ")
for capspec in capspecs:
@@ -333,7 +373,9 @@ def handle_cap(
if LOGIN and PASSWORD and cap == b"sasl":
if cap_arg is None:
- raise ProtocolViolation("Received capability list contains SASL entry without methods")
+ raise ProtocolViolation(
+ "Received capability list contains SASL entry without methods"
+ )
if b"PLAIN" in cap_arg.split(b","):
send(s, b"CAP", b"REQ", b"sasl")
@@ -343,6 +385,7 @@ def handle_cap(
state.deal_with_these_caps_before_cap_end.append(b"sasl")
send(s, b"AUTHENTICATE", b"PLAIN")
+
@register_irc_callback(b"AUTHENTICATE")
def handle_authenticate(
s: socket.socket,
@@ -353,8 +396,13 @@ def handle_authenticate(
msg: Message,
) -> None:
assert LOGIN and PASSWORD
- send(s, b"AUTHENTICATE", base64.b64encode(LOGIN + b"\x00" + LOGIN + b"\x00" + PASSWORD))
-
+ send(
+ s,
+ b"AUTHENTICATE",
+ base64.b64encode(LOGIN + b"\x00" + LOGIN + b"\x00" + PASSWORD),
+ )
+
+
@register_irc_callback(b"903") # SASL success
def handle_903(
s: socket.socket,
@@ -368,6 +416,7 @@ def handle_903(
if not state.deal_with_these_caps_before_cap_end:
send(s, b"CAP", b"END")
+
@register_irc_callback(b"PRIVMSG")
def handle_privmsg(
s: socket.socket,
@@ -467,7 +516,6 @@ def main() -> None:
send(s, b"CAP", b"LS", b"302")
-
# if LOGIN and PASSWORD:
# send(s, b"PASS", LOGIN + b":" + PASSWORD)
# elif LOGIN or PASSWORD: