Advertisement
Guest User

Python IRC Client

a guest
Jul 8th, 2024
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.27 KB | Source Code | 0 0
  1. import select
  2. import socket
  3. from random import randint
  4. from typing import Self
  5.  
  6.  
  7. class IrcUser:
  8.     def __init__(self, nick: str, username: str, host: str, realname: str = None):
  9.         self.nick = nick
  10.         self.username = username
  11.         self.host = host
  12.         self.realname = realname
  13.  
  14.     def __str__(self) -> str:
  15.         return f"<User nick={self.nick} username={self.username}>"
  16.  
  17.     def __repr__(self) -> str:
  18.         return f"{self.nick}!{self.username}@{self.host}"
  19.  
  20.     def __bytes__(self) -> bytes:
  21.         return bytes(repr(self), "utf-8")
  22.  
  23.     @classmethod
  24.     def from_raw(cls, raw: str) -> Self:
  25.         """Construct a User from raw IRC server user string"""
  26.         try:
  27.             nick, remaining = raw.split("!")
  28.             username, host = remaining.split("@")
  29.             return cls(nick, username, host)
  30.         except ValueError as exc:
  31.             raise ValueError("Invalid user string " + raw) from exc
  32.  
  33.  
  34. class IrcMessage:
  35.     def __init__(self, source: IrcUser | str | None, command: str, params: str) -> None:
  36.         self.source = source
  37.         self.command = command
  38.         self.params = params
  39.  
  40.     def __str__(self) -> str:
  41.         return f"<Message source={self.source} command={self.command}>"
  42.  
  43.     def __repr__(self) -> str:
  44.         if isinstance(self.source, IrcUser):
  45.             source = repr(self.source)
  46.         else:
  47.             source = self.source
  48.         return (
  49.             f'{":" + source + " " if self.source else ""}'
  50.             f"{self.command} {self.params}\r\n"
  51.         )
  52.  
  53.     def __bytes__(self) -> bytes:
  54.         return bytes(repr(self), "utf-8")
  55.  
  56.     @classmethod
  57.     def from_raw(cls, raw: str) -> Self:
  58.         """Construct a Message object from raw IRC server output"""
  59.         stripped = raw.strip("\r\n")
  60.         try:
  61.             if stripped.startswith("@"):
  62.                 raise NotImplementedError("This client currently does not support tags")
  63.             elif stripped.startswith(":"):
  64.                 src, cmd, *params = stripped.split(" ")
  65.                 src = src[1:]
  66.                 try:
  67.                     src = IrcUser.from_raw(src)
  68.                 except ValueError:
  69.                     pass
  70.             else:
  71.                 src = None
  72.                 cmd, *params = stripped.split(" ")
  73.             return cls(src, cmd, " ".join(params))
  74.         except ValueError as exc:
  75.             raise ValueError("Invalid message string " + raw) from exc
  76.  
  77.  
  78. class IrcBaseClient:
  79.     def __init__(self, nick: str, username: str, password: str = None) -> None:
  80.         self.nick = nick
  81.         self.password = password
  82.         self.username = username
  83.         self.socket = None
  84.         self.connected = False
  85.  
  86.     def connect(self, hostname: str, port: int = 6667) -> None:
  87.         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  88.         self.socket.connect((hostname, port))
  89.         self.socket.settimeout(10)
  90.         self.initial_auth()
  91.  
  92.         while True:
  93.             message = self.get_message()
  94.             if message:
  95.                 print(repr(message).strip())
  96.                 match message.command:
  97.                     case "005":
  98.                         break
  99.                     case "433":  # Nickname already in use
  100.                         self.nick = f"Guest_{randint(10, 99)}"
  101.                         self.initial_auth()
  102.                         break
  103.  
  104.         self.connected = True
  105.  
  106.     def initial_auth(self):
  107.         if self.password:
  108.             self.send(IrcMessage(None, "PASS", self.password))
  109.         self.set_nick(self.nick)
  110.         self.send(IrcMessage(None, "USER", f"{self.username} 0 * :{self.username}"))
  111.  
  112.     def send(self, message: IrcMessage) -> None:
  113.         raw = bytes(message)
  114.         self.socket.send(raw)
  115.  
  116.     def get_message(self) -> IrcMessage | None:
  117.         readable, _, _ = select.select([self.socket], [], [], 0)
  118.         if readable:
  119.             # Read reply one byte at a time
  120.             reply = b""
  121.             while c := self.socket.recv(1):
  122.                 reply = reply + c
  123.                 if c == b"\n":
  124.                     message = IrcMessage.from_raw(reply.decode("utf-8"))
  125.                     if message.command == "PING":
  126.                         self.pong(message.params[1:])
  127.                         return None
  128.                     return message
  129.  
  130.     def get_all_messages(self) -> list[IrcMessage]:
  131.         messages = []
  132.         while message := self.get_message():
  133.             messages.append(message)
  134.         return messages
  135.  
  136.     def join(self, channel: str) -> None:
  137.         self.send(IrcMessage(None, "JOIN", channel))
  138.  
  139.     def part(self, channel: str, reason: str) -> None:
  140.         self.send(IrcMessage(None, "PART", f"{channel} {reason}"))
  141.  
  142.     def send_private_message(self, to: str, text: str) -> None:
  143.         self.send(IrcMessage(None, "PRIVMSG", f"{to} {text}"))
  144.  
  145.     def send_notice(self, message_target: str, text: str) -> None:
  146.         self.send(IrcMessage(None, "NOTICE", f"{message_target} {text}"))
  147.  
  148.     def get_names(self, channel: str) -> None:
  149.         self.send(IrcMessage(None, "NAMES", channel))
  150.  
  151.     def pong(self, s: str) -> None:
  152.         self.send(IrcMessage(None, "PONG", s))
  153.  
  154.     def query_topic(self, channel: str) -> None:
  155.         self.send(IrcMessage(None, "TOPIC", channel))
  156.  
  157.     def set_topic(self, channel: str, topic: str) -> None:
  158.         self.send(IrcMessage(None, "TOPIC", f"{channel} {topic}"))
  159.  
  160.     def invite(self, nick: str, channel: str) -> None:
  161.         self.send(IrcMessage(None, "INVITE", f"{nick} {channel}"))
  162.  
  163.     def kick(self, channel: str, nick: str, comment: str) -> None:
  164.         self.send(IrcMessage(None, "KICK", f"{channel} {nick} {comment}"))
  165.  
  166.     def motd(self) -> None:
  167.         self.send(IrcMessage(None, "MOTD", ""))
  168.  
  169.     def version(self) -> None:
  170.         self.send(IrcMessage(None, "VERSION", ""))
  171.  
  172.     def oper(self, name: str, password: str) -> None:
  173.         self.send(IrcMessage(None, "OPER", f"{name} {password}"))
  174.  
  175.     def set_nick(self, nick: str) -> None:
  176.         self.send(IrcMessage(None, "NICK", nick))
  177.  
  178.     def disconnect(self, message: str = "Quitting") -> None:
  179.         self.send(IrcMessage(None, "QUIT", message))
  180.         self.socket.shutdown(socket.SHUT_RDWR)
  181.         self.socket.close()
  182.         self.connected = False
  183.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement