Add cookie support
This commit is contained in:
parent
7b7ddaa0d1
commit
e09e2d215f
7 changed files with 127 additions and 11 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Next version
|
## Next version
|
||||||
|
|
||||||
|
- add cookie support
|
||||||
- add !restart to botrulez
|
- add !restart to botrulez
|
||||||
- save (overwrite) `Bot` config file
|
- save (overwrite) `Bot` config file
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ i. e. the text between the end of the "!echo" and the end of the whole message.
|
||||||
## TODOs
|
## TODOs
|
||||||
|
|
||||||
- [ ] document yaboli (markdown files in a "docs" folder?)
|
- [ ] document yaboli (markdown files in a "docs" folder?)
|
||||||
- [ ] cookie support
|
|
||||||
- [ ] fancy argument parsing
|
- [ ] fancy argument parsing
|
||||||
- [ ] document new classes (docstrings, maybe comments)
|
- [ ] document new classes (docstrings, maybe comments)
|
||||||
- [ ] write examples
|
- [ ] write examples
|
||||||
|
|
@ -74,3 +73,4 @@ i. e. the text between the end of the "!echo" and the end of the whole message.
|
||||||
yaboli using `pip install git+https://github.com/Garmelon/yaboli`)
|
yaboli using `pip install git+https://github.com/Garmelon/yaboli`)
|
||||||
- [x] implement !restart
|
- [x] implement !restart
|
||||||
- [x] write project readme
|
- [x] write project readme
|
||||||
|
- [x] cookie support
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,16 @@ class Bot(Client):
|
||||||
|
|
||||||
nick = self.config[self.GENERAL_SECTION].get("nick")
|
nick = self.config[self.GENERAL_SECTION].get("nick")
|
||||||
if nick is None:
|
if nick is None:
|
||||||
logger.warn("No nick set in config file. Defaulting to empty nick")
|
logger.warn(("'nick' not set in config file. Defaulting to empty"
|
||||||
|
" nick"))
|
||||||
nick = ""
|
nick = ""
|
||||||
super().__init__(nick)
|
|
||||||
|
cookie_file = self.config[self.GENERAL_SECTION].get("cookie_file")
|
||||||
|
if cookie_file is None:
|
||||||
|
logger.warn(("'cookie_file' not set in config file. Using no cookie"
|
||||||
|
" file."))
|
||||||
|
|
||||||
|
super().__init__(nick, cookie_file=cookie_file)
|
||||||
|
|
||||||
self._commands: List[Command] = []
|
self._commands: List[Command] = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
from .message import LiveMessage
|
from .message import LiveMessage
|
||||||
from .room import Room
|
from .room import Room
|
||||||
|
|
@ -12,8 +12,12 @@ logger = logging.getLogger(__name__)
|
||||||
__all__ = ["Client"]
|
__all__ = ["Client"]
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
def __init__(self, default_nick: str) -> None:
|
def __init__(self,
|
||||||
|
default_nick: str,
|
||||||
|
cookie_file: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
self._default_nick = default_nick
|
self._default_nick = default_nick
|
||||||
|
self._cookie_file = cookie_file
|
||||||
self._rooms: Dict[str, List[Room]] = {}
|
self._rooms: Dict[str, List[Room]] = {}
|
||||||
self._stop = asyncio.Event()
|
self._stop = asyncio.Event()
|
||||||
|
|
||||||
|
|
@ -48,13 +52,31 @@ class Client:
|
||||||
async def join(self,
|
async def join(self,
|
||||||
room_name: str,
|
room_name: str,
|
||||||
password: Optional[str] = None,
|
password: Optional[str] = None,
|
||||||
nick: Optional[str] = None
|
nick: Optional[str] = None,
|
||||||
|
cookie_file: Union[str, bool] = True,
|
||||||
) -> Optional[Room]:
|
) -> Optional[Room]:
|
||||||
|
"""
|
||||||
|
cookie_file is the name of the file to store the cookies in. If it is
|
||||||
|
True, the client default is used. If it is False, no cookie file name
|
||||||
|
will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
logger.info(f"Joining &{room_name}")
|
logger.info(f"Joining &{room_name}")
|
||||||
|
|
||||||
if nick is None:
|
if nick is None:
|
||||||
nick = self._default_nick
|
nick = self._default_nick
|
||||||
room = Room(room_name, password=password, target_nick=nick)
|
|
||||||
|
this_cookie_file: Optional[str]
|
||||||
|
|
||||||
|
if isinstance(cookie_file, str): # This way, mypy doesn't complain
|
||||||
|
this_cookie_file = cookie_file
|
||||||
|
elif cookie_file:
|
||||||
|
this_cookie_file = self._cookie_file
|
||||||
|
else:
|
||||||
|
this_cookie_file = None
|
||||||
|
|
||||||
|
room = Room(room_name, password=password, target_nick=nick,
|
||||||
|
cookie_file=this_cookie_file)
|
||||||
|
|
||||||
room.register_event("connected",
|
room.register_event("connected",
|
||||||
functools.partial(self.on_connected, room))
|
functools.partial(self.on_connected, room))
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from typing import Any, Awaitable, Callable, Dict, Optional
|
||||||
|
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
|
from .cookiejar import CookieJar
|
||||||
from .events import Events
|
from .events import Events
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
|
|
||||||
|
|
@ -97,8 +98,9 @@ class Connection:
|
||||||
|
|
||||||
# Initialising
|
# Initialising
|
||||||
|
|
||||||
def __init__(self, url: str) -> None:
|
def __init__(self, url: str, cookie_file: Optional[str] = None) -> None:
|
||||||
self._url = url
|
self._url = url
|
||||||
|
self._cookie_jar = CookieJar(cookie_file)
|
||||||
|
|
||||||
self._events = Events()
|
self._events = Events()
|
||||||
self._packet_id = 0
|
self._packet_id = 0
|
||||||
|
|
@ -181,7 +183,8 @@ class Connection:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Creating ws connection to {self._url!r}")
|
logger.debug(f"Creating ws connection to {self._url!r}")
|
||||||
ws = await websockets.connect(self._url)
|
ws = await websockets.connect(self._url,
|
||||||
|
extra_headers=self._cookie_jar.get_cookies_as_headers())
|
||||||
|
|
||||||
self._ws = ws
|
self._ws = ws
|
||||||
self._awaiting_replies = {}
|
self._awaiting_replies = {}
|
||||||
|
|
@ -189,6 +192,11 @@ class Connection:
|
||||||
self._ping_check = asyncio.create_task(
|
self._ping_check = asyncio.create_task(
|
||||||
self._disconnect_in(self.PING_TIMEOUT))
|
self._disconnect_in(self.PING_TIMEOUT))
|
||||||
|
|
||||||
|
# Put received cookies into cookie jar
|
||||||
|
for set_cookie in ws.response_headers.get_all("Set-Cookie"):
|
||||||
|
self._cookie_jar.add_cookie(set_cookie)
|
||||||
|
self._cookie_jar.save()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (websockets.InvalidHandshake, websockets.InvalidStatusCode,
|
except (websockets.InvalidHandshake, websockets.InvalidStatusCode,
|
||||||
|
|
|
||||||
77
yaboli/cookiejar.py
Normal file
77
yaboli/cookiejar.py
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import contextlib
|
||||||
|
import http.cookies as cookies
|
||||||
|
import logging
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
__all__ = ["CookieJar"]
|
||||||
|
|
||||||
|
class CookieJar:
|
||||||
|
"""
|
||||||
|
Keeps your cookies in a file.
|
||||||
|
|
||||||
|
CookieJar doesn't attempt to discard old cookies, but that doesn't appear
|
||||||
|
to be necessary for keeping euphoria session cookies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, filename: Optional[str] = None) -> None:
|
||||||
|
self._filename = filename
|
||||||
|
self._cookies = cookies.SimpleCookie()
|
||||||
|
|
||||||
|
if not self._filename:
|
||||||
|
logger.warning("Could not load cookies, no filename given.")
|
||||||
|
return
|
||||||
|
|
||||||
|
with contextlib.suppress(FileNotFoundError):
|
||||||
|
logger.info(f"Loading cookies from {self._filename!r}")
|
||||||
|
with open(self._filename, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
self._cookies.load(line)
|
||||||
|
|
||||||
|
def get_cookies(self) -> List[str]:
|
||||||
|
return [morsel.OutputString(attrs=[])
|
||||||
|
for morsel in self._cookies.values()]
|
||||||
|
|
||||||
|
def get_cookies_as_headers(self) -> List[Tuple[str, str]]:
|
||||||
|
"""
|
||||||
|
Return all stored cookies as tuples in a list. The first tuple entry is
|
||||||
|
always "Cookie".
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [("Cookie", cookie) for cookie in self.get_cookies()]
|
||||||
|
|
||||||
|
def add_cookie(self, cookie: str) -> None:
|
||||||
|
"""
|
||||||
|
Parse cookie and add it to the jar.
|
||||||
|
|
||||||
|
Example cookie: "a=bcd; Path=/; Expires=Wed, 24 Jul 2019 14:57:52 GMT;
|
||||||
|
HttpOnly; Secure"
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.debug(f"Adding cookie {cookie!r}")
|
||||||
|
self._cookies.load(cookie)
|
||||||
|
|
||||||
|
def save(self) -> None:
|
||||||
|
"""
|
||||||
|
Saves all current cookies to the cookie jar file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self._filename:
|
||||||
|
logger.warning("Could not save cookies, no filename given.")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f"Saving cookies to {self._filename!r}")
|
||||||
|
|
||||||
|
with open(self._filename, "w") as f:
|
||||||
|
for morsel in self._cookies.values():
|
||||||
|
cookie_string = morsel.OutputString()
|
||||||
|
f.write(f"{cookie_string}\n")
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""
|
||||||
|
Removes all cookies from the cookie jar.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.debug("OMNOMNOM, cookies are all gone!")
|
||||||
|
self._cookies = cookies.SimpleCookie()
|
||||||
|
|
@ -60,7 +60,8 @@ class Room:
|
||||||
name: str,
|
name: str,
|
||||||
password: Optional[str] = None,
|
password: Optional[str] = None,
|
||||||
target_nick: str = "",
|
target_nick: str = "",
|
||||||
url_format: str = URL_FORMAT
|
url_format: str = URL_FORMAT,
|
||||||
|
cookie_file: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._name = name
|
self._name = name
|
||||||
self._password = password
|
self._password = password
|
||||||
|
|
@ -78,7 +79,7 @@ class Room:
|
||||||
|
|
||||||
# Connected management
|
# Connected management
|
||||||
self._url = self._url_format.format(self._name)
|
self._url = self._url_format.format(self._name)
|
||||||
self._connection = Connection(self._url)
|
self._connection = Connection(self._url, cookie_file=cookie_file)
|
||||||
self._events = Events()
|
self._events = Events()
|
||||||
|
|
||||||
self._connected = asyncio.Event()
|
self._connected = asyncio.Event()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue