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
+29
View File
@@ -0,0 +1,29 @@
Y64 = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._'
def Y64Encode(string_encode: bytes) -> bytes:
limit = len(string_encode) - (len(string_encode) % 3)
out = bytearray()
buff = [0] * len(string_encode)
for i in range(len(string_encode)):
buff[i] = string_encode[i] & 0xff
for i in range(0, limit, 3):
out.extend([Y64[buff[i] >> 2],
Y64[((buff[i] << 4) & 0x30) | (buff[i + 1] >> 4)],
Y64[((buff[i + 1] << 2) & 0x3c) | (buff[i + 2] >> 6)],
Y64[buff[i + 2] & 0x3f]])
remaining = len(string_encode) - limit
if remaining == 1:
out.extend([Y64[buff[limit] >> 2],
Y64[(buff[limit] << 4) & 0x30],
ord('-'),
ord('-')])
elif remaining == 2:
out.extend([Y64[buff[limit] >> 2],
Y64[((buff[limit] << 4) & 0x30) | (buff[limit + 1] >> 4)],
Y64[(buff[limit + 1] << 2) & 0x3c],
ord('-')])
return bytes(out)
+1
View File
@@ -0,0 +1 @@
from .entry import register
+68
View File
@@ -0,0 +1,68 @@
from typing import Optional, Callable
import asyncio, settings
from aiohttp import web
from core.backend import Backend
from util.misc import Logger
from .ymsg_ctrl import YMSGCtrlBase
def register(loop: asyncio.AbstractEventLoop, backend: Backend, http_app: web.Application, *, devmode: bool = False) -> None:
from util.misc import ProtocolRunner
from . import pager, http, videochat, voicechat
backend.add_runner(ProtocolRunner('0.0.0.0', 5050, ListenerYMSG, args = ['Yahoo', backend, pager.YMSGCtrlPager], service = "YMSG Pager"))
# Funny that Yahoo! used the FTP transfer, Telnet, SMTP, and NNTP (Usenet) ports as the fallback ports.
backend.add_runner(ProtocolRunner('0.0.0.0', 20, ListenerYMSG, args = ['Yahoo', backend, pager.YMSGCtrlPager], service = "YMSG Pager"))
backend.add_runner(ProtocolRunner('0.0.0.0', 23, ListenerYMSG, args = ['Yahoo', backend, pager.YMSGCtrlPager], service = "YMSG Pager"))
#backend.add_runner(ProtocolRunner('0.0.0.0', 25, ListenerYMSG, args = ['Yahoo', backend, pager.YMSGCtrlPager], service = "YMSG Pager"))
#backend.add_runner(ProtocolRunner('0.0.0.0', 119, ListenerYMSG, args = ['Yahoo', backend, pager.YMSGCtrlPager], service = "YMSG Pager"))
# Yahoo! also utilized port 80 for YMSG communication via TCP, but that interferes with the port 80 binded to the HTTP
# services when the server is run in dev mode.
#backend.add_runner(ProtocolRunner('0.0.0.0', 80, ListenerYMSG, args = ['Yahoo', backend, pager.YMSGCtrlPager], service = "YMSG Pager"))
backend.add_runner(ProtocolRunner('0.0.0.0', 8001, ListenerYMSG, args = ['Yahoo', backend, pager.YMSGCtrlPager], service = "YMSG Pager"))
backend.add_runner(ProtocolRunner('0.0.0.0', 8002, ListenerYMSG, args = ['Yahoo', backend, pager.YMSGCtrlPager], service = "YMSG Pager"))
http.register(http_app, devmode = devmode)
voicechat.register(backend)
videochat.register(backend)
class ListenerYMSG(asyncio.Protocol):
logger: Logger
backend: Backend
controller: YMSGCtrlBase
transport: Optional[asyncio.WriteTransport]
def __init__(self, logger_prefix: str, backend: Backend, controller_factory: Callable[[Logger, str, Backend], YMSGCtrlBase]) -> None:
super().__init__()
self.logger = Logger(logger_prefix, self)
self.backend = backend
self.controller = controller_factory(self.logger, 'direct', backend)
self.controller.close_callback = self._on_close
self.transport = None
def connection_made(self, transport: asyncio.BaseTransport) -> None:
assert isinstance(transport, asyncio.WriteTransport)
self.transport = transport
self.logger.log_connect()
def connection_lost(self, exc: Optional[Exception]) -> None:
self.controller.close()
self.logger.log_disconnect()
self.transport = None
def data_received(self, data: bytes) -> None:
transport = self.transport
assert transport is not None
if self.backend.maintenance_mode:
transport.close()
return
self.controller.transport = None
if self.controller.transport is None:
self.controller.transport = self.transport
self.controller.data_received(data)
transport.write(self.controller.flush())
self.controller.transport = transport
def _on_close(self) -> None:
if self.transport is None: return
self.transport.close()
+484
View File
@@ -0,0 +1,484 @@
from typing import Any, Dict, Optional, Tuple
from aiohttp import web
import asyncio, shutil, time, uuid, datetime, util.misc, settings
from markupsafe import Markup
from urllib.parse import unquote, unquote_plus, quote
from pathlib import Path
from core.backend import Backend, BackendSession
from core.models import Contact, Substatus, ContactList
from util.hash import gen_salt
from util.misc import Logger
from .ymsg_ctrl import YMSGCtrlBase, _try_decode_ymsg
from .misc import YMSGService
from .pager import _encode_yahoo_id, Y_COOKIE_TEMPLATE, T_COOKIE_TEMPLATE
YAHOO_TMPL_DIR = 'front/ymsg/tmpl'
# https://github.com/ifwe/digsby/blob/f5fe00244744aa131e07f09348d10563f3d8fa99/digsby/src/yahoo/yahooutil.py#L33
FILE_STORE_PATH = '/storage/file/{filename}'
_tasks_by_token = {} # type: Dict[str, asyncio.Task[None]]
def register(app: web.Application, *, devmode: bool = False) -> None:
util.misc.add_to_jinja_env(app, 'ymsg', YAHOO_TMPL_DIR)
# HTTP auth
app.router.add_get('/config/ncclogin', handle_ncclogin)
app.router.add_get('/config/pwtoken_get', handle_gettoken)
app.router.add_get('/config/pwtoken_login', handle_login)
# Yahoo! Insider
app.router.add_get('/ycontent/', handle_insider_ycontent)
# Yahoo! Chat/Ads
app.router.add_get('/us.yimg.com/i/msgr/chat/conf-banner.html', handle_chat_banad)
app.router.add_get('/c/msg/tabs.html', handle_chat_tabad)
app.router.add_get('/etc/yahoo-tab-ad', handle_chat_tabad)
app.router.add_get('/c/msg/chat.html', handle_chat_notice)
app.router.add_get('/c/msg/alerts.html', handle_chat_alertad)
app.router.add_get('/etc/yahoo-placeholder', handle_placeholder)
app.router.add_get('/external/client_ad.php', handle_banneradredir)
# Yahoo!'s redirector to cookie-based services
#app.router.add_get('/config/reset_cookies', handle_cookies_redirect)
# Yahoo!'s redirect service (rd.yahoo.com)
app.router.add_get('/messenger/search/', handle_rd_yahoo)
app.router.add_get('/messenger/client/', handle_rd_yahoo)
# Yahoo HTTP file transfer fallback
app.router.add_post('/notifyft', handle_ft_http)
app.router.add_get(FILE_STORE_PATH, handle_yahoo_filedl)
# Misc stuff
app.router.add_get('/capacity', handle_capacity)
async def handle_insider_ycontent(req: web.Request) -> web.Response:
backend = req.app['backend']
yab_received = False
yab_set = False
config_xml = []
for query_xml in req.query.keys():
# Ignore any `chatroom_##########` requests for now
if query_xml in IGNORED_QUERIES or query_xml.startswith('chatroom_'): continue
if query_xml in ('ab2','addab2'):
(_, bs) = _parse_cookies(req, backend)
if bs is not None:
user = bs.user
detail = user.detail
if detail is not None:
ab2_tmpl = req.app['jinja_env'].get_template('ymsg:Yinsider/Yinsider.ab2.xml')
if query_xml == 'ab2':
if yab_received or yab_set: continue
ctcs = detail.contacts.values()
records = []
for ctc in ctcs:
records.append(_gen_yab_record(ctc))
config_xml.append(ab2_tmpl.render(epoch = round(time.time()), records = Markup('\n'.join(records))))
if query_xml == 'addab2':
edit_mode = False
if yab_set or yab_received: continue
if req.query.get('ee') == '1' and req.query.get('ow') == '1':
edit_mode = True
if edit_mode:
if req.query.get('id') is None:
continue
target_ctc = None
entry_id = str(req.query['id'])
for ctc in detail.contacts.values():
if ctc.detail.index_id == entry_id:
target_ctc = ctc
if not target_ctc:
continue
if req.query.get('pp') is not None:
if str(req.query['pp']) not in ('0','1','2'):
continue
new_first_name = req.query.get('fn')
new_last_name = req.query.get('ln')
# Yahoo! will set the email/YID as the first name when editing contact details;
# if new_first_name == email and last name isn't set, don't set first name
if new_first_name != target_ctc.head.email and new_last_name:
target_ctc.detail.first_name = new_first_name
target_ctc.detail.last_name = new_last_name
target_ctc.detail.nickname = req.query.get('nn')
target_ctc.detail.personal_email = req.query.get('e')
target_ctc.detail.home_phone = req.query.get('hp')
target_ctc.detail.work_phone = req.query.get('wp')
target_ctc.detail.mobile_phone = req.query.get('mb')
backend._mark_modified(user)
else:
continue
config_xml.append(ab2_tmpl.render(epoch = round(time.time()), records = Markup(_gen_yab_record(target_ctc))))
continue
tmpl = req.app['jinja_env'].get_template('ymsg:Yinsider/Yinsider.' + query_xml + '.xml')
config_xml.append(tmpl.render())
return render(req, 'ymsg:Yinsider/Yinsider.xml', {
'epoch': round(time.time()),
'configxml': Markup('\n'.join(config_xml)),
})
# 'intl', 'os', 'ver', 'fn', 'ln', 'yid', 'nn', 'e', 'hp', 'wp', 'mp', 'pp', 'ee',
# 'ow', and 'id' are NOT queries to retrieve config XML files;
# 'getwc' and 'getgp' are undocumented as of now
# Other queries most likely are just not implemented
IGNORED_QUERIES = {
'intl', 'os', 'ver',
'imv', 'sms', 'getimv', 'getwc', 'getgp',
'fn', 'ln', 'yid',
'nn', 'e', 'hp',
'wp', 'mb', 'pp',
'ee', 'ow', 'id',
}
def _gen_yab_record(ctc: Contact) -> str:
fname = None
lname = None
nname = None
email = None
hphone = None
wphone = None
mphone = None
if ctc.detail.first_name is not None:
fname = ' fname="{}"'.format(ctc.detail.first_name)
if ctc.detail.last_name is not None:
lname = ' lname="{}"'.format(ctc.detail.last_name)
if ctc.detail.nickname is not None:
nname = ' nname="{}"'.format(ctc.detail.nickname)
if ctc.detail.personal_email is not None:
email = ' email="{}"'.format(ctc.detail.personal_email)
if ctc.detail.home_phone is not None:
hphone = ' hphone="{}"'.format(ctc.detail.home_phone)
if ctc.detail.work_phone is not None:
wphone = ' wphone="{}"'.format(ctc.detail.work_phone)
if ctc.detail.mobile_phone is not None:
mphone = ' mphone="{}"'.format(ctc.detail.mobile_phone)
return '<record userid="{yid}"{fname}{lname}{nname}{email}{hphone}{wphone}{mphone} dbid="{contact_id}"/>'.format(
yid = ctc.head.username,
fname = fname or '', lname = lname or '', nname = nname or '',
email = email or '', hphone = hphone or '', wphone = wphone or '', mphone = mphone or '',
contact_id = ctc.detail.index_id,
)
async def handle_chat_banad(req: web.Request) -> web.Response:
return render(req, 'ymsg:placeholders/banad.html')
async def handle_chat_tabad(req: web.Request) -> web.Response:
query = req.query
return render(req, 'ymsg:placeholders/adsmall.html', {
'adtitle': 'banner ad',
'spaceid': (query.get('spaceid') or 0),
})
async def handle_chat_alertad(req: web.Request) -> web.Response:
query = req.query
return render(req, 'ymsg:placeholders/adsmall.html', {
'adtitle': 'alert ad usmsgr',
'spaceid': (query.get('spaceid') or 0),
})
async def handle_placeholder(req: web.Request) -> web.Response:
return render(req, 'ymsg:placeholders/generic.html')
async def handle_chat_notice(req: web.Request) -> web.Response:
return render(req, 'ymsg:placeholders/generic.html')
async def handle_rd_yahoo(req: web.Request) -> web.Response:
return web.HTTPFound(req.query_string.replace(' ', '+'))
async def handle_ft_http(req: web.Request) -> web.Response:
body = await req.read()
# Look for incomplete key-value field `29`
stream_loc = body.find(b'29\xC0\x80')
stream = body[(stream_loc + 4):]
# Parse the rest of the YMSG packet
raw_ymsg_data = body[:stream_loc]
# Now change the length field as fit to get the YMSG parser to gobble it up
import struct
raw_ymsg_part_pre = raw_ymsg_data[0:8]
raw_ymsg_part_post = raw_ymsg_data[10:]
raw_ymsg_data = raw_ymsg_part_pre + struct.pack('!H', len(raw_ymsg_part_post[10:])) + raw_ymsg_part_post
backend = req.app['backend']
try:
y_ft_pkt = _try_decode_ymsg(raw_ymsg_data, 0)[0]
except Exception:
return web.HTTPInternalServerError(text = '')
try:
# check version and vendorId
if y_ft_pkt[1] > 16 or y_ft_pkt[2] not in (0, 100):
return web.HTTPInternalServerError(text = '')
except Exception:
return web.HTTPInternalServerError(text = '')
if y_ft_pkt[0] is not YMSGService.FileTransfer:
return web.HTTPInternalServerError(text = '')
ymsg_data = y_ft_pkt[5]
yahoo_id_sender = util.misc.arbitrary_decode(ymsg_data.get(b'0') or b'')
(yahoo_id, bs) = _parse_cookies(req, backend)
if None in (bs,yahoo_id):
return web.HTTPInternalServerError(text = '')
assert bs is not None
yahoo_id_recipient = util.misc.arbitrary_decode(ymsg_data.get(b'5') or b'')
recipient_uuid = backend.util_get_uuid_from_username(yahoo_id_recipient)
if recipient_uuid is None:
return web.HTTPInternalServerError(text = '')
recipient_head = backend._load_user_record(recipient_uuid)
if recipient_head is None or recipient_head.status.substatus is Substatus.Offline:
return web.HTTPInternalServerError(text = '')
message = util.misc.arbitrary_decode(ymsg_data.get(b'14') or b'')
file_path_raw = ymsg_data.get(b'27') # type: Optional[bytes]
file_len = util.misc.arbitrary_decode(ymsg_data.get(b'28') or b'0')
# https://github.com/ifwe/digsby/blob/master/digsby/src/yahoo/yfiletransfer.py#L7
# Looks like the HTTP file transfer server had its own size limits (10 MB)
if file_path_raw is None or str(len(stream)) != file_len or len(stream) > (2 ** 20):
return web.HTTPInternalServerError(text = '')
file_path = util.misc.arbitrary_decode(file_path_raw)
try:
filename = Path(file_path).name
except:
return web.HTTPInternalServerError(text = '')
token = gen_salt(length = 30)
path = _get_tmp_file_storage_path(token)
path.mkdir(exist_ok = True, parents = True)
file_tmp_path = path / unquote_plus(filename)
file_tmp_path.write_bytes(stream)
upload_time = time.time()
expiry_task = req.app.loop.create_task(_store_tmp_file_until_expiry(file_tmp_path))
_tasks_by_token[token] = expiry_task
for bs_other in bs.backend._sc.iter_sessions():
if bs_other.user.uuid != recipient_uuid:
continue
bs_other.evt.ymsg_on_sent_ft_http(
yahoo_id_sender, '{}?{}'.format(FILE_STORE_PATH.format(filename = quote(file_tmp_path.name)), token),
upload_time, message,
)
bs.evt.ymsg_on_upload_file_ft(yahoo_id_recipient, message)
return web.HTTPOk(text = '')
async def _store_tmp_file_until_expiry(path: Path) -> None:
await asyncio.sleep(86400)
# When a day passes, delete the file (unless it has already been deleted by
# the downloader handler; it will cancel the according task then)
shutil.rmtree(str(path), ignore_errors = True)
async def handle_yahoo_filedl(req: web.Request) -> web.Response:
filename = req.match_info['filename']
token = None
query_keys = list(req.query.keys())
if query_keys:
token = list(req.query.keys())[0]
if token is None:
return web.HTTPNotFound(text = '')
file_storage_path = _get_tmp_file_storage_path(token)
file_path = file_storage_path / unquote(filename)
try:
file_stream = file_path.read_bytes()
except FileNotFoundError:
return web.HTTPNotFound(text = '')
# Only delete temporary file if request is specifically `GET`
if req.method == 'GET':
_tasks_by_token[token].cancel()
del _tasks_by_token[token]
shutil.rmtree(file_storage_path, ignore_errors = True)
return web.Response(status = 200, headers = {
'Content-Disposition': 'attachment; filename="{}"'.format(filename),
}, body = file_stream)
async def handle_capacity(req: web.Request) -> web.Response:
return web.Response(text="COLO_CAPACITY=1\nCS_IP_ADDRESS={}".format(settings.TARGET_IP))
async def handle_gettoken(req: web.Request) -> web.Response:
backend = req.app['backend']
params = req.rel_url.query
username = params.get('login')
encoded_pwd = params.get('passwd')
pwd = unquote(encoded_pwd) if encoded_pwd is not None else None
error_int = None
uuid_val = backend.util_get_uuid_from_username(username)
user = backend.user_service.get(uuid_val) if uuid_val else None
if not username or not encoded_pwd:
error_int = "100"
elif not user:
error_int = "1235"
elif user.suspended:
error_int = "1218"
else:
token_tpl = _login(req, username, pwd, lifetime=86400)
if token_tpl is None:
error_int = "1212"
else:
token, _ = token_tpl
if error_int:
return web.Response(text=error_int)
else:
return web.Response(text="0\r\nymsgr={}\r\npartnerid=0".format(token))
async def handle_login(req: web.Request) -> web.Response:
backend = req.app['backend']
params = req.rel_url.query
token = params.get('token')
tpl = backend.login_auth_service.get_token('ymsg/cookie', token)
crumb = util.misc.generate_random_string(chars=32)
crumbstr = crumb.decode('utf-8')
y_cookie = Y_COOKIE_TEMPLATE.format(encodedname=_encode_yahoo_id(token))
t_cookie = T_COOKIE_TEMPLATE.format(token=token)
import uuid
ssl_cookie = str(uuid.uuid4())
b_cookie = str(uuid.uuid4())
if tpl is not None:
resp = render(req, 'ymsg:auth/ok.tmpl', {
'crumb': crumbstr,
't_cookie': t_cookie,
'y_cookie': y_cookie,
'ssl_cookie': ssl_cookie,
'b_cookie': b_cookie
})
resp.set_cookie('T', t_cookie, path='/')
resp.set_cookie('Y', y_cookie, path='/',)
resp.set_cookie('SSL', ssl_cookie, path='/')
resp.set_cookie('B', b_cookie, path='/')
return resp
else:
return web.Response(text="100")
async def handle_ncclogin(req: web.Request) -> web.Response:
backend = req.app['backend']
params = req.rel_url.query
username = params.get('login')
encoded_pwd = params.get('passwd')
pwd = unquote(encoded_pwd)
uuid = backend.util_get_uuid_from_username(username)
user = backend.user_service.get(uuid) if uuid else None
detail = backend._load_detail(user)
if settings.DEBUG:
print(f"Username: {username}")
print(f"Encoded Password: {encoded_pwd}")
print(f"Decoded Password: {pwd}")
print(f"UUID: {uuid}")
print(f"User: {user}")
contacts = detail.contacts
cs = list(contacts.values())
cs_fl = [c for c in cs if c.lists & ContactList.FL and not c.lists & ContactList.BL]
contact_group_list = []
for grp in detail._groups_by_id.values():
contact_list = []
for c in cs_fl:
for group in c._groups.copy():
if group.id == grp.id:
contact_list.append(c.head.username)
if contact_list:
contact_group_list.append(f"{grp.name}:{','.join(contact_list)}\n")
# Handle contacts that aren't part of any groups
no_group_contacts = [c.head.username for c in cs_fl if not c._groups]
if no_group_contacts:
contact_group_list.append(f"(No Group):{','.join(no_group_contacts)}\n")
contact_list_format = ''.join(contact_group_list)
ignore_list = [c.head.username for c in cs if c.lists & ContactList.BL]
ignore_list_format = ','.join(ignore_list)
if user is None:
return web.Response(status=500, text="Internal Server Error")
else:
return web.Response(text=(
f"OK\r\n"
f"BEGIN BUDDYLIST\r\n{contact_list_format}END BUDDYLIST\r\n"
f"BEGIN IGNORELIST\r\n{ignore_list_format}\r\nEND IGNORELIST\r\n"
f"BEGIN IDENTITIES\r\n{username}\r\nEND IDENTITIES\r\n"
f"Mail=1\r\nLogin={username}"
))
async def handle_banneradredir(req: web.Request) -> web.Response:
return web.HTTPFound(f'http://{settings.TARGET_HOST}/ads/banner')
def _get_tmp_file_storage_path(token: str) -> Path:
return Path('storage/file') / token
def _parse_cookies(
req: web.Request, backend: Backend, y: Optional[str] = None, t: Optional[str] = None,
) -> Tuple[Optional[str], Optional[BackendSession]]:
cookies = req.cookies
if None in (y,t):
y_cookie = cookies.get('Y')
t_cookie = cookies.get('T')
else:
y_cookie = y
t_cookie = t
return (backend.auth_service.get_token('ymsg/cookie', y_cookie or ''), backend.auth_service.get_token('ymsg/cookie', t_cookie or ''))
def _login(req: web.Request, username: str, pwd: str, lifetime: int = 86400) -> Optional[Tuple[str, datetime]]:
backend: Backend = req.app['backend']
uuid_val = backend.user_service.login_with_username(username, pwd)
if uuid_val is None:
return None
token_tuple = backend.login_auth_service.create_token('ymsg/cookie', [uuid_val, None], lifetime=lifetime)
if token_tuple is None:
return None
token, expiry = token_tuple
return (token, expiry)
def render(req: web.Request, tmpl_name: str, ctxt: Optional[Dict[str, Any]] = None, status: int = 200) -> web.Response:
if tmpl_name.endswith('.xml'):
content_type = 'text/xml'
else:
content_type = 'text/html'
tmpl = req.app['jinja_env'].get_template(tmpl_name)
content = tmpl.render(**(ctxt or {})).replace('\n', '\r\n')
return web.Response(status = status, content_type = content_type, text = content)
+223
View File
@@ -0,0 +1,223 @@
from typing import Tuple, Any, Iterable, List
from enum import IntEnum
from util.misc import DefaultDict, MultiDict, arbitrary_encode
from core.backend import BackendSession
from core.models import Substatus
import settings
class YMSGService(IntEnum):
LogOn = 0x01
LogOff = 0x02
IsAway = 0x03
IsBack = 0x04
Message = 0x06
IDActivate = 0x07
IDDeactivate = 0x08
UserStat = 0x0A
ContactNew = 0x0F
AddIgnore = 0x11
PingConfiguration = 0x12
SystemMessage = 0x14
SkinName = 0x15
ClientHostStats = 0x16
MassMessage = 0x17
ConfInvite = 0x18
ConfLogon = 0x19
ConfDecline = 0x1A
ConfLogoff = 0x1B
ConfAddInvite = 0x1C
ConfMsg = 0x1D
MessageV2 = 0x27
AvatarOld = 0xBD
Avatar = 0xC7
FileTransfer = 0x46
VoiceChat = 0x4A
Notify = 0x4B
Handshake = 0x4C
P2PFileXfer = 0x4D
P2PFileXfer8 = 0xDC
P2PPhotoSharing = 0xD2
PeerToPeer = 0x4F
VideoChat = 0x50
AuthResp = 0x54
List = 0x55
Auth = 0x57
FriendAdd = 0x83
FriendRemove = 0x84
Ignore = 0x85
ContactDeny = 0x86
GroupRename = 0x89
Ping = 0x8A
ChatJoin = 0x96
IsInvisible = 0xC5
StatusUpdate = 0xC6
StatusUpdate2 = 0xF0
# `static var YES_CHAT_PING = 161;` 161 = 0xA1
# Yahoo! Messenger 9.0's `desktopHub` SWF seems to list a lot of YMSG service codes and field defs in its code. :p
ChatPing = 0xA1
ChatSession = 0xD4
ContactRegroup = 0xE7
# Documented by the Yahsmosis project (https://www.autoitscript.com/forum/topic/142448-help-with-yahomosis/) - this appears to be used when a protocol-level error occurs (protocol version "cloaking", bad `Y`/`T` cookies, invalid Yahoo ID, invalid key/value pairs, etc.). Unsure what protocol version this was first placed into or how far back in the protocol version Yahoo's servers decided to send this on (A Wireshark capture indicates as far back as YMSG12)
ProtocolError = 0x07D1
class YMSGStatus(IntEnum):
# Available/Client Request
Available = 0x00000000
# BRB/Server Response
BRB = 0x00000001
Busy = 0x00000002
# "Not at Home"/BadUsername
NotAtHome = 0x00000003
NotAtDesk = 0x00000004
# "Not in Office"/OfflineMessage/MultiPacket
NotInOffice = 0x00000005
OnPhone = 0x00000006
OnVacation = 0x00000007
OutToLunch = 0x00000008
SteppedOut = 0x00000009
# Dunno when this is used, but the `PeerToPeer` service sends this according to Pidgin
P2P = 0x0000000B
Invisible = 0x0000000C
Bad = 0x0000000D
Locked = 0x0000000E
Typing = 0x00000016
Custom = 0x00000063
Idle = 0x000003E7
WebLogin = 0x5A55AA55
Offline = 0x5A55AA56
LoginError = 0xFFFFFFFF
@classmethod
def ToSubstatus(cls, ymsg_status: 'YMSGStatus') -> Substatus:
return _ToSubstatus[ymsg_status]
@classmethod
def FromSubstatus(cls, substatus: Substatus) -> 'YMSGStatus':
return _FromSubstatus[substatus]
_ToSubstatus = DefaultDict(Substatus.Busy, {
YMSGStatus.Offline: Substatus.Offline,
YMSGStatus.Available: Substatus.Online,
YMSGStatus.BRB: Substatus.BRB,
YMSGStatus.Busy: Substatus.Busy,
YMSGStatus.Idle: Substatus.Idle,
YMSGStatus.Invisible: Substatus.Invisible,
YMSGStatus.NotAtHome: Substatus.NotAtHome,
YMSGStatus.NotAtDesk: Substatus.NotAtDesk,
YMSGStatus.NotInOffice: Substatus.NotInOffice,
YMSGStatus.OnPhone: Substatus.OnPhone,
YMSGStatus.OutToLunch: Substatus.OutToLunch,
YMSGStatus.SteppedOut: Substatus.SteppedOut,
YMSGStatus.OnVacation: Substatus.OnVacation,
YMSGStatus.Locked: Substatus.Away,
YMSGStatus.LoginError: Substatus.Offline,
YMSGStatus.Bad: Substatus.Offline,
})
_FromSubstatus = DefaultDict(YMSGStatus.Bad, {
Substatus.Offline: YMSGStatus.Offline,
Substatus.Online: YMSGStatus.Available,
Substatus.Busy: YMSGStatus.Busy,
Substatus.Idle: YMSGStatus.Idle,
Substatus.BRB: YMSGStatus.BRB,
Substatus.Away: YMSGStatus.NotAtHome,
Substatus.OnPhone: YMSGStatus.OnPhone,
Substatus.OutToLunch: YMSGStatus.OutToLunch,
Substatus.Invisible: YMSGStatus.Invisible,
Substatus.NotAtHome: YMSGStatus.NotAtHome,
Substatus.NotAtDesk: YMSGStatus.NotAtDesk,
Substatus.NotInOffice: YMSGStatus.NotInOffice,
Substatus.OnVacation: YMSGStatus.OnVacation,
Substatus.SteppedOut: YMSGStatus.SteppedOut,
})
KVSType = MultiDict[bytes, bytes]
EncodedYMSG = Tuple[YMSGService, YMSGStatus, KVSType]
def build_p2p_msg_packet(bs: BackendSession, sess_id: int, p2p_dict: KVSType) -> Iterable[EncodedYMSG]:
user_to = bs.user
p2p_conn_dict = MultiDict([
(b'4', p2p_dict.get(b'4') or b''),
(b'5', arbitrary_encode(user_to.username)),
])
#p2p_conn_dict.add(b'11', binascii.hexlify(struct.pack('!I', sess_id)).decode().upper().encode('utf-8'))
p2p_conn_dict.add(b'11', b'0')
if p2p_dict.get(b'12') is not None: p2p_conn_dict.add(b'12', p2p_dict.get(b'12') or b'')
if p2p_dict.get(b'13') is not None: p2p_conn_dict.add(b'13', p2p_dict.get(b'13') or b'')
p2p_conn_dict.add(b'49', p2p_dict.get(b'49') or b'')
if p2p_dict.get(b'61') is not None: p2p_conn_dict.add(b'61', p2p_dict.get(b'61') or b'')
yield (YMSGService.PeerToPeer, YMSGStatus.BRB, p2p_conn_dict)
def build_ft_packet(bs: BackendSession, sess_id: int, xfer_dict: KVSType) -> Iterable[EncodedYMSG]:
user_to = bs.user
ft_dict = MultiDict([
(b'5', arbitrary_encode(user_to.username)),
(b'4', xfer_dict.get(b'1') or xfer_dict.get(b'4') or b'')
])
ft_type = xfer_dict.get(b'13')
if ft_type is not None: ft_dict.add(b'13', ft_type)
if ft_type == b'1':
if xfer_dict.get(b'27') is not None: ft_dict.add(b'27', xfer_dict.get(b'27') or b'')
if xfer_dict.get(b'28') is not None: ft_dict.add(b'28', xfer_dict.get(b'28') or b'')
if xfer_dict.get(b'20') is not None: ft_dict.add(b'20', xfer_dict.get(b'20') or b'')
if xfer_dict.get(b'53') is not None: ft_dict.add(b'53', xfer_dict.get(b'53') or b'')
if xfer_dict.get(b'14') is not None: ft_dict.add(b'14', xfer_dict.get(b'14') or b'')
if xfer_dict.get(b'54') is not None: ft_dict.add(b'54', xfer_dict.get(b'54') or b'')
if ft_type in (b'2',b'3'):
# For shared files
if xfer_dict.get(b'27') is not None: ft_dict.add(b'27', xfer_dict.get(b'27') or b'')
if xfer_dict.get(b'53') is not None: ft_dict.add(b'53', xfer_dict.get(b'53') or b'')
# For P2P messaging
if xfer_dict.get(b'2') is not None: ft_dict.add(b'2', xfer_dict.get(b'2') or b'')
if xfer_dict.get(b'11') is not None:
#ft_dict.add(b'11', binascii.hexlify(struct.pack('!I', sess_id)).decode().upper().encode('utf-8'))
ft_dict.add(b'11', b'0')
if xfer_dict.get(b'12') is not None: ft_dict.add(b'12', xfer_dict.get(b'12') or b'')
if xfer_dict.get(b'60') is not None: ft_dict.add(b'60', xfer_dict.get(b'60') or b'')
if xfer_dict.get(b'61') is not None: ft_dict.add(b'61', xfer_dict.get(b'61') or b'')
if ft_type == b'5':
if xfer_dict.get(b'54') is not None: ft_dict.add(b'54', xfer_dict.get(b'54') or b'')
if ft_type == b'6':
if xfer_dict.get(b'20') is not None: ft_dict.add(b'20', xfer_dict.get(b'20') or b'')
if xfer_dict.get(b'53') is not None: ft_dict.add(b'53', xfer_dict.get(b'53') or b'')
if xfer_dict.get(b'54') is not None: ft_dict.add(b'54', xfer_dict.get(b'54') or b'')
if ft_type == b'9':
if xfer_dict.get(b'53') is not None: ft_dict.add(b'53', xfer_dict.get(b'53') or b'')
if xfer_dict.get(b'49') is not None: ft_dict.add(b'49', xfer_dict.get(b'49') or b'')
yield (YMSGService.P2PFileXfer, YMSGStatus.BRB, ft_dict)
def build_http_ft_packet(bs: BackendSession, sender: str, url_path: str, upload_time: float, message: str) -> Iterable[Any]:
user = bs.user
yield (YMSGService.FileTransfer, YMSGStatus.BRB, MultiDict([
(b'1', arbitrary_encode(user.username)),
(b'5', arbitrary_encode(sender)),
(b'4', arbitrary_encode(user.username)),
(b'14', arbitrary_encode(message)),
(b'38', str(upload_time + 86400).encode('utf-8')),
(b'20', arbitrary_encode('http://{}{}'.format(settings.USERSTORAGE_HOST, url_path))),
]))
def split_to_chunks(s: str, count: int) -> List[str]:
i = 0
j = 0
final = []
while i < len(s):
j += count
if j > len(s):
j = len(s)
final.append(s[i:j])
i += count
return final
+52
View File
@@ -0,0 +1,52 @@
--- YMSG8 ---
http://web.archive.org/web/20010801212305/http://www.venkydude.com:80/articles/yahoo.htm (Archive of http://www.venkydude.com/articles/yahoo.htm from 2001)
--- YMSG9/10 ---
http://libyahoo2.sourceforge.net/ymsg-9.txt (YMSG9)
http://www.engr.mun.ca/~sircar/ymsg9.htm (YMSG9)
http://web.archive.org/web/20020806213426/http://www.venkydude.com:80/articles/yahoo.htm (YMSG9, Archive of http://www.venkydude.com/articles/yahoo.htm from 2002)
http://web.archive.org/web/20021203013158/http://www.cse.iitb.ac.in/varunk/YahooProtocol.php (YMSG9)
http://web.archive.org/web/20030811172816/http://venkydude.com:80/articles/yahoo.htm (YMSG10, Archive of http://www.venkydude.com/articles/yahoo.htm from 2003)
--- YMSG11 ---
http://web.archive.org/web/20031205031525/venkydude.com/articles/yahoo.htm (Archive of http://www.venkydude.com/articles/yahoo.htm from 2003, first revision)
https://web.archive.org/web/20100615081758/http://www.venkydude.com/articles/yahoo.htm (Last archive of http://www.venkydude.com/articles/yahoo.htm from 2010, second revision)
--- YMSG12 ---
http://web.archive.org/web/20070910203807/http://www.ycoderscookbook.com/index.html
--- YMSG16 ---
https://kb.imfreedom.org/protocols/yahoo/
http://web.archive.org/web/20090623064155/carbonize.co.uk/ymsg16.html (Site no longer exists)
http://web.archive.org/web/20090912133315/http://www.adrensoftware.com/tools/yahoo_v16_protocol.php
--- YMSG18 ---
http://web.archive.org/web/20120626082942/http://www.adrensoftware.com/tools/yahoo_v16_protocol.php
--- Misc. ---
https://github.com/ifwe/digsby/blob/master/digsby/src/yahoo/
https://bitbucket.org/pidgin/main/src/soc.2006.msnp13/src/protocols/yahoo/
--- Yahoo! Messenger client archives ---
http://www.oldversion.com/windows/yahoo-messenger/
------------------------------------------------------------------------------
Return codes for YMSG service 0x7D1 (Protocol-level Failure; taken from Yahsmosis `YMSGLib.INI`):
1004=Protocol Mismatch
1005=Unknown Data/Invalid Field number
1006=Incompatible software (or Cloaking)
; ^ happens when sending the old packets (eg: when using cloak)
1007=Invalid Protocol Version or Authorization
1011=Cookies Expired or Invalid
1013=Username format not acceptable.
: ^ ?
1014=Session expired or terminated
; ^ occurs after 52
1015=Session expired or invalid
;^ occurs after 1011 or 42 or 1051
1017=Authorized failed, a session was already active
1020=Invalid VendorID?
1051=Cookies Expired?
+1966
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
<addressbook time="{{ epoch }}">
<farm id="1646"/>
{{ records }}
</addressbook>
@@ -0,0 +1,75 @@
<audiblemenu signature="YwYF9wY51P0MW+oG9U17PKb70MPP9BjlKlIkVuCThYVzcs2+OB2dK4f1jDy9vhj334eJbansjC8S+N5xSEi/IZh6/34KcwzQPhB1/bSBKVCoRBi+qA0DlLE/I1KpYyiYNWhNohLlEqJRU8Glw8YN/Wwq3U8ZI99XFMrzZd3F/Tf6EiloTZYGDcv2mgFeXTcVORpClBWAfbpGZ8lKNt8xRixHH3u/AGzTE3C1p+EwNXBtzO6NpF3GdqsLv8Z8W9KUP0WNvdn3YN/9kd7OG2ZWlHni0Tf8zXxVvz0t8xpfegHl1YfMbJIeoGyffPIyJQVyqL3teqrq2X4TlsX8DZs6DA==">
<Menu text="See all Audibles">
<set id="base.us.hello" img="1" text="Hellos"/>
<set id="base.us.goodbyes" img="1" text="Goodbyes"/>
<set id="base.us.emoticons2" img="1" text="Emoticons"/>
<set id="base.us.flirts" img="1" text="Flirts" ver="1.2"/>
<set id="base.us.work" img="1" text="Work" ver="1.1"/>
<set id="base.us.insults" img="1" text="Insults"/>
<set id="base.us.yodel" img="1" text="Yahoo! Yodels" ver="4.0"/>
<set id="base.us.emoticats" img="1" text="Emoticats" ver="5.0"/>
<set id="base.us.halloween" img="1" text="Halloween" ver="1.0"/>
<Menu text="Game Audibles">
<set id="base.us.losing" img="1" text="Game - Losing" ver="1.2"/>
<set id="base.us.taunt" img="1" text="Game - Taunts"/>
<set id="base.us.winning" img="1" text="Game - Winning"/>
</Menu>
<set id="base.us.htf" img="1" text="Happy Tree Friends"/>
<set id="base.us.contender" img="1" text="The Contender"/>
<set id="base.us.madonna" img="1" text="Madonna"/>
<set id="base.us.chilis" img="1" text="Chili&apos;s"/>
<set id="base.us.valentines" img="1" text="Valentines" ver="1.0"/>
<Menu text="International">
<Menu text="Chinese">
<set id="base.cn.emotion" img="1" text="China"/>
<set id="base.tw.smiley" img="1" text="Taiwan"/>
</Menu>
<Menu text="English">
<set id="base.uk.general" img="1" text="UK"/>
</Menu>
<set id="base.de.siedler5" img="1" text="German"/>
<Menu text="Indic Languages">
<set id="base.in.gujarati" img="1" text="Gujarati"/>
<set id="base.in.malayalam" img="1" text="Malayalam"/>
<set id="base.in.marathi" img="1" text="Marathi"/>
<set id="base.in.masti" img="1" text="Masti"/>
<set id="base.in.punjabi" img="1" text="Punjabi"/>
<set id="base.in.tamil" img="1" text="Tamil"/>
</Menu>
<Menu text="Indonesia">
<set id="base.id.changcuters" img="1" text="Changcuters"/>
<set id="base.aa.indonesia2" img="1" text="Indonesian"/>
</Menu>
<set id="base.aa.malaysia2" img="1" text="Malay"/>
<Menu text="Spanish">
<Menu text="Neutral">
<set id="base.es.generic" img="1" text="Genéricos"/>
<set id="base.es.flirts" img="1" text="Coqueteo"/>
<set id="base.es.hello" img="1" text="Hola"/>
<set id="base.es.goodbyes" img="1" text="Despedidas"/>
<set id="base.es.winning" img="1" text="Ganando"/>
<set id="base.es.losing" img="1" text="Perdiendo"/>
</Menu>
<Menu text="Argentina">
<set id="base.ar.valentines2" img="1" text="Enamorados"/>
<set id="base.ar.friends" img="1" text="Amigos"/>
<set id="base.ar.hello" img="1" text="¡Hola!"/>
<set id="base.ar.goodbye" img="1" text="Chau"/>
<set id="base.ar.yesno" img="1" text="¿Sí o no?"/>
<set id="base.ar.moods" img="1" text="Estados de ánimo"/>
<set id="base.ar.games" img="1" text="Jugando"/>
<set id="base.ar.springtime2" img="1" text="Primavera"/>
<set id="base.ar.winter" img="1" text="Invierno"/>
<set id="base.ar.generic" img="1" text="Variados"/>
</Menu>
</Menu>
<set id="base.aa.philippines2" img="1" text="Tagalog"/>
<Menu text="Vietnam">
<set id="base.vn.yahooinvietnam" img="1" text="Yahoo! in Vietnam" ver="1.1"/>
<set id="base.aa.vietnam4" img="1" text="Vietnamese" ver="1.1"/>
</Menu>
</Menu>
</Menu>
<separator/>
<set id="RecentAudibles" img="1" text="Recently used Audibles"/>
</audiblemenu>
@@ -0,0 +1,2 @@
<chatCategories intl="us">
</chatCategories>
@@ -0,0 +1,2 @@
<countries>
</countries>
@@ -0,0 +1,2 @@
<countries>
</countries>
@@ -0,0 +1,48 @@
<filters signature="gkwxLK0AFQJLQ7OOCdw2waHSj+Be+vC21j1FavJ/aiK6v2P073+nY8htit8T22Pa3jdpbSaZe/9qmEXNBhMy4Uy1fF9QxoopUsLglUTDAlgR3UkL/r9P9Zytoh+gQoABeEf3Q/Us2O/AEkXeviW541qdfAEnMd4KqQem3d6b19wj5BtXAaFD4QG7cY7SwfogDpXj9sCudnDBGg19udHvIsE62ottiILdk5+qvEF6DgL/Z54+PYIeebk8qoWXFX1njmnXTX603+FwSrnY0M6SyIIIXsZy88A6ZXJrp+pJYu5VaTj9RYCekAY2ogTlhanatdefILS0CCnIOfgGsQhYVw==">
<replace field="135" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*|^\s*$" with=""/>
<replace field="310" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*|^\s*$" with=""/>
<replace field="265" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*|^\s*$" with=""/>
<replace field="11" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*" with=""/>
<replace field="180" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*" with=""/>
<replace field="230" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*" with=""/>
<replace field="231" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*" with=""/>
<replace field="232" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*" with=""/>
<replace field="182" flags="3" pattern=".*[&lt;&gt;&quot;&apos;&amp;\/].*" with=""/>
<replace command="208" field="231" flags="3" pattern="http.*|www..*" with=" "/>
<replace field="14" flags="3" pattern="&lt;search[^&gt;]*&gt;.*&lt;search[^&gt;]*&gt;.*&lt;search[^&gt;]*&gt;.*&lt;search[^&gt;]*&gt;" with=" "/>
<replace field="14" flags="3" pattern="msg:" with="msg: "/>
<replace field="14" flags="3" pattern="ymsgr:-kill" with=" "/>
<replace field="14" flags="3" pattern="geocities.com/a[0-9]{4,9}" with=" "/>
<replace field="14" flags="3" pattern="rmarriso.blogspot.com" with=" "/>
<replace field="280" flags="3" pattern="&amp;#39;" with="&apos;"/>
<replace field="280" flags="3" pattern="&amp;quot;" with="&quot;"/>
<replace field="280" flags="3" pattern="&amp;." with=" "/>
<replace field="254" flags="3" pattern="[&lt;&gt;]." with=" "/>
<replace field="216" flags="3" pattern="[&lt;&gt;]." with=" "/>
<replace field="19" flags="3" pattern="msg:" with="msg: "/>
<replace field="63" flags="3" pattern="msg:" with="msg: "/>
<replace field="117" flags="3" pattern="msg:" with="msg: "/>
<replace field="57" flags="3" pattern="msg:" with="msg: "/>
<replace field="58" flags="3" pattern="msg:" with="msg: "/>
<replace field="105" flags="3" pattern="msg:" with="msg: "/>
<replace field="231" flags="3" pattern="msg:" with="msg: "/>
<replace field="27" flags="3" pattern="msg:" with="msg: "/>
<replace command="220" field="27" flags="3" pattern="msg:" with="msg: "/>
<replace field="4" flags="3" pattern="msg:" with="msg: "/>
<replace field="5" flags="3" pattern="&lt;" with=" "/>
<replace field="4" flags="3" pattern="&lt;" with=" "/>
<replace field="14" flags="3" pattern="y-source.com" with=" "/>
<replace field="19" flags="3" pattern="y-source.com" with=" "/>
<replace field="63" flags="3" pattern="y-source.com" with=" "/>
<replace field="117" flags="3" pattern="y-source.com" with=" "/>
<replace field="57" flags="3" pattern="y-source.com" with=" "/>
<replace field="58" flags="3" pattern="y-source.com" with=" "/>
<replace field="105" flags="3" pattern="y-source.com" with=" "/>
<replace field="231" flags="3" pattern="y-source.com" with=" "/>
<replace field="27" flags="3" pattern="y-source.com" with=" "/>
<replace field="4" flags="3" pattern="y-source.com" with=" "/>
<replace field="14" flags="3" pattern="geocities.com/a[0-9]{4,9}" with=" "/>
<replace field="14" flags="3" pattern="thecoolpics.com" with=" "/>
<replace field="14" flags="3" pattern="^s*s:" with=" s;"/>
<replace command="233" field="59" flags="3" pattern="[,&quot;&lt;&gt;]" with=" "/>
</filters>
@@ -0,0 +1,2 @@
<games>
</games>
@@ -0,0 +1,147 @@
<imvironments signature="E88BOvcU+WllkxR80dQOa0Cyu4++8tVzFzMTmnW7Uk+6KjwsFxBbNBKLvNp1jbNKVOx5uA5Ybzsb9T8xjSChH5mhSu3GS3LI+vGonIGZ0CJeAPkzmVedGeP9Tg//XB4I9SsTP5J6JtdS8x+DTdBPu+tywYBp05yLaewn1do4Xfx2mHDDJ+gOA/jxsAs/UTS4GpCKyS9v15Vw/c/dkX0C3A70UF4ufGxn30r2vB0EjJd33kCIqGxBX/P6Tmk+PFnIC/LZCsqv0LtlsurrnzQm2JtEg0ncVeEYvnQPxgC5khz6kNcfFNY/VxFy842S5QlJ4lLWGymA1xFnGF7o/3eQdA==">
<imvgallerypromo>
<headertext>Featured IMVironments</headertext>
<ctatext>Try it now!</ctatext>
</imvgallerypromo>
<imv gallery="1" id="hearts" name="Falling Hearts" ver="107">
<category>Love and Friendship</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/imv_hearts.gif</picture>
<description>What better way to show you care, than with an IMVironment that comes from the heart. For that extra special someone, use the buzz feature (Ctrl + G) to give your loved one a virtual kiss.</description>
<galleryName>Falling Hearts</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="leaves" name="Autumn Leaves" ver="105">
<category>Animals and Nature</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/imv_leaves.gif</picture>
<description>Watch autumn leaves fall with this IMVironment, and make them blow with the wind when you Buzz your friends (Ctrl + G).</description>
<galleryName>Autumn Leaves</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="precious" name="Precious Moments" ver="105">
<category>Love and Friendship</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/imv_precious.gif</picture>
<description>Send a smile to friends and family with this adorable Precious Moments IMVironment. Scroll through five totally cute Precious Moment backgrounds, then hit Ctrl-G to share a giggle and some instant message fun.</description>
<galleryName>Precious Moments</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="dilbert" name="Dilbert" ver="104">
<category>Comics and Characters</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/dilbert3_gallery.gif</picture>
<description>Dilbert is still stuck in his cubicle, but now he&apos;s back with a new Messenger IMV! Dilbert works on his desktop computer as Dogbert looks on. Be sure to buzz your friends (Ctrl+G) and see what Dogbert really thinks of Dilbert.</description>
<galleryName>Dilbert</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="peanuts" name="Peanuts" ver="108">
<category>Comics and Characters</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/peanuts3_gallery.gif</picture>
<description>The gang from Peanuts is back in a whole new IMVironment. Charlie Brown eagerly checks his mail, and be sure to buzz your friend to see what&apos;s waiting for him. Click to visit the Peanuts official site, or get Peanuts in your e-mail box.</description>
<galleryName>Peanuts</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="garfieldgames" name="Garfield" ver="105">
<category>Comics and Characters</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/imv_garfield2.gif</picture>
<description>Look who&apos;s raiding the refrigerator... it&apos;s your old pal Garfield! Andy our friend can help him rummage for a tasty snack simply by typing a message to you. Be sure to buzz a friend (Ctrl + G) to see another snacktime visitor.</description>
<galleryName>Garfield</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="fishtank" name="Fishtank" ver="105">
<category>Animals and Nature</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/imv_fish.gif</picture>
<description>Looking for a virtual fish tank to calm your day? Download our Fishtank IMVironment and watch the colorful fishes swim around effortlessly. A great way to relieve stress as you chat with a friend or have it open in your desktop while you work.</description>
<galleryName>Fishtank</galleryName>
</imv>
<imv gallery="1" id="snowflake" name="Snowflakes" ver="108">
<category>Animals and Nature</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/imv_snow.gif</picture>
<description>Start a virtual snowball fight with your friends using the Snowflake IMV! Just open it up, hit buzz (Control + G) and watch the snowballs fly!</description>
<galleryName>Snowflake</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="doodle" name="Doodle" ver="109">
<category>Interactive Fun</category>
<picture>https://s.yimg.com/lq/i/mesg/insider/doodle_gallery.gif</picture>
<description>Everyone has a little bit of an artistic streak in them. Express yourself, and create a great work of art with your friends with this neat IMVironment! After you&apos;re done, you can print your work of art to show off to your friends and family. Or if drawing isn&apos;t your thing, there are some neat games to try.</description>
<galleryName>Doodle</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="doodle" name="Doodle" ver="109">
<category>Yahoo Tools</category>
<picture>https://s.yimg.com/lq/i/mesg/insider/doodle_gallery.gif</picture>
<description>Everyone has a little bit of an artistic streak in them. Express yourself, and create a great work of art with your friends with this neat IMVironment! After you&apos;re done, you can print your work of art to show off to your friends and family. Or if drawing isn&apos;t your thing, there are some neat games to try.</description>
<galleryName>Doodle</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" id="search" name="Yahoo Search" ver="104">
<category>Yahoo Tools</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/search_gallery.gif</picture>
<description>Looking for something on the Internet with your friends? Why paste links back and forth when you can search together in an IMVironment? Search, then share the links with your friends, all in one message window!</description>
<galleryName>Yahoo Search</galleryName>
<feature>buzz</feature>
</imv>
<imv gallery="1" gallerypromo="5" id="emoticats" name="Emoticats" ver="15">
<promo ver="2">https://s.yimg.com/pu/dl/imv/promo/emoticats.cab</promo>
<category>Yahoo Tools</category>
<topFive>1</topFive>
<picture>https://s.yimg.com/lq/i/mesg/imv/emoticats_gallery.jpg</picture>
<description>Love emoticons? Now you can adopt an &quot;Emoticat&amp;reg;&quot; as your display image or just insert one into your conversation. Open it up and start turning the cat photos into emoticons by zooming in on each cat!</description>
<galleryName>Emoticats&amp;reg;</galleryName>
</imv>
<imv gallery="1" id="emoticats" name="Emoticats" ver="15">
<category>Animals and Nature</category>
<topFive>1</topFive>
<picture>https://s.yimg.com/lq/i/mesg/imv/emoticats_gallery.jpg</picture>
<description>Love emoticons? Now you can adopt an &quot;Emoticat&amp;reg;&quot; as your display image or just insert one into your conversation. Open it up and start turning the cat photos into emoticons by zooming in on each cat!</description>
<galleryName>Emoticats&amp;reg;</galleryName>
</imv>
<imv gallery="1" gallerypromo="4" id="footballkick" name="Football Kick" ver="4">
<category>Yahoo Tools</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/footballkick_gallery.jpg</picture>
<description>Think you&apos;ve got a winning foot? Challenge your friend to a game of Football Kick to determine a champion. Use the arrow keys to adjust the direction of the ball, and then hold the spacebar down to let it fly. The first player to score 3 points wins!</description>
<galleryName>Football Kick</galleryName>
</imv>
<imv gallery="1" id="footballkick" name="Football Kick" ver="4">
<category>Interactive Fun</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/footballkick_gallery.jpg</picture>
<description>Think you&apos;ve got a winning foot? Challenge your friend to a game of Football Kick to determine a champion. Use the arrow keys to adjust the direction of the ball, and then hold the spacebar down to let it fly. The first player to score 3 points wins!</description>
<galleryName>Football Kick</galleryName>
</imv>
<imv gallery="1" id="footballkick" name="Football Kick" ver="4">
<category>Sports and Games</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/footballkick_gallery.jpg</picture>
<description>Think you&apos;ve got a winning foot? Challenge your friend to a game of Football Kick to determine a champion. Use the arrow keys to adjust the direction of the ball, and then hold the spacebar down to let it fly. The first player to score 3 points wins!</description>
<galleryName>Football Kick</galleryName>
</imv>
<imv gallery="1" gallerypromo="3" id="purpleleaves" name="Purple Leaves" ver="3">
<category>Animals and Nature</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/purpleleaves_gallery.jpg</picture>
<description>Fall is here and Purple is this season&apos;s biggest color. Celebrate fall by watching these gently falling purple leaves flutter in the breeze. Be stylish and stress-free at the same time.</description>
<galleryName>Purple Leaves</galleryName>
</imv>
<imv gallery="1" id="purpleleaves" name="Purple Leaves" ver="3">
<category>Purple</category>
<picture>https://s.yimg.com/lq/i/mesg/imv/purpleleaves_gallery.jpg</picture>
<description>Fall is here and Purple is this season&apos;s biggest color. Celebrate fall by watching these gently falling purple leaves flutter in the breeze. Be stylish and stress-free at the same time.</description>
<galleryName>Purple Leaves</galleryName>
</imv>
<imv gallery="1" gallerypromo="2" id="flickr" name="Flickr" ver="12">
<promo ver="4">https://s.yimg.com/pu/dl/imv/promo/flickr.cab</promo>
<category>Yahoo Tools</category>
<topFive>3</topFive>
<picture>https://s.yimg.com/lq/i/mesg/imv/flickr_gallery2.jpg</picture>
<description>From snowy landscapes in the winter, to sunny beaches in the summer, spice up your conversation with the some of the most interesting seasonal photos on Flickr.</description>
<galleryName>Flickr</galleryName>
</imv>
<imv gallery="1" gallerypromo="1" id="thethread" name="The Thread" ver="53">
<promo ver="30">https://s.yimg.com/pu/dl/imv/promo/thethreadgeneric.cab</promo>
<category>Beauty and Fashion</category>
<topFive>2</topFive>
<picture>https://s.yimg.com/pu/asset/imv/thethreadgeneric_gallery.jpg</picture>
<description>Do you love celebrity style? Then watch the Thread while you chat. The Thread brings you the hottest styles from your favorite TV shows, movies and music videos.</description>
<galleryName>The Thread</galleryName>
</imv>
<imv id="ourworld" name="ourWorld" ver="10">
<promo ver="1">https://s.yimg.com/pu/dl/imv/promo/ourworld.cab</promo>
<category>Interactive Fun</category>
</imv>
</imvironments>
+112
View File
@@ -0,0 +1,112 @@
<sms signature="Q7ZR0QLgniIrASEJlPHHPeeaRNSjWbSEBDyhqSlQT8A7GmXsQnFJkD17nnevfcNfHFmQcxmCHIAELuMlBpiQuLKUgTBrYMRcdZczBCpgJF+02kVPZmlb1TdY5CnrP1rtKHUDWrgZGMn4YhPZzo8LhS1qCfbXc65/y3Q5Lc6xguf7kkYZ2uwpisv+0ofmUIyn9JF8A/8GSXtxvbKttsdL1nejOyVRCu4QPbtgDUqc9TcvQdU3TLF+cuI2mWSJr9xZFWGJVIu9LtmEcPpVz/fYKA+clrC4qXTZDuZhzLy+UWFiKOCRMTRKZczzg8u6e3RTdIG6Qi4BMbA1gizl0ojHUw==">
<countries>
<intl code="1" id="us" maxdigits="10" mindigits="10"/>
<intl code="1" id="ca" maxdigits="10" mindigits="10"/>
<intl code="91" id="in" maxdigits="10" mindigits="10"/>
<intl code="60" maxdigits="9" mindigits="9" name="Malaysia"/>
<intl code="965" maxdigits="7" mindigits="7" name="Kuwait"/>
<intl code="962" maxdigits="9" mindigits="9" name="Jordan"/>
<intl code="974" maxdigits="7" mindigits="7" name="Qatar"/>
<intl code="94" maxdigits="9" mindigits="9" name="Sri Lanka"/>
<intl code="66" maxdigits="9" mindigits="8" name="Thailand"/>
<intl code="65" id="sg" maxdigits="8" mindigits="8"/>
<intl code="62" maxdigits="11" mindigits="8" name="Indonesia"/>
<intl code="63" maxdigits="10" mindigits="10" name="Philippines"/>
<intl code="880" maxdigits="10" mindigits="10" name="Bangladesh"/>
<intl code="234" maxdigits="8" mindigits="8" name="Nigeria"/>
<intl code="84" maxdigits="12" mindigits="9" name="Vietnam"/>
</countries>
<carrierslist>
<carrier id="pcsms.in.acl" maxchars="159" name="ACL India"/>
<carrier id="pcsms.us.boost" maxchars="159" name="Sprint Boost"/>
<carrier id="pcsms.us.att" maxchars="159" name="AT&amp;T"/>
<carrier id="pcsms.us.cingular" maxchars="156" name="Cingular"/>
<carrier id="pcsms.us.verizon" maxchars="159" name="Verizon"/>
<carrier id="pcsms.us.virgin" maxchars="159" name="Virgin"/>
<carrier id="pcsms.us.tmobile" maxchars="159" name="T-Mobile"/>
<carrier id="pcsms.us.alltel" maxchars="159" name="Alltel"/>
<carrier id="pcsms.us.sprintpcs" maxchars="159" name="Sprint PCS"/>
<carrier id="pcsms.us.sprint_iden" maxchars="159" name="Sprint IDEN(Nextel)"/>
<carrier id="pcsms.us.uscell" maxchars="159" name="US Cellular"/>
<carrier id="pcsms.ca.rogers" maxchars="159" name="Rogers Wireless"/>
<carrier id="hutchinson.in" maxchars="159" name="Orange Mumbai"/>
<carrier id="hutchinson.in.mum" maxchars="159" name="Orange Mumbai (Pre-Paid)"/>
<carrier id="hutchinson.in.del" maxchars="159" name="Hutch Delhi"/>
<carrier id="hutchinson.in.guj" maxchars="159" name="Hutch Gujarat"/>
<carrier id="hutchinson.in.kol" maxchars="159" name="Hutch Kolkata"/>
<carrier id="hutchinson.in.south" maxchars="159" name="Hutch South"/>
<carrier id="hutchinson.in.south2" maxchars="159" name="Hutch South"/>
<carrier id="hutchinson.in.adil" maxchars="159" name="Hutch ADIL"/>
<carrier id="escotel.in.up" maxchars="159" name="Escotel UP"/>
<carrier id="escotel.in.har" maxchars="159" name="Escotel Haryana"/>
<carrier id="escotel.in.ker" maxchars="159" name="Escotel Kerala"/>
<carrier id="spice.in.kk" maxchars="159" name="Spice Karnataka"/>
<carrier id="spice.in.pun" maxchars="159" name="Spice Punjab"/>
<carrier id="aircel.in.north" maxchars="159" name="Aircel North"/>
<carrier id="airtel.in.del" maxchars="159" name="Airtel Delhi"/>
<carrier id="airtel.in.pun" maxchars="159" name="Airtel Punjab"/>
<carrier id="airtel.in.ap" maxchars="159" name="Airtel AP"/>
<carrier id="airtel.in.kk" maxchars="159" name="Airtel Karnataka"/>
<carrier id="airtel.in.mum" maxchars="159" name="Airtel Mumbai"/>
<carrier id="airtel.in.kol" maxchars="159" name="Airtel Kolkata"/>
<carrier id="airtel.in.chen" maxchars="159" name="Airtel Chennai"/>
<carrier id="idea.in" maxchars="159" name="Idea India"/>
<carrier id="idea.in.ap" maxchars="159" name="Idea AP"/>
<carrier id="idea.in.mp" maxchars="159" name="Idea MP"/>
<carrier id="idea.in.guj" maxchars="159" name="Idea Gujarat"/>
<carrier id="idea.in.del" maxchars="159" name="Idea Delhi"/>
<carrier id="idea.in.mah" maxchars="159" name="Idea Maharashtra"/>
<carrier id="reliance.in" maxchars="159" name="Reliance India"/>
<carrier id="tatatele.in" maxchars="159" name="TataTele India"/>
<carrier id="tatagsm.in" maxchars="159" name="Tata GSM India"/>
<carrier id="rpg.in" maxchars="159" name="RPG India"/>
<carrier id="oasis.in" maxchars="159" name="Oasis India"/>
<carrier id="mtnl.in.mum" maxchars="159" name="MTNL Mumbai"/>
<carrier id="mtnl.in.del" maxchars="159" name="MTNL Delhi"/>
<carrier id="reliance" maxchars="159" name="Reliance"/>
<carrier id="bpl.in.mum" maxchars="159" name="BPL Mumbai"/>
<carrier id="bpl.in.mah" maxchars="159" name="BPL Maharashtra"/>
<carrier id="bpl.in.ker" maxchars="159" name="BPL Kerala"/>
<carrier id="bpl.in.tn" maxchars="159" name="BPL Tamil Nadu"/>
<carrier id="bsnl.in.punjab" maxchars="159" name="BSNL Punjab"/>
<carrier id="reliance.in.infocomm" maxchars="159" name="Reliance Infocomm"/>
<carrier id="reliance.in.infocomm6" maxchars="159" name="Reliance Infocomm6"/>
<carrier id="kuwataniya" maxchars="159" name="Wataniya Kuwait"/>
<carrier id="pcsms.th.ais" maxchars="159" name="AIS Thailand"/>
<carrier id="pcsms.th.dtac" maxchars="159" name="DTAC Thailand"/>
<carrier id="pcsms.ph.smart" maxchars="159" name="Smart Philippines"/>
<carrier id="globe.ph" maxchars="159" name="Globe Philippines"/>
<carrier id="telkomsel.id" maxchars="159" name="Telkomsel Indonesia"/>
<carrier id="excelcom.id" maxchars="159" name="Excelcom Indonesia"/>
<carrier id="pcsms.id.excelcom" maxchars="159" name="Excelcom Indonesia2"/>
<carrier id="pcsms.id.hutch3" maxchars="159" name="Hutch3 Indonesia"/>
<carrier id="pcsms.id.indosat" maxchars="159" name="Indosat Indonesia"/>
<carrier id="pcsms.my.celcom" maxchars="159" name="Celcom Malaysia"/>
<carrier id="pcsms.my.akn_celcom" maxchars="159" name="AKN Celcom Malaysia"/>
<carrier id="pcsms.my.akn_digi" maxchars="159" name="MyDigi Malaysia"/>
<carrier id="pcsms.vn.gapit_vinaphone" maxchars="159" name="Vinaphone Vietnam"/>
<carrier id="pcsms.vn.gapit_mobifone" maxchars="159" name="Mobifone Vietnam"/>
<carrier id="pcsms.vn.gapit_viettel" maxchars="159" name="Viettel Vietnam"/>
<carrier id="pcsms.vn.htmobile" maxchars="159" name="Htmobile Vietnam"/>
<carrier id="pcsms.vn.evntelecom" maxchars="159" name="Evntelecom Vietnam"/>
<carrier id="pcsms.vn.sfone" maxchars="159" name="Sfone Vietnam"/>
<carrier id="pcsms.yahoo.test" maxchars="159" name="Yahoo Test Carrier"/>
<carrier id="reliancegsm.in" maxchars="159" name="Reliance GSM"/>
<carrier id="airtel.in" maxchars="159" name="Airtel"/>
<carrier id="aircel.in.south" maxchars="159" name="Aircel"/>
<carrier id="idea.in.pun" maxchars="159" name="Idea"/>
<carrier id="idea.in.ker" maxchars="159" name="Idea"/>
<carrier id="reliancecdma.in" maxchars="159" name="Reliance CDMA"/>
<carrier id="bpl.in" maxchars="159" name="BPL"/>
<carrier id="pcsms.id.mobile8" maxchars="159" name="Mobile 8"/>
<carrier id="pcsms.my.maxis" maxchars="159" name="Maxis"/>
<carrier id="pcsms.ca.telus" maxchars="159" name="CA Telus"/>
<carrier id="pcsms.id.bakrie" maxchars="159" name="ID Bakrie"/>
<carrier id="pcsms.ph.sun" maxchars="159" name="Sun Cellular"/>
<carrier id="pcsms.us.test" maxchars="159" name="Yahoo US Test Carrier"/>
<carrier id="pcsms.id.smart" maxchars="159" name="ID Smart"/>
<carrier id="pcsms.vn.gtel" maxchars="159" name="Gtel"/>
<carrier id="tatacdma.in" maxchars="159" name="TataCDMA"/>
<carrier id="pcsms.ca.bell" maxchars="159" name="Bell Cannada"/>
</carrierslist>
</sms>
@@ -0,0 +1,30 @@
<system signature="Sb6CAnTIeMO6Tn6e6K+IJPM+3NDuoQ0aHddsgSOC5qRMPVe+F9DIWjYLIcxKpd3/Y34ymZPoWUfU7dKPWHJs8U6xYRgVPgEAXYVun36gkb3WgkmI/ys729iXayQ0RGOgzJ3NTKSuHOm6snlmh+5jdTvm//n+JbhisiICYklrPTcoVIofKcmUtlwtCXTMk+AjmD7sQlviJn87tSv21zIm6t4GY2zZwykyvF+n3v7sfmPQjIfhXKXgzmjG+ZYbw4EDzJ5laW5L7fNTF2XAiUAdEmqHJ3xyXTK+iPj9nS7HafyTbpL8w4Otc/nGD3CHHtQ11DDjJ8lGqMJqjipEe/4J7Q==">
<feature enabled="0" name="guestmsg"/>
<feature enabled="1" name="sms"/>
<feature enabled="1" name="login2mobile"/>
<feature enabled="1" name="imtbphoto"/>
<feature enabled="0" name="yahoo360"/>
<feature enabled="1" name="launchcast"/>
<feature enabled="1" name="UserChatRooms"/>
<feature enabled="1" name="chat"/>
<feature enabled="0" name="pstn"/>
<feature enabled="0" name="pstnin"/>
<feature enabled="1" name="call_forwarding"/>
<feature enabled="1" name="ymsgrads"/>
<feature enabled="1" name="gallery"/>
<feature enabled="0" name="InteropPromo"/>
<feature enabled="1" name="cookie_token"/>
<feature enabled="1" name="sumo"/>
<feature enabled="0" name="vitality"/>
<feature enabled="0" name="pingbox"/>
<feature enabled="0" name="keywordtooltips"/>
<feature enabled="1" name="langpacks"/>
<feature enabled="1" name="mail_alert"/>
<feature enabled="1" name="avatar"/>
<appdata>
<flickr>
<d1>f_Drbm3OeQq1hDzuUozZXxogIdw-</d1>
<d2>DaftNW2VLF3JjhoE7cOFSc33SKSw_EeJhROFQlU5C7gJ9F06</d2>
</flickr>
</appdata>
</system>
+3
View File
@@ -0,0 +1,3 @@
<content time="{{ epoch }}">
{{ configxml }}
</content>
+12
View File
@@ -0,0 +1,12 @@
OK
BEGIN BUDDYLIST
{{ buddylist }}
END BUDDYLIST
BEGIN IGNORELIST
{{ blocklist }}
END IGNORELIST
BEGIN IDENTITIES
{{ username }}
END IDENTITIES
Mail=1
Login={{ username }}
+6
View File
@@ -0,0 +1,6 @@
0
crumb={{ crumb }}
Y={{ y_cookie }}
T={{ t_cookie }}
SSL={{ ssl_cookie }}
cookievalidfor=86400
+20
View File
@@ -0,0 +1,20 @@
<html><head></head>
<body scroll="no" topmargin="0" leftmargin="0">
<style>
body {
font-family: 'Trebuchet MS', sans-serif;
background: #1d1b52;
color: white;
}
</style>
<center>
<br>
<strong><p>This is a placeholder.</p></strong>
</center>
</body>
</html>
+20
View File
@@ -0,0 +1,20 @@
<html><head><title>banner ad</title></head>
<body scroll="no" topmargin="0" leftmargin="0">
<style>
body {
font-family: 'Trebuchet MS', sans-serif;
background: #1d1b52;
color: white;
}
</style>
<center>
<br>
<h1>This is a placeholder.</h1>
</center>
</body>
</html>
+20
View File
@@ -0,0 +1,20 @@
<html><head></head>
<body scroll="no" topmargin="0" leftmargin="0">
<style>
body {
font-family: 'Trebuchet MS', sans-serif;
background: #1d1b52;
color: white;
}
</style>
<center>
<br>
<h1>This is a placeholder.</h1>
</center>
</body>
</html>
+19
View File
@@ -0,0 +1,19 @@
from typing import Optional
import asyncio
from core.backend import Backend
def register(backend: Backend) -> None:
from util.misc import ProtocolRunner
backend.add_runner(ProtocolRunner('0.0.0.0', 5100, ListenerVideoChat, service = 'YMSG Video'))
class ListenerVideoChat(asyncio.Protocol):
def connection_made(self, transport: asyncio.BaseTransport) -> None:
print("Video chat connection_made")
def connection_lost(self, exc: Optional[Exception]) -> None:
print("Video chat connection_lost")
def data_received(self, data: bytes) -> None:
print("Video chat data_received", data)
+22
View File
@@ -0,0 +1,22 @@
from typing import Optional
import asyncio
from core.backend import Backend
def register(backend: Backend) -> None:
from util.misc import ProtocolRunner
# TODO: Implement UDP ports
# https://wiki.imfreedom.org/index.php/Yahoo#Network
backend.add_runner(ProtocolRunner('0.0.0.0', 5000, ListenerVoiceChat, service = 'YMSG Voice'))
backend.add_runner(ProtocolRunner('0.0.0.0', 5001, ListenerVoiceChat, service = 'YMSG Voice'))
class ListenerVoiceChat(asyncio.Protocol):
def connection_made(self, transport: asyncio.BaseTransport) -> None:
print("Voice chat connection_made")
def connection_lost(self, exc: Optional[Exception]) -> None:
print("Voice chat connection_lost")
def data_received(self, data: bytes) -> None:
print("Voice chat data_received", data)
+210
View File
@@ -0,0 +1,210 @@
import io, asyncio, binascii, struct, settings
from abc import ABCMeta, abstractmethod
from typing import Tuple, Any, Optional, Callable, Iterable
from core import error
from util.misc import Logger, MultiDict
from .misc import YMSGStatus, YMSGService
KVS = MultiDict[bytes, bytes]
class YMSGCtrlBase(metaclass = ABCMeta):
__slots__ = ('logger', 'decoder', 'encoder', 'peername', 'closed', 'close_callback', 'transport', 'session_id')
logger: Logger
decoder: 'YMSGDecoder'
encoder: 'YMSGEncoder'
peername: Tuple[str, int]
close_callback: Optional[Callable[[], None]]
closed: bool
transport: Optional[asyncio.WriteTransport]
session_id: int
def __init__(self, logger: Logger) -> None:
self.logger = logger
self.decoder = YMSGDecoder(logger)
self.encoder = YMSGEncoder(logger)
self.peername = ('0.0.0.0', 5050)
self.closed = False
self.close_callback = None
self.transport = None
self.session_id = 0
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 y in self.decoder.data_received(data):
try:
# check version and vendorId
if y[1] > 18 or y[2] not in (0, 1, 100):
continue
if y[4]:
self.session_id = y[4]
f = getattr(self, '_y_{}'.format(binascii.hexlify(struct.pack('!H', y[0])).decode()))
f(*y[1:])
except Exception as ex:
self.logger.error(ex)
def send_reply(self, service: YMSGService, status: YMSGStatus, session_id: int, kvs: Optional[KVS] = None) -> None:
if session_id == 0:
session_id = self.session_id
try:
self.encoder.encode(service, status, session_id, kvs)
except error.DataTooLargeToSend:
return
transport = self.transport
if transport is not None:
transport.write(self.flush())
def flush(self) -> bytes:
return self.encoder.flush()
def close(self, **kwargs: Any) -> None:
if self.closed: return
self.closed = True
if self.close_callback:
self.close_callback()
self._on_close(**kwargs)
@abstractmethod
def _on_close(self, remove_sess_id: bool = True) -> None: pass
class YMSGEncoder:
__slots__ = ('_logger', '_buf')
_logger: Logger
_buf: io.BytesIO
def __init__(self, logger: Logger) -> None:
self._logger = logger
self._buf = io.BytesIO()
def encode(self, service: YMSGService, status: YMSGStatus, session_id: int, kvs: Optional[KVS] = None) -> None:
payload_list = []
if kvs is not None:
k = None # type: Optional[bytes]
v = None # type: Optional[bytes]
for k, v in kvs.items():
payload_list.extend([k, SEP, v, SEP])
payload = b''.join(payload_list)
# TODO: Yahoo!'s servers used to split large payloads into packet chunks,
# but there's little information on how it was exactly handled.
# Just drop packets if they're too big (for the length field to handle unfortunately) until we can find a solution.
if len(payload) > 0xffff:
raise error.DataTooLargeToSend()
w = self._buf.write
w(PRE)
# version number and vendor id are replaced with 0x00000000
w(b'\x00\x00\x00\x00')
# Have to call `int` on these because they might be an IntEnum, which
# get `repr`'d to `EnumName.ValueName`. Grr.
w(struct.pack('!HHII', len(payload), int(service), int(status), session_id))
w(payload)
self._logger.debug('[Server]', service, status, session_id)
if kvs:
_truncated_kvs(service, kvs)
def flush(self) -> bytes:
data = self._buf.getvalue()
if data:
#self._logger.info('<<<', data)
self._buf = io.BytesIO()
return data
DecodedYMSG = Tuple[YMSGService, int, int, YMSGStatus, int, KVS]
class YMSGDecoder:
__slots__ = ('logger', '_data', '_i')
logger: Logger
_data: bytes
_i: int
def __init__(self, logger: Logger) -> None:
self.logger = logger
self._data = b''
self._i = 0
def data_received(self, data: bytes) -> Iterable[DecodedYMSG]:
if self._data:
self._data += data
else:
self._data = data
while self._data:
y = self._ymsg_read()
if y is None: break
yield y
def _ymsg_read(self) -> Optional[DecodedYMSG]:
try:
y, e = _try_decode_ymsg(self._data, self._i)
except Exception:
print("ERR _ymsg_read", self._data)
raise
self._data = self._data[e:]
self._i = 0
self.logger.debug('[Client]', 'YMSG{}'.format(str(y[1])), y[0], y[3], y[4])
_truncated_kvs(y[0], y[5])
return y
def _try_decode_ymsg(d: bytes, i: int) -> Tuple[DecodedYMSG, int]:
kvs = MultiDict() # type: KVS
e = 20
assert len(d[i:]) >= e
assert d[i:i+4] == PRE
header = d[i+4:i+e]
if header[:2] in (b'\x08\x00',b'\x09\x00',b'\x0a\x00'):
version = struct.unpack('<H', header[:2])[0] # type: int
else:
version = struct.unpack('!H', header[:2])[0]
(vendor_id, n, service, status, session_id) = struct.unpack('!HHHII', header[2:]) # type: Tuple[int, int, int, int, int]
assert version in YMSG_DIALECTS
assert e+n <= len(d[i:])
payload = d[e:e+n]
if payload:
parts = payload.split(SEP)
del parts[-1]
assert len(parts) % 2 == 0
for j in range(1, len(parts), 2):
kvs.add(parts[j-1], parts[j])
e += n
return ((YMSGService(service), version, vendor_id, YMSGStatus(status), session_id, kvs), e)
def _truncated_kvs(service: YMSGService, kvs: KVS) -> None:
restricted_keys = set()
if service in (YMSGService.AuthResp, YMSGService.List):
restricted_keys.add(b'59')
if service in (
YMSGService.Message, YMSGService.MassMessage, YMSGService.ContactNew, YMSGService.FriendAdd,
YMSGService.ContactDeny, YMSGService.ConfDecline, YMSGService.ConfMsg,YMSGService.P2PFileXfer, YMSGService.FileTransfer
):
restricted_keys.add(b'14')
if service in (YMSGService.ConfInvite, YMSGService.ConfAddInvite):
restricted_keys.add(b'58')
if service in (YMSGService.P2PFileXfer, YMSGService.FileTransfer):
restricted_keys.add(b'20')
if settings.DEBUG:
for k, v in kvs.items():
print('{!r} -> {}'.format(k, (v)))
PRE = b'YMSG'
SEP = b'\xC0\x80'
YMSG_DIALECTS = [
# Not actually supported
18, 17, 16, 8,
# Actually supported
15, 14, 13, 12, 11, 10, 9
]