production init

This commit is contained in:
Athena Funderburg
2026-05-26 16:41:23 +00:00
commit 21f38ee3e1
680 changed files with 47071 additions and 0 deletions
+469
View File
@@ -0,0 +1,469 @@
from typing import Tuple, Optional, Iterable, List, Any, Callable, Dict
import io, asyncio, settings
from datetime import datetime
from enum import IntEnum
from util.misc import Logger
from core import event
from core.models import (
Contact, Substatus, User, Circle, CircleRole, TextWithData,
MessageData, MessageType, LoginOption, OIM,
)
from core.backend import Backend, BackendSession, Chat, ChatSession
from core.client import Client
class IRCCtrl:
__slots__ = (
'logger', 'reader', 'writer', 'peername', 'close_callback', 'closed', 'transport',
'backend', 'bs', 'client',
'password', 'username', 'chat_sessions'
)
logger: Logger
reader: 'IRCReader'
writer: 'IRCWriter'
peername: Tuple[str, int]
close_callback: Optional[Callable[[], None]]
closed: bool
transport: Optional[asyncio.WriteTransport]
backend: Backend
bs: Optional[BackendSession]
client: Client
password: Optional[str]
username: Optional[str]
chat_sessions: Dict[Chat, ChatSession]
def __init__(self, logger: Logger, via: str, backend: Backend) -> None:
self.logger = logger
self.reader = IRCReader(logger)
self.writer = IRCWriter(logger)
self.peername = ('0.0.0.0', 6667)
self.close_callback = None
self.closed = False
self.transport = None
self.backend = backend
self.bs = None
self.client = Client('irc', '?', via)
self.password = None
self.username = None
self.chat_sessions = {}
def _m_pass(self, pwd: str) -> None:
self.password = pwd
def _m_user(self, email: str, junk1: str, junk2: str, realname: str) -> None:
password = self.password
self.password = None
assert password is not None
uuid = self.backend.user_service.login(email, password)
if uuid is not None:
bs = self.backend.login(uuid, self.client, BackendEventHandler(self), option = LoginOption.BootOthers)
else:
bs = None
if bs is None:
self.send_numeric(Err.PasswdMismatch, ':Wrong email/password')
return
self.bs = bs
user = bs.user
if user.suspended:
self.send_numeric(Err.YoureBannedCreep, ':Your CrossTalk account has been suspended. You may not connect to the service.')
self.close()
return
self.bs.me_update({ 'substatus': Substatus.Online })
self.send_numeric(RPL.Welcome, email, ':Log on successful.')
self._m_motd()
def _m_ping(self, *servers: str) -> None:
self.send_reply('PONG', *servers)
def _m_join(self, channel: str, keys: Optional[str] = None) -> None:
assert self.bs is not None
email = self.bs.user.email
chat = self._channel_to_chat(channel)
if chat is None:
chat = self.backend.chat_create()
chat.add_id('irc', channel)
cs = self._channel_to_chatsession(channel)
if cs is None:
cs = chat.join('irc', self.bs, ChatEventHandler(self))
chat.send_participant_joined(cs)
self.chat_sessions[chat] = cs
self.send_numeric(RPL.NamReply, email, '=', channel, ':' + ' '.join(
cs.user.email for cs in chat.get_roster_single()
))
# TODO: Chats created in other frontends are usually secret+private.
#self.send_numeric(Err.InviteOnlyChan, email, channel, ":Cannot join channel")
def _m_invite(self, user_email: str, channel: str) -> None:
assert self.bs is not None
cs = self._channel_to_chatsession(channel)
assert cs is not None
uuid = self.backend.util_get_uuid_from_email(user_email)
assert uuid is not None
user = self.backend.user_service.get(uuid)
assert user is not None
cs.invite(user)
self.send_numeric(RPL.Inviting, self.bs.user.email, user_email, channel)
def _m_list(self, arg1: Optional[str] = None, arg2: Optional[str] = None) -> None:
assert self.bs is not None
email = self.bs.user.email
for chat in self.backend.get_chats_by_scope('irc'):
self.send_numeric(RPL.List, email, chat.ids['irc'], str(len(list(chat.get_roster_single()))))
self.send_numeric(RPL.ListEnd, email, ":End of /LIST")
def _m_mode(self, channel: str) -> None:
#self.send_numeric(RPL.ChannelModeIs, self.bs.user.email, channel, '+tnl', 200)
pass
def _m_nick(self, nickname: str) -> None:
self.bs.me_update({ 'name': nickname })
def _m_userhost(self, email: str) -> None:
self._reply_unsupported('USERHOST')
def _m_who(self, channel: str) -> None:
assert self.bs is not None
cs = self._channel_to_chatsession(channel)
assert cs is not None
for cs_other in cs.chat.get_roster():
email = cs_other.user.email
name = cs_other.user.status.name or email
self.send_numeric(RPL.WhoReply, self.bs.user.email, channel, email, 'host', 'server', email, 'H', ':0 0PNE ' + name)
def _m_part(self, channel: str, message: Optional[str] = None) -> None:
assert self.bs is not None
cs = self._channel_to_chatsession(channel)
assert cs is not None
cs.close()
self.send_reply('PART', channel, source = self.bs.user.email)
def _m_privmsg(self, dest: str, message: str) -> None:
assert self.bs is not None
cs = self._channel_to_chatsession(dest)
assert cs is not None
cs.send_message_to_everyone(MessageData(sender = self.bs.user, type = MessageType.Chat, text = message))
def _m_away(self, message: Optional[str]) -> None:
assert self.bs is not None
if self.bs.user.status.substatus == Substatus.Online:
awaymsg = None
if len(message) > 0:
awaymsg = message
self.bs.me_update({ 'substatus': Substatus.Away, message: awaymsg })
self.send_numeric(RPL.NowAway, ':You are now away.')
else:
self.bs.me_update({ 'substatus': Substatus.Online, 'message': None })
self.send_numeric(RPL.UnAway, ':You are no longer away.')
def _m_motd(self) -> None:
self.send_numeric(RPL.MOTDStart, ": ----- CrossTalk (BETA) -----")
self.send_numeric(RPL.MOTD, ": - Welcome to CrossTalk! This frontend is undergoing a massive overhaul, so expect things to be iffy for now.")
self.send_numeric(RPL.MOTD, ": - We support MSNP, YMSG, and OSCAR as well, though as of right now, only MSNP and YMSG are in a usable state.")
self.send_numeric(RPL.MOTD, ": - Development is going at a faster pace, so this won't stay the case for long. Stay tuned!")
#if settings.PRIVACY_POLICY_UPDATE_NOTIF:
# self.send_numeric(RPL.MOTD, ": - NOTE: We have updated our Privacy Policy. Go here to take a look: https://crosstalk.im/ppolicy")
#if settings.TOS_UPDATE_NOTIF:
# self.send_numeric(RPL.MOTD, ": - NOTE: We have updated our Terms of Service. You must read and agree to these to continue using CrossTalk. Go here to take a look: https://crosstalk.im/tos")
#if settings.REROUTE_UPDATE_NOTIF:
# settings.send_numeric(RPL.MOTD, ": - NOTE for Yahoo! and MSN users: We have updated Reroute. Please download a new copy and replace your existing DLL with it. You can find that here: https://storage.ugnet.gay/crosstalk-dist/client/all/patching/reroute/reroute.dll")
self.send_numeric(RPL.MOTDEnd, ": - End of the MOTD.")
def _m_version(self) -> None:
self.send_numeric(RPL.Version, ": - Azul (BETA) - IRC Frontend")
def _m_quit(self, reason: Optional[str]) -> None:
self.close()
def _m_cap(self, subcommand: str, capabilities: Optional[str] = None) -> None:
self._reply_unsupported('CAP')
def _m_time(self) -> None:
self.send_numeric(RPL.Time, ': - {}'.format(datetime.now()))
def _reply_unsupported(self, cmd: str) -> None:
self.send_numeric(Err.UnknownCommand, cmd, ":Not supported")
def _channel_to_chatsession(self, channel: str) -> Optional[ChatSession]:
chat = self._channel_to_chat(channel)
assert chat is not None
return self.chat_sessions.get(chat)
def _channel_to_chat(self, channel: str) -> Optional[Chat]:
return self.backend.chat_get('irc', channel)
def data_received(self, data: bytes, *, transport: Optional[asyncio.BaseTransport] = None) -> None:
if transport is None:
transport = self.transport
assert transport is not None
self.peername = transport.get_extra_info('peername')
for m in self.reader.data_received(data):
try:
f = getattr(self, '_m_{}'.format(m[0].lower()))
f(*m[1:])
except Exception as ex:
self.logger.error(ex)
def send_numeric(self, n: int, *m: str, source: Optional[str] = None) -> None:
self.send_reply('{:03}'.format(n), *m, source = source)
def send_reply(self, *m: str, source: Optional[str] = None) -> None:
if source is None:
source = 'localhost'
self.writer.write((':' + source,) + m)
transport = self.transport
if transport is not None:
transport.write(self.flush())
def flush(self) -> bytes:
return self.writer.flush()
def close(self) -> None:
if self.closed: return
self.closed = True
for cs in list(self.chat_sessions.values()):
cs.close()
if self.close_callback:
self.close_callback()
if self.bs:
self.bs.close()
class BackendEventHandler(event.BackendEventHandler):
__slots__ = ('ctrl', 'bs')
ctrl: IRCCtrl
bs: BackendSession
def __init__(self, ctrl: IRCCtrl) -> None:
self.ctrl = ctrl
def on_client_alert(self, icon_url: Optional[str], url: Optional[str], message: str = '') -> None:
self.ctrl.send_reply('PRIVMSG', '$*', ':' + message, source = "System")
def on_maintenance_message(self, *args: Any, **kwargs: Any) -> None:
if args[1] is not None:
self.ctrl.send_reply(
'NOTICE', '$*', ':CrossTalk is going to go down for maintenance in {} minutes. Now is a good time to wrap up any conversations.'.format(str(args[1])), source = 'System',
)
def on_maintenance_boot(self) -> None:
msg = ':CrossTalk is now in maintenance mode. Try connecting to the service later..'
self.ctrl.send_reply(
'KILL', '$*', msg, source = 'System'
)
self.ctrl.close()
def on_presence_notification(
self, ctc: Contact, on_contact_add: bool, old_substatus: Substatus, *,
trid: Optional[str] = None, update_status: bool = True, update_info_other: bool = True,
send_status_on_bl: bool = False, sess_id: Optional[int] = None, updated_phone_info: Optional[Dict[str, Any]] = None,
) -> None:
if update_status:
self.ctrl.send_reply('NOTICE', ":{} is now {}".format(ctc.head.email, ctc.status.substatus))
def on_presence_self_notification(self, old_substatus: Substatus, *, update_status: bool = True, update_info: bool = True) -> None:
pass
def on_circle_created(self, circle: Circle) -> None:
pass
def on_circle_updated(self, circle: Circle) -> None:
pass
def on_left_circle(self, circle: Circle) -> None:
pass
def on_accepted_circle_invite(self, circle: Circle) -> None:
pass
def on_circle_invite_revoked(self, chat_id: str) -> None:
pass
def on_circle_role_updated(self, chat_id: str, role: CircleRole) -> None:
pass
def on_chat_invite(
self, chat: Chat, inviter: User, *, circle: bool = False, inviter_id: Optional[str] = None, invite_msg: str = '',
) -> None:
if circle: return
self.ctrl.send_reply('INVITE', self.bs.user.email, chat.ids['main'], source = inviter.email)
def on_declined_chat_invite(self, chat: Chat, circle: bool = False) -> None:
pass
def on_added_me(self, user: User, *, adder_id: Optional[str] = None, message: Optional[TextWithData] = None) -> None:
self.ctrl.send_reply('NOTICE', ":{} has added you to their friends list".format(user.email), source = user.email)
if message:
self.ctrl.send_reply('NOTICE', ":\"{}\"".format(message.text), source = user.email)
def on_removed_me(self, user: User) -> None:
pass
def on_contact_request_denied(self, user_added: User, message: Optional[str], *, contact_id: Optional[str] = None) -> None:
self.ctrl.send_reply('NOTICE', ":{} has declined your friend request".format(user_added.email), source = user_added.email)
if message:
self.ctrl.send_reply('NOTICE', ":\"{}\"".format(message), source = user_added.email)
def on_oim_sent(self, oim: 'OIM') -> None:
pass
def on_login_elsewhere(self, option: LoginOption) -> None:
if option is LoginOption.BootOthers:
self.ctrl.send_reply('NOTICE', ":You are being booted because you are logging in from another location.")
else:
self.ctrl.send_reply('NOTICE', ":You are now logged in from multiple locations.")
def on_close(self) -> None:
self.ctrl.close()
class ChatEventHandler(event.ChatEventHandler):
__slots__ = ('ctrl', 'cs')
ctrl: IRCCtrl
cs: ChatSession
def __init__(self, ctrl: IRCCtrl) -> None:
self.ctrl = ctrl
def on_close(self) -> None:
self.ctrl.chat_sessions.pop(self.cs.chat, None)
def on_participant_joined(self, cs_other: ChatSession, first_pop: bool, initial_join: bool) -> None:
if first_pop:
self.ctrl.send_reply('JOIN', self.cs.chat.ids['irc'], source = cs_other.user.email)
def on_participant_left(self, cs_other: ChatSession, last_pop: bool) -> None:
if last_pop:
self.ctrl.send_reply('PART', self.cs.chat.ids['irc'], source = cs_other.user.email)
def on_chat_invite_declined(
self, chat: Chat, invitee: User, *, invitee_id: Optional[str] = None, message: Optional[str] = None, circle: bool = False,
) -> None:
if circle: return
self.ctrl.send_reply('NOTICE', ":{} declined the invitation".format(invitee.email), source = invitee.email)
if message:
self.ctrl.send_reply('NOTICE', ":\"{}\"".format(message), source = invitee.email)
def on_chat_updated(self) -> None:
pass
def on_chat_roster_updated(self) -> None:
pass
def on_participant_status_updated(self, cs_other: ChatSession, first_pop: bool, initial: bool, old_substatus: Substatus) -> None:
pass
def on_message(self, data: MessageData) -> None:
if data.type is not MessageType.Chat:
return
if data.text is None:
return
self.ctrl.send_reply('PRIVMSG', self.cs.chat.ids['irc'], ':' + data.text, source = data.sender.email)
class IRCReader:
__slots__ = ('_logger', '_data')
_logger: Logger
_data: bytes
def __init__(self, logger: Logger) -> None:
self._logger = logger
self._data = b''
def data_received(self, data: bytes) -> Iterable[List[str]]:
if self._data:
self._data += data
else:
self._data = data
while self._data:
m = self._read()
if m is None: break
self._logger.debug('[Client]', *m)
yield m
def _read(self) -> Optional[List[str]]:
try:
i = self._data.index(b'\r\n')
except IndexError:
return None
except ValueError:
return None
chunk = self._data[:i].decode('utf-8')
self._data = self._data[i+2:]
# TODO: Support @foo :bar prefixes
toks = []
while True:
chunk = chunk.lstrip(' ')
if chunk[:1] == ':':
toks.append(chunk[1:])
break
k = chunk.find(' ')
if k < 0:
tok = chunk
else:
tok = chunk[:k]
chunk = chunk[k:]
if tok:
toks.append(tok)
if k < 0:
break
return toks
class IRCWriter:
__slots__ = ('_logger', '_buf')
_logger: Logger
_buf: io.BytesIO
def __init__(self, logger: Logger) -> None:
self._logger = logger
self._buf = io.BytesIO()
def write(self, m: Iterable[Any]) -> None:
self._logger.debug('[Server]', *m)
self._buf.write(' '.join(map(str, m)).encode('utf-8'))
self._buf.write(b'\r\n')
def flush(self) -> bytes:
data = self._buf.getvalue()
if data:
self._buf = io.BytesIO()
return data
class Err(IntEnum):
UnknownError = 400
UnknownCommand = 421
NoNicknameGiven = 431
NicknameInUse = 433
PasswdMismatch = 464
YoureBannedCreep = 465
InviteOnlyChan = 473
class RPL(IntEnum):
Welcome = 1
List = 322
ListEnd = 323
NamReply = 353
ChannelModeIs = 324
Inviting = 341
WhoReply = 352
MOTDStart = 375
MOTD = 372
MOTDEnd = 376
UnAway = 305
NowAway = 306
Away = 301
Version = 351
Time = 391