aminoacid

  1#!/usr/bin/env python
  2from __future__ import annotations
  3
  4import asyncio
  5from functools import wraps
  6from importlib.util import find_spec
  7from logging import Logger, getLogger
  8from secrets import token_urlsafe
  9from shlex import split
 10from time import asctime
 11from typing import Any, Callable, Coroutine, Dict, List, Optional, TypeVar
 12
 13from aiohttp import ClientResponse, ClientSession
 14
 15from ._socket import SocketClient
 16from .abc import _ORJSON, Context, Message, Session, User, json
 17from .client import ApiClient
 18from .exceptions import AminoBaseException, CommandExists, CommandNotFound
 19from .util import HelpCommand, UserCommand, __version__, get_headers
 20
 21__author__ = "okok7711"
 22
 23_COLOR = find_spec("coloredlogs")
 24
 25if _COLOR:
 26    import coloredlogs
 27
 28    coloredlogs.install()
 29
 30T = TypeVar("T")
 31
 32json_serialize = (
 33    lambda obj, *args, **kwargs: json.dumps(obj).decode()
 34    if _ORJSON
 35    else json.dumps(obj)
 36)
 37"""Function to serialize data with ORJson if its installed, uses built in json module if not
 38
 39Parameters
 40----------
 41obj : dict
 42    object to serialize
 43        
 44Returns
 45-------
 46str
 47    serialized json data
 48"""
 49
 50
 51class Bot(ApiClient):
 52    r"""Bot client class, this is the interface for doing anything
 53
 54    Parameters
 55    ----------
 56    key : bytes
 57        The key to use for signing requests. This has to be supplied in kwargs otherwise the Bot will not work
 58    device : str
 59        The deviceId to use. This is also needed for the bot to run
 60    prefix : str, optional
 61        The prefix the bot should listen to. By default "/"
 62    help_command : UserCommand, optional
 63        The help_command to use, if not supplied a standard Help Command will be used.
 64    v : bytes, optional
 65        The version of the signing algorithm. This is currently \x42
 66    sessionId : str, optional
 67        If given, the bot will not auth via login but will use the given session instead
 68    """
 69    loop: asyncio.AbstractEventLoop
 70    profile: User
 71
 72    def __init__(
 73        self, prefix: str = "/", *, help_command: Optional[UserCommand] = None, **kwargs
 74    ) -> None:
 75        self.prefix = prefix
 76        self.__command_map__: Dict[str, UserCommand] = {
 77            "help": help_command or HelpCommand()
 78        }
 79        self.events: Dict[str, Callable[..., Coroutine[Any, Any, T]]] = {}
 80        self.logger = getLogger(__name__)
 81        self._http = HttpClient(logger=self.logger, **kwargs)
 82
 83        super().__init__()
 84    
 85    def unregister_command(self, name: str):
 86        if not self.__command_map__.pop(name, ""):
 87            raise CommandNotFound(name)
 88
 89    def command(
 90        self,
 91        name="",
 92        *,
 93        check: Optional[Callable[[Context], bool]] = lambda _: True,
 94        check_any: Optional[List[Callable[[Context], bool]]] = [],
 95        cooldown: Optional[int] = 0,
 96    ):
 97        """Wrapper to register commands to the bot
 98
 99        Parameters
100        ----------
101        name : str, optional
102            Name that the command should listen on, by default the name of the function
103        check : Optional[Callable[[Context], bool]], optional
104            function to check whether the command may be executed, by default always returns True
105        check_any : Optional[List[Callable[[Context], bool]]], optional
106            list of checks of which any need to pass, by default []
107        cooldown : Optional[int], optional
108            cooldown before someone can use the command again, by default 0
109
110        Returns
111        -------
112        UserCommand
113            returns the command to register error handlers etc.
114        """
115
116        def wrap(f: Callable[..., Coroutine[Any, Any, T]]):
117            @wraps(f)
118            def func() -> UserCommand:
119                if (name or f.__name__) in self.__command_map__:
120                    return self.logger.exception(CommandExists(name))
121                self.__command_map__[name or f.__name__] = (
122                    cmd := UserCommand(
123                        func=f,
124                        command_name=name,
125                        check=check,
126                        check_any=check_any,
127                        cooldown=cooldown,
128                    )
129                )
130                return cmd
131
132            return func()
133
134        return wrap
135
136    def event(self, name=""):
137        """Wrapper to add events the bot should listen to
138        All possible events are defined in `aminoacid.util.events`
139
140        Parameters
141        ----------
142        name : str, optional
143            Name that the event should listen on, by default the name of the function
144        """
145
146        def wrap(f: Callable):
147            @wraps(f)
148            def func():
149                if (name or f.__name__) in self.events:
150                    return
151                self.events[name or f.__name__] = f
152
153            return func()
154
155        return wrap
156
157    def run(
158        self,
159        email: Optional[str] = "",
160        password: Optional[str] = "",
161        *,
162        session: str = "",
163    ):
164        """Run the `main_loop()` of the bot and initiate authentication
165
166        Parameters
167        ----------
168        email : str, optional
169            email of the account to use
170        password : str, optional
171            password of the account to use
172        session : str, optional
173            session to use instead of email password combination
174        """
175        if not any((email, password, session)):
176            raise Exception("No Auth")
177        self.loop = asyncio.get_event_loop()
178        self.loop.run_until_complete(
179            self.main_loop(email=email, password=password, sessionId=session)
180        )
181
182    async def main_loop(
183        self,
184        email: Optional[str],
185        password: Optional[str],
186        *,
187        sessionId: Optional[str] = "",
188    ):
189        """Main loop of the Bot, this authenticates and sets the session
190
191        Parameters
192        ----------
193        email : Optional[str]
194            Email of the account to login
195        password : Optional[str]
196            Password of the account to login
197        sessionId : Optional[str], optional
198            Session of the Account, by default ""
199        """
200        if not sessionId:
201            await self.login(email=email, password=password)
202        else:
203            self._http.session = Session(sessionId)
204            self.profile = await self.fetch_user(self._http.session.uid)
205        self.socket = sock = SocketClient(self)
206        try:
207            await self.set_device()
208            await sock.run_loop()
209        finally:
210            await self.cleanup_loop()
211
212    async def handle_command(self, message: Message):
213        """Handles a command for the supplied message
214
215        Parameters
216        ----------
217        message : Message
218            The message to handle
219        """
220        if not message.startswith(self.prefix):
221            return
222        args = split(message.content[len(self.prefix) :])
223        if args[0] in self.__command_map__:
224            coro = (cmd := self.__command_map__[args.pop(0)])(
225                (ctx := Context(client=self, message=message)), *args
226            )
227            if coro:
228                try:
229                    await coro
230                except AminoBaseException as exc:
231                    if cmd.handler:
232                        await cmd.handler(exc, ctx)
233                    else:
234                        self.logger.exception(exc)
235        else:
236            self.logger.exception(CommandNotFound(message))
237
238    async def cleanup_loop(self):
239        """Cleans up, closes sessions, etc."""
240        # TODO: maybe make better cleanup
241        await self._http.close()
242
243
244class HttpClient(ClientSession):
245    session: Session
246
247    def __init__(self, logger: Logger, *args, **kwargs) -> None:
248        self.base: str = kwargs.pop("base_uri", "https://service.narvii.com/api/v1")
249        self.key: bytes = kwargs.pop("key")
250        self.device: str = kwargs.pop("device")
251        self.v: bytes = kwargs.pop("v", b"\x42")
252        self.token: str = token_urlsafe(152)
253        """pushToken for notification sending, randomly generated"""
254
255        self.logger = logger
256
257        self.session = None
258
259        super().__init__(*args, **kwargs, json_serialize=json_serialize)
260
261    async def request(self, method: str, url: str, *args, **kwargs) -> ClientResponse:
262        """Execute a request
263
264        Parameters
265        ----------
266        method : str
267            Method of the request
268        url : str
269            url to be appended to the base
270
271        Returns
272        -------
273        ClientResponse
274            Response the server returned
275        """
276
277        headers = kwargs.pop("headers", {}) or get_headers(
278            data=json_serialize(kwargs.get("json", {})).encode(),
279            device=self.device,
280            key=self.key,
281            v=self.v,
282        )
283        if self.session:
284            headers["NDCAUTH"] = self.session.sid
285        response = await super().request(
286            method,
287            url=(self.base + url if not "wss" in url else url),
288            headers=headers,
289            *args,
290            **kwargs,
291        )
292        await self.log_request(response, self.logger)
293        return response
294
295    @staticmethod
296    async def log_request(response: ClientResponse, logger: Logger) -> None:
297        """Logs a request and its response with info level
298
299        Parameters
300        ----------
301        response : ClientResponse
302            the response object that the request returned
303        """
304        logger.info(
305            f"{response.request_info.method} [{asctime()}] -> {response.url}: {response.status} [{response.content_type}] "
306            f"Received Headers: {response.headers}, Sent Headers: {response.request_info.headers}, Received Content: {await response.text()}"
307        )
def json_serialize(obj, *args, **kwargs):
34    lambda obj, *args, **kwargs: json.dumps(obj).decode()

Function to serialize data with ORJson if its installed, uses built in json module if not

Parameters
  • obj (dict): object to serialize
Returns
  • str: serialized json data
class Bot(aminoacid.client.ApiClient):
 52class Bot(ApiClient):
 53    r"""Bot client class, this is the interface for doing anything
 54
 55    Parameters
 56    ----------
 57    key : bytes
 58        The key to use for signing requests. This has to be supplied in kwargs otherwise the Bot will not work
 59    device : str
 60        The deviceId to use. This is also needed for the bot to run
 61    prefix : str, optional
 62        The prefix the bot should listen to. By default "/"
 63    help_command : UserCommand, optional
 64        The help_command to use, if not supplied a standard Help Command will be used.
 65    v : bytes, optional
 66        The version of the signing algorithm. This is currently \x42
 67    sessionId : str, optional
 68        If given, the bot will not auth via login but will use the given session instead
 69    """
 70    loop: asyncio.AbstractEventLoop
 71    profile: User
 72
 73    def __init__(
 74        self, prefix: str = "/", *, help_command: Optional[UserCommand] = None, **kwargs
 75    ) -> None:
 76        self.prefix = prefix
 77        self.__command_map__: Dict[str, UserCommand] = {
 78            "help": help_command or HelpCommand()
 79        }
 80        self.events: Dict[str, Callable[..., Coroutine[Any, Any, T]]] = {}
 81        self.logger = getLogger(__name__)
 82        self._http = HttpClient(logger=self.logger, **kwargs)
 83
 84        super().__init__()
 85    
 86    def unregister_command(self, name: str):
 87        if not self.__command_map__.pop(name, ""):
 88            raise CommandNotFound(name)
 89
 90    def command(
 91        self,
 92        name="",
 93        *,
 94        check: Optional[Callable[[Context], bool]] = lambda _: True,
 95        check_any: Optional[List[Callable[[Context], bool]]] = [],
 96        cooldown: Optional[int] = 0,
 97    ):
 98        """Wrapper to register commands to the bot
 99
100        Parameters
101        ----------
102        name : str, optional
103            Name that the command should listen on, by default the name of the function
104        check : Optional[Callable[[Context], bool]], optional
105            function to check whether the command may be executed, by default always returns True
106        check_any : Optional[List[Callable[[Context], bool]]], optional
107            list of checks of which any need to pass, by default []
108        cooldown : Optional[int], optional
109            cooldown before someone can use the command again, by default 0
110
111        Returns
112        -------
113        UserCommand
114            returns the command to register error handlers etc.
115        """
116
117        def wrap(f: Callable[..., Coroutine[Any, Any, T]]):
118            @wraps(f)
119            def func() -> UserCommand:
120                if (name or f.__name__) in self.__command_map__:
121                    return self.logger.exception(CommandExists(name))
122                self.__command_map__[name or f.__name__] = (
123                    cmd := UserCommand(
124                        func=f,
125                        command_name=name,
126                        check=check,
127                        check_any=check_any,
128                        cooldown=cooldown,
129                    )
130                )
131                return cmd
132
133            return func()
134
135        return wrap
136
137    def event(self, name=""):
138        """Wrapper to add events the bot should listen to
139        All possible events are defined in `aminoacid.util.events`
140
141        Parameters
142        ----------
143        name : str, optional
144            Name that the event should listen on, by default the name of the function
145        """
146
147        def wrap(f: Callable):
148            @wraps(f)
149            def func():
150                if (name or f.__name__) in self.events:
151                    return
152                self.events[name or f.__name__] = f
153
154            return func()
155
156        return wrap
157
158    def run(
159        self,
160        email: Optional[str] = "",
161        password: Optional[str] = "",
162        *,
163        session: str = "",
164    ):
165        """Run the `main_loop()` of the bot and initiate authentication
166
167        Parameters
168        ----------
169        email : str, optional
170            email of the account to use
171        password : str, optional
172            password of the account to use
173        session : str, optional
174            session to use instead of email password combination
175        """
176        if not any((email, password, session)):
177            raise Exception("No Auth")
178        self.loop = asyncio.get_event_loop()
179        self.loop.run_until_complete(
180            self.main_loop(email=email, password=password, sessionId=session)
181        )
182
183    async def main_loop(
184        self,
185        email: Optional[str],
186        password: Optional[str],
187        *,
188        sessionId: Optional[str] = "",
189    ):
190        """Main loop of the Bot, this authenticates and sets the session
191
192        Parameters
193        ----------
194        email : Optional[str]
195            Email of the account to login
196        password : Optional[str]
197            Password of the account to login
198        sessionId : Optional[str], optional
199            Session of the Account, by default ""
200        """
201        if not sessionId:
202            await self.login(email=email, password=password)
203        else:
204            self._http.session = Session(sessionId)
205            self.profile = await self.fetch_user(self._http.session.uid)
206        self.socket = sock = SocketClient(self)
207        try:
208            await self.set_device()
209            await sock.run_loop()
210        finally:
211            await self.cleanup_loop()
212
213    async def handle_command(self, message: Message):
214        """Handles a command for the supplied message
215
216        Parameters
217        ----------
218        message : Message
219            The message to handle
220        """
221        if not message.startswith(self.prefix):
222            return
223        args = split(message.content[len(self.prefix) :])
224        if args[0] in self.__command_map__:
225            coro = (cmd := self.__command_map__[args.pop(0)])(
226                (ctx := Context(client=self, message=message)), *args
227            )
228            if coro:
229                try:
230                    await coro
231                except AminoBaseException as exc:
232                    if cmd.handler:
233                        await cmd.handler(exc, ctx)
234                    else:
235                        self.logger.exception(exc)
236        else:
237            self.logger.exception(CommandNotFound(message))
238
239    async def cleanup_loop(self):
240        """Cleans up, closes sessions, etc."""
241        # TODO: maybe make better cleanup
242        await self._http.close()

Bot client class, this is the interface for doing anything

Parameters
  • key (bytes): The key to use for signing requests. This has to be supplied in kwargs otherwise the Bot will not work
  • device (str): The deviceId to use. This is also needed for the bot to run
  • prefix (str, optional): The prefix the bot should listen to. By default "/"
  • help_command (UserCommand, optional): The help_command to use, if not supplied a standard Help Command will be used.
  • v (bytes, optional): The version of the signing algorithm. This is currently \x42
  • sessionId (str, optional): If given, the bot will not auth via login but will use the given session instead
Bot( prefix: str = '/', *, help_command: Optional[aminoacid.util.commands.UserCommand] = None, **kwargs)
73    def __init__(
74        self, prefix: str = "/", *, help_command: Optional[UserCommand] = None, **kwargs
75    ) -> None:
76        self.prefix = prefix
77        self.__command_map__: Dict[str, UserCommand] = {
78            "help": help_command or HelpCommand()
79        }
80        self.events: Dict[str, Callable[..., Coroutine[Any, Any, T]]] = {}
81        self.logger = getLogger(__name__)
82        self._http = HttpClient(logger=self.logger, **kwargs)
83
84        super().__init__()
def unregister_command(self, name: str):
86    def unregister_command(self, name: str):
87        if not self.__command_map__.pop(name, ""):
88            raise CommandNotFound(name)
def command( self, name='', *, check: Optional[Callable[[aminoacid.abc.Context], bool]] = <function Bot.<lambda>>, check_any: Optional[List[Callable[[aminoacid.abc.Context], bool]]] = [], cooldown: Optional[int] = 0):
 90    def command(
 91        self,
 92        name="",
 93        *,
 94        check: Optional[Callable[[Context], bool]] = lambda _: True,
 95        check_any: Optional[List[Callable[[Context], bool]]] = [],
 96        cooldown: Optional[int] = 0,
 97    ):
 98        """Wrapper to register commands to the bot
 99
100        Parameters
101        ----------
102        name : str, optional
103            Name that the command should listen on, by default the name of the function
104        check : Optional[Callable[[Context], bool]], optional
105            function to check whether the command may be executed, by default always returns True
106        check_any : Optional[List[Callable[[Context], bool]]], optional
107            list of checks of which any need to pass, by default []
108        cooldown : Optional[int], optional
109            cooldown before someone can use the command again, by default 0
110
111        Returns
112        -------
113        UserCommand
114            returns the command to register error handlers etc.
115        """
116
117        def wrap(f: Callable[..., Coroutine[Any, Any, T]]):
118            @wraps(f)
119            def func() -> UserCommand:
120                if (name or f.__name__) in self.__command_map__:
121                    return self.logger.exception(CommandExists(name))
122                self.__command_map__[name or f.__name__] = (
123                    cmd := UserCommand(
124                        func=f,
125                        command_name=name,
126                        check=check,
127                        check_any=check_any,
128                        cooldown=cooldown,
129                    )
130                )
131                return cmd
132
133            return func()
134
135        return wrap

Wrapper to register commands to the bot

Parameters
  • name (str, optional): Name that the command should listen on, by default the name of the function
  • check (Optional[Callable[[Context], bool]], optional): function to check whether the command may be executed, by default always returns True
  • check_any (Optional[List[Callable[[Context], bool]]], optional): list of checks of which any need to pass, by default []
  • cooldown (Optional[int], optional): cooldown before someone can use the command again, by default 0
Returns
  • UserCommand: returns the command to register error handlers etc.
def event(self, name=''):
137    def event(self, name=""):
138        """Wrapper to add events the bot should listen to
139        All possible events are defined in `aminoacid.util.events`
140
141        Parameters
142        ----------
143        name : str, optional
144            Name that the event should listen on, by default the name of the function
145        """
146
147        def wrap(f: Callable):
148            @wraps(f)
149            def func():
150                if (name or f.__name__) in self.events:
151                    return
152                self.events[name or f.__name__] = f
153
154            return func()
155
156        return wrap

Wrapper to add events the bot should listen to All possible events are defined in aminoacid.util.events

Parameters
  • name (str, optional): Name that the event should listen on, by default the name of the function
def run( self, email: Optional[str] = '', password: Optional[str] = '', *, session: str = ''):
158    def run(
159        self,
160        email: Optional[str] = "",
161        password: Optional[str] = "",
162        *,
163        session: str = "",
164    ):
165        """Run the `main_loop()` of the bot and initiate authentication
166
167        Parameters
168        ----------
169        email : str, optional
170            email of the account to use
171        password : str, optional
172            password of the account to use
173        session : str, optional
174            session to use instead of email password combination
175        """
176        if not any((email, password, session)):
177            raise Exception("No Auth")
178        self.loop = asyncio.get_event_loop()
179        self.loop.run_until_complete(
180            self.main_loop(email=email, password=password, sessionId=session)
181        )

Run the main_loop() of the bot and initiate authentication

Parameters
  • email (str, optional): email of the account to use
  • password (str, optional): password of the account to use
  • session (str, optional): session to use instead of email password combination
async def main_loop( self, email: Optional[str], password: Optional[str], *, sessionId: Optional[str] = ''):
183    async def main_loop(
184        self,
185        email: Optional[str],
186        password: Optional[str],
187        *,
188        sessionId: Optional[str] = "",
189    ):
190        """Main loop of the Bot, this authenticates and sets the session
191
192        Parameters
193        ----------
194        email : Optional[str]
195            Email of the account to login
196        password : Optional[str]
197            Password of the account to login
198        sessionId : Optional[str], optional
199            Session of the Account, by default ""
200        """
201        if not sessionId:
202            await self.login(email=email, password=password)
203        else:
204            self._http.session = Session(sessionId)
205            self.profile = await self.fetch_user(self._http.session.uid)
206        self.socket = sock = SocketClient(self)
207        try:
208            await self.set_device()
209            await sock.run_loop()
210        finally:
211            await self.cleanup_loop()

Main loop of the Bot, this authenticates and sets the session

Parameters
  • email (Optional[str]): Email of the account to login
  • password (Optional[str]): Password of the account to login
  • sessionId (Optional[str], optional): Session of the Account, by default ""
async def handle_command(self, message: aminoacid.abc.Message):
213    async def handle_command(self, message: Message):
214        """Handles a command for the supplied message
215
216        Parameters
217        ----------
218        message : Message
219            The message to handle
220        """
221        if not message.startswith(self.prefix):
222            return
223        args = split(message.content[len(self.prefix) :])
224        if args[0] in self.__command_map__:
225            coro = (cmd := self.__command_map__[args.pop(0)])(
226                (ctx := Context(client=self, message=message)), *args
227            )
228            if coro:
229                try:
230                    await coro
231                except AminoBaseException as exc:
232                    if cmd.handler:
233                        await cmd.handler(exc, ctx)
234                    else:
235                        self.logger.exception(exc)
236        else:
237            self.logger.exception(CommandNotFound(message))

Handles a command for the supplied message

Parameters
  • message (Message): The message to handle
async def cleanup_loop(self):
239    async def cleanup_loop(self):
240        """Cleans up, closes sessions, etc."""
241        # TODO: maybe make better cleanup
242        await self._http.close()

Cleans up, closes sessions, etc.

class HttpClient(aiohttp.client.ClientSession):
245class HttpClient(ClientSession):
246    session: Session
247
248    def __init__(self, logger: Logger, *args, **kwargs) -> None:
249        self.base: str = kwargs.pop("base_uri", "https://service.narvii.com/api/v1")
250        self.key: bytes = kwargs.pop("key")
251        self.device: str = kwargs.pop("device")
252        self.v: bytes = kwargs.pop("v", b"\x42")
253        self.token: str = token_urlsafe(152)
254        """pushToken for notification sending, randomly generated"""
255
256        self.logger = logger
257
258        self.session = None
259
260        super().__init__(*args, **kwargs, json_serialize=json_serialize)
261
262    async def request(self, method: str, url: str, *args, **kwargs) -> ClientResponse:
263        """Execute a request
264
265        Parameters
266        ----------
267        method : str
268            Method of the request
269        url : str
270            url to be appended to the base
271
272        Returns
273        -------
274        ClientResponse
275            Response the server returned
276        """
277
278        headers = kwargs.pop("headers", {}) or get_headers(
279            data=json_serialize(kwargs.get("json", {})).encode(),
280            device=self.device,
281            key=self.key,
282            v=self.v,
283        )
284        if self.session:
285            headers["NDCAUTH"] = self.session.sid
286        response = await super().request(
287            method,
288            url=(self.base + url if not "wss" in url else url),
289            headers=headers,
290            *args,
291            **kwargs,
292        )
293        await self.log_request(response, self.logger)
294        return response
295
296    @staticmethod
297    async def log_request(response: ClientResponse, logger: Logger) -> None:
298        """Logs a request and its response with info level
299
300        Parameters
301        ----------
302        response : ClientResponse
303            the response object that the request returned
304        """
305        logger.info(
306            f"{response.request_info.method} [{asctime()}] -> {response.url}: {response.status} [{response.content_type}] "
307            f"Received Headers: {response.headers}, Sent Headers: {response.request_info.headers}, Received Content: {await response.text()}"
308        )

First-class interface for making HTTP requests.

HttpClient(logger: logging.Logger, *args, **kwargs)
248    def __init__(self, logger: Logger, *args, **kwargs) -> None:
249        self.base: str = kwargs.pop("base_uri", "https://service.narvii.com/api/v1")
250        self.key: bytes = kwargs.pop("key")
251        self.device: str = kwargs.pop("device")
252        self.v: bytes = kwargs.pop("v", b"\x42")
253        self.token: str = token_urlsafe(152)
254        """pushToken for notification sending, randomly generated"""
255
256        self.logger = logger
257
258        self.session = None
259
260        super().__init__(*args, **kwargs, json_serialize=json_serialize)
token: str

pushToken for notification sending, randomly generated

async def request( self, method: str, url: str, *args, **kwargs) -> aiohttp.client_reqrep.ClientResponse:
262    async def request(self, method: str, url: str, *args, **kwargs) -> ClientResponse:
263        """Execute a request
264
265        Parameters
266        ----------
267        method : str
268            Method of the request
269        url : str
270            url to be appended to the base
271
272        Returns
273        -------
274        ClientResponse
275            Response the server returned
276        """
277
278        headers = kwargs.pop("headers", {}) or get_headers(
279            data=json_serialize(kwargs.get("json", {})).encode(),
280            device=self.device,
281            key=self.key,
282            v=self.v,
283        )
284        if self.session:
285            headers["NDCAUTH"] = self.session.sid
286        response = await super().request(
287            method,
288            url=(self.base + url if not "wss" in url else url),
289            headers=headers,
290            *args,
291            **kwargs,
292        )
293        await self.log_request(response, self.logger)
294        return response

Perform HTTP request.

@staticmethod
async def log_request( response: aiohttp.client_reqrep.ClientResponse, logger: logging.Logger) -> None:
296    @staticmethod
297    async def log_request(response: ClientResponse, logger: Logger) -> None:
298        """Logs a request and its response with info level
299
300        Parameters
301        ----------
302        response : ClientResponse
303            the response object that the request returned
304        """
305        logger.info(
306            f"{response.request_info.method} [{asctime()}] -> {response.url}: {response.status} [{response.content_type}] "
307            f"Received Headers: {response.headers}, Sent Headers: {response.request_info.headers}, Received Content: {await response.text()}"
308        )

Logs a request and its response with info level

Parameters
  • response (ClientResponse): the response object that the request returned
Inherited Members
aiohttp.client.ClientSession
ATTRS
ws_connect
get
options
head
post
put
patch
delete
close
closed
connector
cookie_jar
version
requote_redirect_url
loop
timeout
headers
skip_auto_headers
auth
json_serialize
connector_owner
raise_for_status
auto_decompress
trust_env
trace_configs
detach