Browse Source

Better PoC

master
Darks 5 months ago
parent
commit
4b722ff2dc
Signed by: Darks GPG Key ID: 7515644268BE1433
6 changed files with 180 additions and 157 deletions
  1. +1
    -0
      .gitignore
  2. +35
    -0
      bot.py
  3. +134
    -0
      irc.py
  4. +10
    -46
      main.py
  5. +0
    -42
      sample.txt
  6. +0
    -69
      utils.py

+ 1
- 0
.gitignore View File

@ -1 +1,2 @@
__pycache__/*
secrets.py

+ 35
- 0
bot.py View File

@ -0,0 +1,35 @@
import socket
from threading import Thread
from irc import IRC
from secrets import USER, PASSWORD
class Bot(object):
def __init__(self, irc, v5, channels):
self.irc = IRC(*irc)
self.sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
self.channels = channels
self._v5_handler = Thread(target=self._handle_v5)
def start(self):
# Start IRC
self.irc.start(USER, PASSWORD)
for c in self.channels:
self.irc.join(c)
# Start v5 handler
self._v5_handler.start()
# Run IRC
self.irc.run()
def _handle_v5(self):
while True:
data, addr = self.v5_sock.recvfrom(4096)
data = data.decode()
print(f"v5: Received <{data}>")
self.irc.msg(data, "#glados")

+ 134
- 0
irc.py View File

@ -0,0 +1,134 @@
# Manage the IRC layer of GLaDOS
import re, socket, ssl
from functools import wraps
from queue import Queue
from threading import Thread
class IRC(object):
def __init__(self, host, port):
""" Initialize an IRC wrapper """
# Public attributes
self.connected = False # Simple lock
# Private attributes
self._socket = ssl.create_default_context().wrap_socket(
socket.create_connection((host, port)),
server_hostname=host)
self._inbox = Queue()
self._handler = Thread(target=self._handle)
self._callbacks = []
# Public methods
def start(self, nick, password):
""" Start the IRC layer. Manage authentication as well """
self._handler.start()
self._send(f"USER {nick} * * :{nick}")
self._send(f"NICK {nick}")
self._waitfor(lambda m: "NOTICE" in m and "/AUTH" in m)
self._send(f"AUTH {nick}:{password}")
self._waitfor(lambda m: "You are now logged in" in m)
self.connected = True
def run(self):
""" Handle new messages """
while True:
message = self.receive()
for event, callback in self._callbacks:
if event(message):
callback(message)
def send(self, target, message):
""" Send a message to the specified target (channel or user) """
self._send(f"PRIVMSG {target} :{message}")
def receive(self):
""" Receive a private message """
while True:
message = self._recv()
print(f"receive: {message}")
if " PRIVMSG " in message:
return Message(message)
def join(self, channel):
""" Join a channel """
self._send(f"JOIN {channel}")
def on(self, event):
""" Adds a callback to the IRC handler
Event is a function taking in parameter a Message and returning
True if the callback should be executed on the message """
def callback(func):
@wraps(func)
def wrapper(message):
func(message)
self._callbacks.append((event, wrapper))
return wrapper
return callback
# Private methods
def _handle(self):
""" Handle raw messages from irc and manage ping """
while True:
# Get incoming messages
data = self._socket.recv(4096).decode()
# Split multiple lines
for m in data.split('\r\n'):
# Manage ping
if m.startswith("PING"):
self._send(m.replace("PING", "PONG"))
# Or add a new message to inbox
elif len(m):
self._inbox.put(m)
print(f"_handle: <{m}>")
def _send(self, raw):
""" Wrap and encode raw message to send """
self._socket.send(f"{raw}\r\n".encode())
def _recv(self):
m = self._inbox.get()
return m
def _waitfor(self, condition):
""" Wait for a raw message that matches the condition """
msg = self._recv()
while not condition(msg):
msg = self._recv()
return msg
class Message(object):
r = re.compile("^:(?P<author>[\w.~]+)(?:!(?P<host>\S+))? PRIVMSG (?P<to>\S+) :(?P<text>.+)")
def __init__(self, raw):
match = re.search(Message.r, raw)
self.author = match.group("author")
self.to = match.group("to")
self.text = match.group("text")
print(self)
def __str__(self):
return f"<{self.author}> à <{self.to}> : {self.text}"
if __name__ == "__main__":
bot = IRC("irc.planet-casio.com", 6697)
@bot.on(lambda m: "Hello" in m.text)
def hello(msg):
bot.send(msg.to, f"Nice to see you {msg.author}!")
bot.start("glados", "abcdef123456")
bot.join("#glados")
bot.run()

+ 10
- 46
main.py View File

@ -3,56 +3,20 @@
import sys
import time
from threading import Thread
# from queue import Queue
from utils import create_socket, IRC
server_name = 'localhost'
server_port = 5500
NICK = "GLaDOS"
PASSWORD = "abcdef123456"
class Bot(object):
def __init__(self, irc, v5):
self.irc = IRC(irc)
self.v5_sock = create_socket("v5", v5)
self.channels = ["#general", "#glados"]
self.connected = False # Dirty lock
def run(self):
# Start a thread for raw IRC messages
Thread(target=self.irc.raw_handle).start()
# Authenticate to server
self.irc.connect(NICK, PASSWORD)
for c in self.channels:
self.irc.join(c)
# Finally, handle v5 messages
while True:
data, addr = self.v5_sock.recvfrom(4096)
data = data.decode()
print(f"v5: Received <{data}>")
self.irc.msg(data, "#glados")
def handle_irc(self):
self.connected = True
print("irc connected")
while True:
msg = self.irc.get_msg()
print(f"IRC: {data}")
if "Hello" in msg:
self.irc.msg("Hello :)", "#glados")
from bot import Bot
from secrets import USER, PASSWORD
glados = Bot(
('irc.planet-casio.com', 6697),
('127.0.0.1', 5555)
('127.0.0.1', 5555),
["#general", "#glados"]
)
glados.run()
@glados.irc.on(lambda m: "Hello" in m.text)
def say_hello(msg):
glados.irc.send(msg.to, f"Nice to see you, {msg.author}")
#abcdef123456
glados.start()
glados.run()

+ 0
- 42
sample.txt View File

@ -1,42 +0,0 @@
< :irc.planet-casio.com NOTICE * :*** Looking up your hostname...
< :irc.planet-casio.com NOTICE * :*** Found your hostname (cached)
> USER GLaDOS * * :GLaDOS
> NICK GLaDOS
< PING :5B61F700
> PONG :5B61F700
< :irc.planet-casio.com NOTICE GLaDOS :Serveur réservé aux membres de Planète Casio.
< :irc.planet-casio.com NOTICE GLaDOS :Ce serveur nécessite de s'authentifier. Les identifiants sont ceux de votre compte Planète Casio (https://www.planet-casio.com).
< :irc.planet-casio.com NOTICE GLaDOS :Pour vous connecter, configurez le SASL dans votre client ou entrez la commande suivante : /AUTH <login>:<mot de passe>
> AUTH GLaDOS:password
< :irc.planet-casio.com 900 GLaDOS GLaDOS!GLaDOS@87-231-180-235.rev.numericable.fr GLaDOS :You are now logged in as GLaDOS.
< :irc.planet-casio.com 001 GLaDOS :Welcome to the Planete Casio IRC Network GLaDOS!GLaDOS@87-231-180-235.rev.numericable.fr
< :irc.planet-casio.com 002 GLaDOS :Your host is irc.planet-casio.com, running version UnrealIRCd-5.0.7
< :irc.planet-casio.com 003 GLaDOS :This server was created Sun Oct 11 2020 at 12:46:49 MSK
< :irc.planet-casio.com 004 GLaDOS irc.planet-casio.com UnrealIRCd-5.0.7 iowrsxzdHtIDZRqpWGTSB lvhopsmntikraqbeIHzMQNRTOVKDdGLPZSCcf
< :irc.planet-casio.com 005 GLaDOS AWAYLEN=307 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=beI,kLf,lH,psmntirzMQNRTOVKDdGPZSCc CHANNELLEN=32 CHANTYPES=# CLIENTTAGDENY=*,-draft/typing,-typing DEAF=d ELIST=MNUCT EXCEPTS EXTBAN=~,ptmTSOcarnqjf :are supported by this server
< :irc.planet-casio.com 005 GLaDOS HCN INVEX KICKLEN=307 KNOCK MAP MAXCHANNELS=100 MAXLIST=b:60,e:60,I:60 MAXNICKLEN=30 MINNICKLEN=0 MODES=12 NAMESX NETWORK=Planete-Casio :are supported by this server
< :irc.planet-casio.com 005 GLaDOS NICKLEN=30 PREFIX=(qaohv)~&@%+ QUITLEN=307 SAFELIST SILENCE=15 STATUSMSG=~&@%+ TARGMAX=DCCALLOW:,ISON:,JOIN:,KICK:4,KILL:,LIST:,NAMES:1,NOTICE:1,PART:,PRIVMSG:4,SAJOIN:,SAPART:,TAGMSG:1,USERHOST:,USERIP:,WATCH:,WHOIS:1,WHOWAS:1 TOPICLEN=360 UHNAMES USERIP WALLCHOPS WATCH=128 :are supported by this server
< :irc.planet-casio.com 005 GLaDOS WATCHOPTS=A WHOX :are supported by this server
< :irc.planet-casio.com 396 GLaDOS Clk-3B0FEC88.rev.numericable.fr :is now your displayed host
< :irc.planet-casio.com NOTICE GLaDOS :*** You are connected to irc.planet-casio.com with TLSv1.3-TLS_CHACHA20_POLY1305_SHA256
< :irc.planet-casio.com 251 GLaDOS :There are 1 users and 10 invisible on 2 servers
< :irc.planet-casio.com 252 GLaDOS 7 :operator(s) online
< :irc.planet-casio.com 254 GLaDOS 1 :channels formed
< :irc.planet-casio.com 255 GLaDOS :I have 4 clients and 1 servers
< :irc.planet-casio.com 265 GLaDOS 4 8 :Current local users 4, max 8
< :irc.planet-casio.com 266 GLaDOS 11 13 :Current global users 11, max 13
< :irc.planet-casio.com 422 GLaDOS :MOTD File is missing
< :GLaDOS MODE GLaDOS :+iwxz
< :GLaDOS!GLaDOS@Clk-3B0FEC88.rev.numericable.fr JOIN :#general
< :irc.planet-casio.com 353 GLaDOS = #general :GLaDOS Darks Breizh eragon
< :irc.planet-casio.com 366 GLaDOS #general :End of /NAMES list.
< :NickServ MODE GLaDOS :+r
> Hello
< :irc.planet-casio.com 421 GLaDOS Hello :Unknown command
> join #general
> PRIVMSG #general :Hello
< :Darks!eldeberen@Clk-3B0FEC88.rev.numericable.fr PRIVMSG #general :Nice

+ 0
- 69
utils.py View File

@ -1,69 +0,0 @@
import socket, ssl
from queue import Queue
def create_socket(type, target):
if type == "irc":
s = socket.create_connection(('irc.planet-casio.com', 6697))
context = ssl.create_default_context()
s = context.wrap_socket(s, server_hostname='irc.planet-casio.com')
return s
elif type == "v5":
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind(target)
return s
class IRC(object):
def __init__(self, target):
self.socket = create_socket("irc", target)
self.inbox = Queue()
# Public API
def raw_handle(self):
""" Handle new messages and ping responses. Runs in its own thread"""
data = self.socket.recv(4096).decode()
for m in data.split('\r\n'):
if m.startswith("PING"):
self._send(m.replace("PING", "PONG"))
else:
self.inbox.put(m)
def connect(self, nick, password):
""" Connect and authenticate to IRC server """
self._send(f"NICK {nick}")
self._send(f"USER {nick} * * :{nick}")
self._waitfor(lambda m: "NOTICE" in m and "/AUTH" in m)
self._send(f"auth {nick}:{password}")
self._waitfor(lambda m: f":{nick} MODE {nick} :+iwxz" in m)
def join(self, channel):
""" Join a channel """
self._send("JOIN :{channel}")
def msg(self, msg, channel):
self._send(f"PRIVMSG {channel} :{msg}")
def get_msg(self):
""" Get the next message on the inbox """
return _waitfor(lambda m: "PRIVMSG" in m)
# Private API
def _send(self, raw):
""" Send raw data """
self.socket.send(f"{raw}\r\n".encode())
def _recv(self):
""" Pop a new item from INBOX """
print("Waiting for new item")
return self.inbox.get()
def _waitfor(self, condition):
""" Wait for a message that matches the condition """
msg = self._recv()
while not condition(msg):
print(f"Wait for: {msg}")
msg = self._recv()
return msg

Loading…
Cancel
Save