/ src / aria2p / client.py
client.py
   1  """Client module.
   2  
   3  This module defines the ClientException and Client classes, which are used to communicate with a remote aria2c
   4  process through the JSON-RPC protocol.
   5  """
   6  
   7  from __future__ import annotations
   8  
   9  import json
  10  from typing import Any, Callable, ClassVar, Union
  11  
  12  import requests
  13  import websocket
  14  from loguru import logger
  15  
  16  from aria2p.utils import SignalHandler
  17  
  18  DEFAULT_ID = -1
  19  DEFAULT_HOST = "http://localhost"
  20  DEFAULT_PORT = 6800
  21  DEFAULT_TIMEOUT: float = 60.0
  22  
  23  JSONRPC_PARSER_ERROR = -32700
  24  JSONRPC_INVALID_REQUEST = -32600
  25  JSONRPC_METHOD_NOT_FOUND = -32601
  26  JSONRPC_INVALID_PARAMS = -32602
  27  JSONRPC_INTERNAL_ERROR = -32603
  28  
  29  JSONRPC_CODES = {
  30      JSONRPC_PARSER_ERROR: "Invalid JSON was received by the server.",
  31      JSONRPC_INVALID_REQUEST: "The JSON sent is not a valid Request object.",
  32      JSONRPC_METHOD_NOT_FOUND: "The method does not exist / is not available.",
  33      JSONRPC_INVALID_PARAMS: "Invalid method parameter(s).",
  34      JSONRPC_INTERNAL_ERROR: "Internal JSON-RPC error.",
  35  }
  36  
  37  NOTIFICATION_START = "aria2.onDownloadStart"
  38  NOTIFICATION_PAUSE = "aria2.onDownloadPause"
  39  NOTIFICATION_STOP = "aria2.onDownloadStop"
  40  NOTIFICATION_COMPLETE = "aria2.onDownloadComplete"
  41  NOTIFICATION_ERROR = "aria2.onDownloadError"
  42  NOTIFICATION_BT_COMPLETE = "aria2.onBtDownloadComplete"
  43  
  44  NOTIFICATION_TYPES = [
  45      NOTIFICATION_START,
  46      NOTIFICATION_PAUSE,
  47      NOTIFICATION_STOP,
  48      NOTIFICATION_COMPLETE,
  49      NOTIFICATION_ERROR,
  50      NOTIFICATION_BT_COMPLETE,
  51  ]
  52  
  53  CallsType = list[tuple[str, list[str], Union[str, int]]]
  54  Multicalls2Type = list[tuple[str, list[str]]]
  55  CallReturnType = Union[dict, list, str, int]
  56  
  57  
  58  class ClientException(Exception):  # noqa: N818
  59      """An exception specific to JSON-RPC errors."""
  60  
  61      def __init__(self, code: int, message: str) -> None:
  62          """Initialize the exception.
  63  
  64          Parameters:
  65              code: The error code.
  66              message: The error message.
  67          """
  68          super().__init__()
  69          if code in JSONRPC_CODES:
  70              message = f"{JSONRPC_CODES[code]}\n{message}"
  71  
  72          self.code = code
  73          self.message = message
  74  
  75      def __str__(self):
  76          return self.message
  77  
  78      def __bool__(self):
  79          return False
  80  
  81  
  82  class Client:
  83      """The JSON-RPC client class.
  84  
  85      In this documentation, all the following terms refer to the same entity, the remote aria2c process:
  86      remote process, remote server, server, daemon process, background process, remote.
  87  
  88      This class implements method to communicate with a daemon aria2c process through the JSON-RPC protocol.
  89      Each method offered by the aria2c process is implemented in this class, in snake_case instead of camelCase
  90      (example: add_uri instead of addUri).
  91  
  92      The class defines a `METHODS` variable which contains the names of the available methods.
  93  
  94      The class is instantiated using an address and port, and optionally a secret token. The token is never passed
  95      as a method argument.
  96  
  97      The class provides utility methods:
  98  
  99      - `call`, which performs a JSON-RPC call for a single method;
 100      - `batch_call`, which performs a JSON-RPC call for a list of methods;
 101      - `multicall2`, which is an equivalent of multicall, but easier to use;
 102      - `post`, which is responsible for actually sending a payload to the remote process using a POST request;
 103      - `get_payload`, which is used to build payloads;
 104      - `get_params`, which is used to build list of parameters.
 105      """
 106  
 107      ADD_URI = "aria2.addUri"
 108      ADD_TORRENT = "aria2.addTorrent"
 109      ADD_METALINK = "aria2.addMetalink"
 110      REMOVE = "aria2.remove"
 111      FORCE_REMOVE = "aria2.forceRemove"
 112      PAUSE = "aria2.pause"
 113      PAUSE_ALL = "aria2.pauseAll"
 114      FORCE_PAUSE = "aria2.forcePause"
 115      FORCE_PAUSE_ALL = "aria2.forcePauseAll"
 116      UNPAUSE = "aria2.unpause"
 117      UNPAUSE_ALL = "aria2.unpauseAll"
 118      TELL_STATUS = "aria2.tellStatus"
 119      GET_URIS = "aria2.getUris"
 120      GET_FILES = "aria2.getFiles"
 121      GET_PEERS = "aria2.getPeers"
 122      GET_SERVERS = "aria2.getServers"
 123      TELL_ACTIVE = "aria2.tellActive"
 124      TELL_WAITING = "aria2.tellWaiting"
 125      TELL_STOPPED = "aria2.tellStopped"
 126      CHANGE_POSITION = "aria2.changePosition"
 127      CHANGE_URI = "aria2.changeUri"
 128      GET_OPTION = "aria2.getOption"
 129      CHANGE_OPTION = "aria2.changeOption"
 130      GET_GLOBAL_OPTION = "aria2.getGlobalOption"
 131      CHANGE_GLOBAL_OPTION = "aria2.changeGlobalOption"
 132      GET_GLOBAL_STAT = "aria2.getGlobalStat"
 133      PURGE_DOWNLOAD_RESULT = "aria2.purgeDownloadResult"
 134      REMOVE_DOWNLOAD_RESULT = "aria2.removeDownloadResult"
 135      GET_VERSION = "aria2.getVersion"
 136      GET_SESSION_INFO = "aria2.getSessionInfo"
 137      SHUTDOWN = "aria2.shutdown"
 138      FORCE_SHUTDOWN = "aria2.forceShutdown"
 139      SAVE_SESSION = "aria2.saveSession"
 140      MULTICALL = "system.multicall"
 141      LIST_METHODS = "system.listMethods"
 142      LIST_NOTIFICATIONS = "system.listNotifications"
 143  
 144      METHODS: ClassVar[list[str]] = [
 145          ADD_URI,
 146          ADD_TORRENT,
 147          ADD_METALINK,
 148          REMOVE,
 149          FORCE_REMOVE,
 150          PAUSE,
 151          PAUSE_ALL,
 152          FORCE_PAUSE,
 153          FORCE_PAUSE_ALL,
 154          UNPAUSE,
 155          UNPAUSE_ALL,
 156          TELL_STATUS,
 157          GET_URIS,
 158          GET_FILES,
 159          GET_PEERS,
 160          GET_SERVERS,
 161          TELL_ACTIVE,
 162          TELL_WAITING,
 163          TELL_STOPPED,
 164          CHANGE_POSITION,
 165          CHANGE_URI,
 166          GET_OPTION,
 167          CHANGE_OPTION,
 168          GET_GLOBAL_OPTION,
 169          CHANGE_GLOBAL_OPTION,
 170          GET_GLOBAL_STAT,
 171          PURGE_DOWNLOAD_RESULT,
 172          REMOVE_DOWNLOAD_RESULT,
 173          GET_VERSION,
 174          GET_SESSION_INFO,
 175          SHUTDOWN,
 176          FORCE_SHUTDOWN,
 177          SAVE_SESSION,
 178          MULTICALL,
 179          LIST_METHODS,
 180          LIST_NOTIFICATIONS,
 181      ]
 182  
 183      def __init__(
 184          self,
 185          host: str = DEFAULT_HOST,
 186          port: int = DEFAULT_PORT,
 187          secret: str = "",
 188          timeout: float = DEFAULT_TIMEOUT,
 189      ) -> None:
 190          """Initialize the object.
 191  
 192          Parameters:
 193              host: The remote process address.
 194              port: The remote process port.
 195              secret: The secret token.
 196              timeout: The timeout to use for requests towards the remote server.
 197          """
 198          host = host.rstrip("/")
 199  
 200          self.host = host
 201          self.port = port
 202          self.secret = secret
 203          self.timeout = timeout
 204          self.listening = False
 205  
 206      def __str__(self):
 207          return self.server
 208  
 209      def __repr__(self):
 210          return f"Client(host='{self.host}', port={self.port}, secret='********')"
 211  
 212      @property
 213      def server(self) -> str:
 214          """Return the full remote process / server address.
 215  
 216          Returns:
 217              The server address.
 218          """
 219          return f"{self.host}:{self.port}/jsonrpc"
 220  
 221      @property
 222      def ws_server(self) -> str:
 223          """Return the full WebSocket remote server address.
 224  
 225          Returns:
 226              The WebSocket server address.
 227          """
 228          return f"ws{self.host[4:]}:{self.port}/jsonrpc"
 229  
 230      def call(
 231          self,
 232          method: str,
 233          params: list[Any] | None = None,
 234          msg_id: int | str | None = None,
 235          insert_secret: bool = True,  # noqa: FBT001,FBT002
 236      ) -> CallReturnType:
 237          """Call a single JSON-RPC method.
 238  
 239          Parameters:
 240              method: The method name. You can use the constant defined in [`Client`][aria2p.client.Client].
 241              params: A list of parameters.
 242              msg_id: The ID of the call, sent back with the server's answer.
 243              insert_secret: Whether to insert the secret token in the parameters or not.
 244  
 245          Returns:
 246              The answer from the server, as a Python object.
 247          """
 248          params = self.get_params(*(params or []))
 249  
 250          if insert_secret and self.secret:
 251              if method.startswith("aria2."):
 252                  params.insert(0, f"token:{self.secret}")
 253              elif method == self.MULTICALL:
 254                  for param in params[0]:
 255                      param["params"].insert(0, f"token:{self.secret}")
 256  
 257          payload: str = self.get_payload(method, params, msg_id=msg_id)  # type: ignore
 258          return self.res_or_raise(self.post(payload))
 259  
 260      def batch_call(
 261          self,
 262          calls: CallsType,
 263          insert_secret: bool = True,  # noqa: FBT001,FBT002
 264      ) -> list[CallReturnType]:
 265          """Call multiple methods in one request.
 266  
 267          A batch call is simply a list of full payloads, sent at once to the remote process. The differences with a
 268          multicall are:
 269  
 270          - multicall is a special "system" method, whereas batch_call is simply the concatenation of several methods
 271          - multicall payloads define the "jsonrpc" and "id" keys only once, whereas these keys are repeated in
 272            each part of the batch_call payload
 273          - as a result of the previous line, you must pass different IDs to the batch_call methods, whereas the
 274            ID in multicall is optional
 275  
 276          Parameters:
 277              calls: A list of tuples composed of method name, parameters and ID.
 278              insert_secret: Whether to insert the secret token in the parameters or not.
 279  
 280          Returns:
 281              The results for each call in the batch.
 282          """
 283          payloads = []
 284  
 285          for method, params, msg_id in calls:
 286              params = self.get_params(*params)  # noqa: PLW2901
 287              if insert_secret and self.secret and method.startswith("aria2."):
 288                  params.insert(0, f"token:{self.secret}")
 289              payloads.append(self.get_payload(method, params, msg_id, as_json=False))
 290  
 291          payload: str = json.dumps(payloads)
 292          responses = self.post(payload)
 293          return [self.res_or_raise(resp) for resp in responses]
 294  
 295      def multicall2(self, calls: Multicalls2Type, insert_secret: bool = True) -> CallReturnType:  # noqa: FBT001,FBT002
 296          """Call multiple methods in one request.
 297  
 298          A method equivalent to multicall, but with a simplified usage.
 299  
 300          Instead of providing dictionaries with "methodName" and "params" keys and values, this method allows you
 301          to provide the values only, in tuples of length 2.
 302  
 303          With a classic multicall, you would write your params like:
 304  
 305              [
 306                  {"methodName": client.REMOVE, "params": ["0000000000000001"]},
 307                  {"methodName": client.REMOVE, "params": ["2fa07b6e85c40205"]},
 308              ]
 309  
 310          With multicall2, you can reduce the verbosity:
 311  
 312              [
 313                  (client.REMOVE, ["0000000000000001"]),
 314                  (client.REMOVE, ["2fa07b6e85c40205"]),
 315              ]
 316  
 317          Note:
 318              multicall2 is not part of the JSON-RPC protocol specification.
 319              It is implemented here as a simple convenience method.
 320  
 321          Parameters:
 322              calls: List of tuples composed of method name and parameters.
 323              insert_secret: Whether to insert the secret token in the parameters or not.
 324  
 325          Returns:
 326              The answer from the server, as a Python object (dict / list / str / int).
 327          """
 328          multicall_params = []
 329  
 330          for method, params in calls:
 331              params = self.get_params(*params)  # noqa: PLW2901
 332              if insert_secret and self.secret and method.startswith("aria2."):
 333                  params.insert(0, f"token:{self.secret}")
 334              multicall_params.append({"methodName": method, "params": params})
 335  
 336          payload: str = self.get_payload(self.MULTICALL, [multicall_params])  # type: ignore
 337          return self.res_or_raise(self.post(payload))
 338  
 339      def post(self, payload: str) -> dict:
 340          """Send a POST request to the server.
 341  
 342          The response is a JSON string, which we then load as a Python object.
 343  
 344          Parameters:
 345              payload: The payload / data to send to the remote process. It contains the following key-value pairs:
 346                  "jsonrpc": "2.0", "method": method, "id": id, "params": params (optional).
 347  
 348          Returns:
 349              The answer from the server, as a Python dictionary.
 350          """
 351          return requests.post(self.server, data=payload, timeout=self.timeout).json()
 352  
 353      @staticmethod
 354      def response_as_exception(response: dict) -> ClientException:
 355          """Transform the response as a [`ClientException`][aria2p.client.ClientException] instance and return it.
 356  
 357          Parameters:
 358              response: A response sent by the server.
 359  
 360          Returns:
 361              An instance of the [`ClientException`][aria2p.client.ClientException] class.
 362          """
 363          return ClientException(response["error"]["code"], response["error"]["message"])
 364  
 365      @staticmethod
 366      def res_or_raise(response: dict) -> CallReturnType:
 367          """Return the result of the response, or raise an error with code and message.
 368  
 369          Parameters:
 370              response: A response sent by the server.
 371  
 372          Returns:
 373              The "result" value of the response.
 374  
 375          Raises:
 376              ClientException: When the response contains an error (client/server error).
 377                  See the [`ClientException`][aria2p.client.ClientException] class.
 378          """
 379          if "error" in response:
 380              raise Client.response_as_exception(response)
 381          return response["result"]
 382  
 383      @staticmethod
 384      def get_payload(
 385          method: str,
 386          params: list[Any] | None = None,
 387          msg_id: int | str | None = None,
 388          as_json: bool = True,  # noqa: FBT001,FBT002
 389      ) -> str | dict:
 390          """Build a payload.
 391  
 392          Parameters:
 393              method: The method name. You can use the constant defined in [`Client`][aria2p.client.Client].
 394              params: The list of parameters.
 395              msg_id: The ID of the call, sent back with the server's answer.
 396              as_json: Whether to return the payload as a JSON-string or Python dictionary.
 397  
 398          Returns:
 399              The payload as a JSON string or as Python dictionary.
 400          """
 401          payload: dict[str, Any] = {"jsonrpc": "2.0", "method": method}
 402  
 403          if msg_id is None:
 404              payload["id"] = DEFAULT_ID
 405          else:
 406              payload["id"] = msg_id
 407  
 408          if params:
 409              payload["params"] = params
 410  
 411          return json.dumps(payload) if as_json else payload
 412  
 413      @staticmethod
 414      def get_params(*args: Any) -> list:
 415          """Build the list of parameters.
 416  
 417          This method simply removes the `None` values from the given arguments.
 418  
 419          Parameters:
 420              *args: List of parameters.
 421  
 422          Returns:
 423              A new list, with `None` values filtered out.
 424          """
 425          return [_ for _ in args if _ is not None]
 426  
 427      def add_uri(
 428          self,
 429          uris: list[str],
 430          options: dict | None = None,
 431          position: int | None = None,
 432      ) -> str:
 433          """Add a new download.
 434  
 435          This method adds a new download and returns the GID of the newly registered download.
 436  
 437          Original signature:
 438  
 439              aria2.addUri([secret], uris[, options[, position]])
 440  
 441          Parameters:
 442              uris: `uris` is an array of HTTP/FTP/SFTP/BitTorrent URIs (strings) pointing to the same resource.
 443                  If you mix URIs pointing to different resources,
 444                  then the download may fail or be corrupted without aria2 complaining.
 445                  When adding BitTorrent Magnet URIs,
 446                  uris must have only one element and it should be BitTorrent Magnet URI.
 447              options: `options` is a struct and its members are pairs of option name and value.
 448                  See [Options][aria2p.options.Options] for more details.
 449              position: If `position` is given, it must be an integer starting from 0.
 450                  The new download will be inserted at `position` in the waiting queue.
 451                  If `position` is omitted or `position` is larger than the current size of the queue,
 452                  the new download is appended to the end of the queue.
 453  
 454          Returns:
 455              The GID of the created download.
 456  
 457          Examples:
 458              **Original JSON-RPC Example**
 459  
 460              The following example adds http://example.org/file:
 461  
 462              >>> import urllib2, json
 463              >>> jsonreq = json.dumps(
 464              ...     {
 465              ...         "jsonrpc": "2.0",
 466              ...         "id": "qwer",
 467              ...         "method": "aria2.addUri",
 468              ...         "params": [["http://example.org/file"]],
 469              ...     }
 470              ... )
 471              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
 472              >>> c.read()
 473              '{"id":"qwer","jsonrpc":"2.0","result":"0000000000000001"}'
 474          """
 475          return self.call(self.ADD_URI, params=[uris, options, position])  # type: ignore
 476  
 477      def add_torrent(
 478          self,
 479          torrent: str,
 480          uris: list[str],
 481          options: dict | None = None,
 482          position: int | None = None,
 483      ) -> str:
 484          """Add a BitTorrent download.
 485  
 486          This method adds a BitTorrent download by uploading a ".torrent" file and returns the GID of the
 487          newly registered download.
 488  
 489          Original signature:
 490  
 491              aria2.addTorrent([secret], torrent[, uris[, options[, position]]])
 492  
 493          If you want to add a BitTorrent Magnet URI, use the [`add_uri()`][aria2p.client.Client.add_uri] method instead.
 494  
 495          If [`--rpc-save-upload-metadata`][aria2p.options.Options.rpc_save_upload_metadata] is true,
 496          the uploaded data is saved as a file named as the hex string of SHA-1 hash of data plus ".torrent"
 497          in the directory specified by [`--dir`][aria2p.options.Options.dir] option.
 498          E.g. a file name might be 0a3893293e27ac0490424c06de4d09242215f0a6.torrent.
 499          If a file with the same name already exists, it is overwritten!
 500          If the file cannot be saved successfully
 501          or [`--rpc-save-upload-metadata`][aria2p.options.Options.rpc_save_upload_metadata] is false,
 502          the downloads added by this method are not saved by [`--save-session`][aria2p.options.Options.save_session].
 503  
 504          Parameters:
 505              torrent: `torrent` must be a base64-encoded string containing the contents of the ".torrent" file.
 506              uris: `uris` is an array of URIs (string). `uris` is used for Web-seeding.
 507                  For single file torrents, the URI can be a complete URI pointing to the resource; if URI ends with /,
 508                  name in torrent file is added. For multi-file torrents, name and path in torrent are added to form a URI
 509                  for each file.
 510              options: `options` is a struct and its members are pairs of option name and value.
 511                  See [Options][aria2p.options.Options] for more details.
 512              position: If `position` is given, it must be an integer starting from 0.
 513                  The new download will be inserted at `position` in the waiting queue.
 514                  If `position` is omitted or `position` is larger than the current size of the queue,
 515                  the new download is appended to the end of the queue.
 516  
 517          Returns:
 518              The GID of the created download.
 519  
 520          Examples:
 521              **Original JSON-RPC Example**
 522  
 523              The following examples add local file file.torrent.
 524  
 525              >>> import urllib2, json, base64
 526              >>> torrent = base64.b64encode(open("file.torrent").read())
 527              >>> jsonreq = json.dumps(
 528              ...     {
 529              ...         "jsonrpc": "2.0",
 530              ...         "id": "asdf",
 531              ...         "method": "aria2.addTorrent",
 532              ...         "params": [torrent],
 533              ...     }
 534              ... )
 535              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
 536              >>> c.read()
 537              '{"id":"asdf","jsonrpc":"2.0","result":"0000000000000001"}'
 538          """
 539          return self.call(self.ADD_TORRENT, [torrent, uris, options, position])  # type: ignore
 540  
 541      def add_metalink(
 542          self,
 543          metalink: str,
 544          options: dict | None = None,
 545          position: int | None = None,
 546      ) -> list[str]:
 547          """Add a Metalink download.
 548  
 549          This method adds a Metalink download by uploading a ".metalink" file
 550          and returns an array of GIDs of newly registered downloads.
 551  
 552          Original signature:
 553  
 554              aria2.addMetalink([secret], metalink[, options[, position]])
 555  
 556          If [`--rpc-save-upload-metadata`][aria2p.options.Options.rpc_save_upload_metadata] is true,
 557          the uploaded data is saved as a file named hex string of SHA-1 hash of data plus ".metalink"
 558          in the directory specified by [`--dir`][aria2p.options.Options.dir] option.
 559          E.g. a file name might be 0a3893293e27ac0490424c06de4d09242215f0a6.metalink.
 560          If a file with the same name already exists, it is overwritten!
 561          If the file cannot be saved successfully
 562          or [`--rpc-save-upload-metadata`][aria2p.options.Options.rpc_save_upload_metadata] is false,
 563          the downloads added by this method are not saved by [`--save-session`][aria2p.options.Options.save_session].
 564  
 565          Parameters:
 566              metalink: `metalink` is a base64-encoded string which contains the contents of the ".metalink" file.
 567              options: `options` is a struct and its members are pairs of option name and value.
 568                  See [Options][aria2p.options.Options] for more details.
 569              position: If `position` is given, it must be an integer starting from 0.
 570                  The new download will be inserted at `position` in the waiting queue.
 571                  If `position` is omitted or `position` is larger than the current size of the queue,
 572                  the new download is appended to the end of the queue.
 573  
 574          Returns:
 575              The GID of the created download.
 576  
 577          Examples:
 578              **Original JSON-RPC Example**
 579  
 580              The following examples add local file file.meta4.
 581  
 582              >>> import urllib2, json, base64
 583              >>> metalink = base64.b64encode(open("file.meta4").read())
 584              >>> jsonreq = json.dumps(
 585              ...     {
 586              ...         "jsonrpc": "2.0",
 587              ...         "id": "qwer",
 588              ...         "method": "aria2.addMetalink",
 589              ...         "params": [metalink],
 590              ...     }
 591              ... )
 592              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
 593              >>> c.read()
 594              '{"id":"qwer","jsonrpc":"2.0","result":["0000000000000001"]}'
 595          """
 596          return self.call(self.ADD_METALINK, [metalink, options, position])  # type: ignore
 597  
 598      def remove(self, gid: str) -> str:
 599          """Remove a download.
 600  
 601          This method removes the download denoted by gid (string). If the specified download is in progress,
 602          it is first stopped. The status of the removed download becomes removed. This method returns GID of
 603          removed download.
 604  
 605          Original signature:
 606  
 607              aria2.remove([secret], gid)
 608  
 609          Parameters:
 610              gid: The download to remove.
 611  
 612          Returns:
 613              The GID of the removed download.
 614  
 615          Examples:
 616              **Original JSON-RPC Example**
 617  
 618              The following examples remove a download with GID#0000000000000001.
 619  
 620              >>> import urllib2, json
 621              >>> jsonreq = json.dumps(
 622              ...     {
 623              ...         "jsonrpc": "2.0",
 624              ...         "id": "qwer",
 625              ...         "method": "aria2.remove",
 626              ...         "params": ["0000000000000001"],
 627              ...     }
 628              ... )
 629              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
 630              >>> c.read()
 631              '{"id":"qwer","jsonrpc":"2.0","result":"0000000000000001"}'
 632          """
 633          return self.call(self.REMOVE, [gid])  # type: ignore[return-value]
 634  
 635      def force_remove(self, gid: str) -> str:
 636          """Force remove a download.
 637  
 638          This method removes the download denoted by gid.
 639          This method behaves just like [`remove()`][aria2p.client.Client.remove] except
 640          that this method removes the download without performing any actions which take time, such as contacting
 641          BitTorrent trackers to unregister the download first.
 642  
 643          Original signature:
 644  
 645              aria2.forceRemove([secret], gid)
 646  
 647          Parameters:
 648              gid: The download to force remove.
 649  
 650          Returns:
 651              The GID of the removed download.
 652          """
 653          return self.call(self.FORCE_REMOVE, [gid])  # type: ignore
 654  
 655      def pause(self, gid: str) -> str:
 656          """Pause a download.
 657  
 658          This method pauses the download denoted by gid (string).
 659          The status of paused download becomes paused.
 660          If the download was active, the download is placed in the front of waiting queue.
 661          While the status is paused, the download is not started.
 662          To change status to waiting, use the [`unpause()`][aria2p.client.Client.unpause] method.
 663  
 664          Original signature:
 665  
 666              aria2.pause([secret], gid)
 667  
 668          Parameters:
 669              gid: The download to pause.
 670  
 671          Returns:
 672              The GID of the paused download.
 673          """
 674          return self.call(self.PAUSE, [gid])  # type: ignore
 675  
 676      def pause_all(self) -> str:
 677          """Pause all active/waiting downloads.
 678  
 679          This method is equal to calling [`pause()`][aria2p.client.Client.pause] for every active/waiting download.
 680  
 681          Original signature:
 682  
 683              aria2.pauseAll([secret])
 684  
 685          Returns:
 686              `"OK"`.
 687          """
 688          return self.call(self.PAUSE_ALL)  # type: ignore
 689  
 690      def force_pause(self, gid: str) -> str:
 691          """Force pause a download.
 692  
 693          This method pauses the download denoted by gid.
 694          This method behaves just like [`pause()`][aria2p.client.Client.pause] except that
 695          this method pauses downloads without performing any actions which take time,
 696          such as contacting BitTorrent trackers to unregister the download first.
 697  
 698          Original signature:
 699  
 700              aria2.forcePause([secret], gid)
 701  
 702          Parameters:
 703              gid: The download to force pause.
 704  
 705          Returns:
 706              The GID of the paused download.
 707          """
 708          return self.call(self.FORCE_PAUSE, [gid])  # type: ignore
 709  
 710      def force_pause_all(self) -> str:
 711          """Force pause all active/waiting downloads.
 712  
 713          This method is equal to calling [`force_pause()`][aria2p.client.Client.force_pause] for every active/waiting download.
 714  
 715          Original signature:
 716  
 717              aria2.forcePauseAll([secret])
 718  
 719          Returns:
 720              `"OK"`.
 721          """
 722          return self.call(self.FORCE_PAUSE_ALL)  # type: ignore
 723  
 724      def unpause(self, gid: str) -> str:
 725          """Resume a download.
 726  
 727          This method changes the status of the download denoted by gid (string) from paused to waiting,
 728          making the download eligible to be restarted. This method returns the GID of the unpaused download.
 729  
 730          Original signature:
 731  
 732              aria2.unpause([secret], gid)
 733  
 734          Parameters:
 735              gid: The download to resume.
 736  
 737          Returns:
 738              The GID of the resumed download.
 739          """
 740          return self.call(self.UNPAUSE, [gid])  # type: ignore
 741  
 742      def unpause_all(self) -> str:
 743          """Resume all downloads.
 744  
 745          This method is equal to calling [`unpause()`][aria2p.client.Client.unpause] for every active/waiting download.
 746  
 747          Original signature:
 748  
 749              aria2.unpauseAll([secret])
 750  
 751          Returns:
 752              `"OK"`.
 753          """
 754          return self.call(self.UNPAUSE_ALL)  # type: ignore
 755  
 756      def tell_status(self, gid: str, keys: list[str] | None = None) -> dict:
 757          """Tell status of a download.
 758  
 759          This method returns the progress of the download denoted by gid (string). keys is an array of strings. If
 760          specified, the response contains only keys in the keys array. If keys is empty or omitted, the response
 761          contains all keys. This is useful when you just want specific keys and avoid unnecessary transfers. For
 762          example, `tell_status("0000000000000001", ["gid", "status"])` returns the gid and status keys only. The
 763          response is a struct and contains following keys. Values are strings.
 764  
 765          - `gid`: GID of the download.
 766          - `status`: active for currently downloading/seeding downloads. waiting for downloads in the queue; download is
 767            not started. paused for paused downloads. error for downloads that were stopped because of error.
 768            complete for stopped and completed downloads. removed for the downloads removed by user.
 769          - `totalLength`: Total length of the download in bytes.
 770          - `completedLength`: Completed length of the download in bytes.
 771          - `uploadLength`: Uploaded length of the download in bytes.
 772          - `bitfield`: Hexadecimal representation of the download progress. The highest bit corresponds to the piece at
 773            index 0. Any set bits indicate loaded pieces, while unset bits indicate not yet loaded and/or missing
 774            pieces. Any overflow bits at the end are set to zero. When the download was not started yet, this key
 775            will not be included in the response.
 776          - `downloadSpeed`: Download speed of this download measured in bytes/sec.
 777          - `uploadSpeed`: Upload speed of this download measured in bytes/sec.
 778          - `infoHash`: InfoHash. BitTorrent only.
 779          - `numSeeders`: The number of seeders aria2 has connected to. BitTorrent only.
 780          - `seeder` true if the local endpoint is a seeder. Otherwise false. BitTorrent only.
 781          - `pieceLength`: Piece length in bytes.
 782          - `numPieces`: The number of pieces.
 783          - `connections`: The number of peers/servers aria2 has connected to.
 784          - `errorCode`: The code of the last error for this item, if any. The value is a string. The error codes are defined
 785            in the EXIT STATUS section. This value is only available for stopped/completed downloads.
 786          - `errorMessage`: The (hopefully) human readable error message associated to errorCode.
 787          - `followedBy`: List of GIDs which are generated as the result of this download. For example, when aria2 downloads a
 788            Metalink file, it generates downloads described in the Metalink
 789            (see the [`--follow-metalink`][aria2p.options.Options.follow_metalink] option).
 790            This value is useful to track auto-generated downloads. If there are no such downloads,
 791            this key will not be included in the response.
 792          - `following`: The reverse link for followedBy.
 793            A download included in followedBy has this object's GID in its following value.
 794          - `belongsTo`: GID of a parent download. Some downloads are a part of another download. For example, if a file in a
 795            Metalink has BitTorrent resources, the downloads of ".torrent" files are parts of that parent. If
 796            this download has no parent, this key will not be included in the response.
 797          - `dir`:Directory to save files.
 798          - `files`: Return the list of files.
 799            The elements of this list are the same structs used in [`get_files()`][aria2p.client.Client.get_files] method.
 800          - `bittorrent`: Struct which contains information retrieved from the .torrent (file). BitTorrent only.
 801            It contains the following keys:
 802              - `announceList`: List of lists of announce URIs. If the torrent contains announce and no announce-list, announce
 803                is converted to the announce-list format.
 804              - `comment`: The comment of the torrent. comment.utf-8 is used if available.
 805              - `creationDate`: The creation time of the torrent. The value is an integer since the epoch, measured in seconds.
 806              - `mode`: File mode of the torrent. The value is either single or multi.
 807              - `info`: Struct which contains data from Info dictionary. It contains following keys.
 808                  - `name`: name in info dictionary. name.utf-8 is used if available.
 809          - `verifiedLength`: The number of verified number of bytes while the files are being hash checked. This key exists only
 810            when this download is being hash checked.
 811          - `verifyIntegrityPending`: true if this download is waiting for the hash check in a queue.
 812            This key exists only when this download is in the queue.
 813  
 814          Original signature:
 815  
 816              aria2.tellStatus([secret], gid[, keys])
 817  
 818          Parameters:
 819              gid: The download to tell status of.
 820              keys: The keys to return.
 821  
 822          Returns:
 823              The details of a download.
 824  
 825          Examples:
 826              **Original JSON-RPC Example**
 827  
 828              The following example gets information about a download with GID#0000000000000001:
 829  
 830              >>> import urllib2, json
 831              >>> from pprint import pprint
 832              >>> jsonreq = json.dumps(
 833              ...     {
 834              ...         "jsonrpc": "2.0",
 835              ...         "id": "qwer",
 836              ...         "method": "aria2.tellStatus",
 837              ...         "params": ["0000000000000001"],
 838              ...     }
 839              ... )
 840              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
 841              >>> pprint(json.loads(c.read()))
 842              {u'id': u'qwer',
 843               u'jsonrpc': u'2.0',
 844               u'result': {u'bitfield': u'0000000000',
 845                           u'completedLength': u'901120',
 846                           u'connections': u'1',
 847                           u'dir': u'/downloads',
 848                           u'downloadSpeed': u'15158',
 849                           u'files': [{u'index': u'1',
 850                                       u'length': u'34896138',
 851                                       u'completedLength': u'34896138',
 852                                       u'path': u'/downloads/file',
 853                                       u'selected': u'true',
 854                                       u'uris': [{u'status': u'used',
 855                                                  u'uri': u'http://example.org/file'}]}],
 856                           u'gid': u'0000000000000001',
 857                           u'numPieces': u'34',
 858                           u'pieceLength': u'1048576',
 859                           u'status': u'active',
 860                           u'totalLength': u'34896138',
 861                           u'uploadLength': u'0',
 862                           u'uploadSpeed': u'0'}}
 863  
 864              The following example gets only specific keys:
 865  
 866              >>> jsonreq = json.dumps(
 867              ...     {
 868              ...         "jsonrpc": "2.0",
 869              ...         "id": "qwer",
 870              ...         "method": "aria2.tellStatus",
 871              ...         "params": [
 872              ...             "0000000000000001",
 873              ...             ["gid", "totalLength", "completedLength"],
 874              ...         ],
 875              ...     }
 876              ... )
 877              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
 878              >>> pprint(json.loads(c.read()))
 879              {u'id': u'qwer',
 880               u'jsonrpc': u'2.0',
 881               u'result': {u'completedLength': u'5701632',
 882                           u'gid': u'0000000000000001',
 883                           u'totalLength': u'34896138'}}
 884          """
 885          return self.call(self.TELL_STATUS, [gid, keys])  # type: ignore
 886  
 887      def get_uris(self, gid: str) -> dict:
 888          """Return URIs used in a download.
 889  
 890          This method returns the URIs used in the download denoted by gid (string). The response is an array of
 891          structs and it contains following keys. Values are string.
 892  
 893          - `uri`: URI
 894          - `status`: 'used' if the URI is in use. 'waiting' if the URI is still waiting in the queue.
 895  
 896          Original signature:
 897  
 898              aria2.getUris([secret], gid)
 899  
 900          Parameters:
 901              gid: The download to list URIs of.
 902  
 903          Returns:
 904              The URIs used in a download.
 905  
 906          Examples:
 907              **Original JSON-RPC Example**
 908  
 909              >>> import urllib2, json
 910              >>> from pprint import pprint
 911              >>> jsonreq = json.dumps(
 912              ...     {
 913              ...         "jsonrpc": "2.0",
 914              ...         "id": "qwer",
 915              ...         "method": "aria2.getUris",
 916              ...         "params": ["0000000000000001"],
 917              ...     }
 918              ... )
 919              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
 920              >>> pprint(json.loads(c.read()))
 921              {u'id': u'qwer',
 922               u'jsonrpc': u'2.0',
 923               u'result': [{u'status': u'used',
 924                            u'uri': u'http://example.org/file'}]}
 925          """
 926          return self.call(self.GET_URIS, [gid])  # type: ignore
 927  
 928      def get_files(self, gid: str) -> dict:
 929          """Return file list of a download.
 930  
 931          This method returns the file list of the download denoted by gid (string). The response is an array of
 932          structs which contain following keys. Values are strings.
 933  
 934          - `index`: Index of the file, starting at 1, in the same order as files appear in the multi-file torrent.
 935          - `path`: File path.
 936          - `length`: File size in bytes.
 937          - `completedLength`: Completed length of this file in bytes.
 938            Please note that it is possible that sum of `completedLength`
 939            is less than the `completedLength` returned by the [`tell_status()`][aria2p.client.Client.tell_status] method.
 940            This is because `completedLength` in [`get_files()`][aria2p.client.Client.get_files] only includes completed pieces.
 941            On the other hand, `completedLength` in [`tell_status()`][aria2p.client.Client.tell_status]
 942            also includes partially completed pieces.
 943          - `selected`: true if this file is selected by [`--select-file`][aria2p.options.Options.select_file] option.
 944            If [`--select-file`][aria2p.options.Options.select_file] is not specified
 945            or this is single-file torrent or not a torrent download at all, this value is always true. Otherwise false.
 946          - `uris` Returns a list of URIs for this file.
 947            The element type is the same struct used in the [`get_uris()`][aria2p.client.Client.get_uris] method.
 948  
 949          Original signature:
 950  
 951              aria2.getFiles([secret], gid)
 952  
 953          Parameters:
 954              gid: The download to list files of.
 955  
 956          Returns:
 957              The file list of a download.
 958  
 959          Examples:
 960              **Original JSON-RPC Example**
 961  
 962              >>> import urllib2, json
 963              >>> from pprint import pprint
 964              >>> jsonreq = json.dumps(
 965              ...     {
 966              ...         "jsonrpc": "2.0",
 967              ...         "id": "qwer",
 968              ...         "method": "aria2.getFiles",
 969              ...         "params": ["0000000000000001"],
 970              ...     }
 971              ... )
 972              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
 973              >>> pprint(json.loads(c.read()))
 974              {u'id': u'qwer',
 975               u'jsonrpc': u'2.0',
 976               u'result': [{u'index': u'1',
 977                            u'length': u'34896138',
 978                            u'completedLength': u'34896138',
 979                            u'path': u'/downloads/file',
 980                            u'selected': u'true',
 981                            u'uris': [{u'status': u'used',
 982                                       u'uri': u'http://example.org/file'}]}]}
 983          """
 984          return self.call(self.GET_FILES, [gid])  # type: ignore
 985  
 986      def get_peers(self, gid: str) -> dict:
 987          """Return peers list of a download.
 988  
 989          This method returns the list of peers of the download denoted by gid (string). This method is for BitTorrent
 990          only. The response is an array of structs and contains the following keys. Values are strings.
 991  
 992          - `peerId`: Percent-encoded peer ID.
 993          - `ip`: IP address of the peer.
 994          - `port`: Port number of the peer.
 995          - `bitfield`: Hexadecimal representation of the download progress of the peer. The highest bit corresponds to
 996            the piece at index 0. Set bits indicate the piece is available and unset bits indicate the piece is
 997            missing. Any spare bits at the end are set to zero.
 998          - `amChoking`: true if aria2 is choking the peer. Otherwise false.
 999          - `peerChoking`: true if the peer is choking aria2. Otherwise false.
1000          - `downloadSpeed`: Download speed (byte/sec) that this client obtains from the peer.
1001          - `uploadSpeed`: Upload speed(byte/sec) that this client uploads to the peer.
1002          - `seeder`: true if this peer is a seeder. Otherwise false.
1003  
1004          Original signature:
1005  
1006              aria2.getPeers([secret], gid)
1007  
1008          Parameters:
1009              gid: The download to get peers from.
1010  
1011          Returns:
1012              The peers connected to a download.
1013  
1014          Examples:
1015              **Original JSON-RPC Example**
1016  
1017              >>> import urllib2, json
1018              >>> from pprint import pprint
1019              >>> jsonreq = json.dumps(
1020              ...     {
1021              ...         "jsonrpc": "2.0",
1022              ...         "id": "qwer",
1023              ...         "method": "aria2.getPeers",
1024              ...         "params": ["0000000000000001"],
1025              ...     }
1026              ... )
1027              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1028              >>> pprint(json.loads(c.read()))
1029              {u'id': u'qwer',
1030               u'jsonrpc': u'2.0',
1031               u'result': [{u'amChoking': u'true',
1032                            u'bitfield': u'ffffffffffffffffffffffffffffffffffffffff',
1033                            u'downloadSpeed': u'10602',
1034                            u'ip': u'10.0.0.9',
1035                            u'peerChoking': u'false',
1036                            u'peerId': u'aria2%2F1%2E10%2E5%2D%87%2A%EDz%2F%F7%E6',
1037                            u'port': u'6881',
1038                            u'seeder': u'true',
1039                            u'uploadSpeed': u'0'},
1040                           {u'amChoking': u'false',
1041                            u'bitfield': u'ffffeff0fffffffbfffffff9fffffcfff7f4ffff',
1042                            u'downloadSpeed': u'8654',
1043                            u'ip': u'10.0.0.30',
1044                            u'peerChoking': u'false',
1045                            u'peerId': u'bittorrent client758',
1046                            u'port': u'37842',
1047                            u'seeder': u'false',
1048                            u'uploadSpeed': u'6890'}]}
1049          """
1050          return self.call(self.GET_PEERS, [gid])  # type: ignore
1051  
1052      def get_servers(self, gid: str) -> dict:
1053          """Return servers currently connected for a download.
1054  
1055          This method returns currently connected HTTP(S)/FTP/SFTP servers of the download denoted by gid (string). The
1056          response is an array of structs and contains the following keys. Values are strings.
1057  
1058          - `index`: Index of the file, starting at 1, in the same order as files appear in the multi-file metalink.
1059          - `servers`: A list of structs which contain the following keys.
1060              - `uri`: Original URI.
1061              - `currentUri`: This is the URI currently used for downloading.
1062                If redirection is involved, currentUri and uri may differ.
1063              - `downloadSpeed`: Download speed (byte/sec).
1064  
1065          Original signature:
1066  
1067              aria2.getServers([secret], gid)
1068  
1069          Parameters:
1070              gid: The download to get servers from.
1071  
1072          Returns:
1073              The servers connected to a download.
1074  
1075          Examples:
1076              **Original JSON-RPC Example**
1077  
1078              >>> import urllib2, json
1079              >>> from pprint import pprint
1080              >>> jsonreq = json.dumps(
1081              ...     {
1082              ...         "jsonrpc": "2.0",
1083              ...         "id": "qwer",
1084              ...         "method": "aria2.getServers",
1085              ...         "params": ["0000000000000001"],
1086              ...     }
1087              ... )
1088              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1089              >>> pprint(json.loads(c.read()))
1090              {u'id': u'qwer',
1091               u'jsonrpc': u'2.0',
1092               u'result': [{u'index': u'1',
1093                            u'servers': [{u'currentUri': u'http://example.org/file',
1094                                          u'downloadSpeed': u'10467',
1095                                          u'uri': u'http://example.org/file'}]}]}
1096          """
1097          return self.call(self.GET_SERVERS, [gid])  # type: ignore
1098  
1099      def tell_active(self, keys: list[str] | None = None) -> list[dict]:
1100          """Return the list of active downloads.
1101  
1102          Original signature:
1103  
1104              aria2.tellActive([secret][, keys])
1105  
1106          Parameters:
1107              keys: The keys to return. Please refer to the [`tell_status()`][aria2p.client.Client.tell_status] method.
1108  
1109          Returns:
1110              An array of the same structs as returned by the [`tell_status()`][aria2p.client.Client.tell_status] method.
1111          """
1112          return self.call(self.TELL_ACTIVE, [keys])  # type: ignore
1113  
1114      def tell_waiting(self, offset: int, num: int, keys: list[str] | None = None) -> list[dict]:
1115          """Return the list of waiting downloads.
1116  
1117          This method returns a list of waiting downloads, including paused ones.
1118  
1119          Original signature:
1120  
1121              aria2.tellWaiting([secret], offset, num[, keys])
1122  
1123          Parameters:
1124              offset: An integer to specify the offset from the download waiting at the front.
1125                  If `offset` is a positive integer, this method returns downloads in the range of [`offset`, `offset` + `num`).
1126                  `offset` can be a negative integer. `offset == -1` points last download in the waiting queue and `offset == -2`
1127                  points the download before the last download, and so on. Downloads in the response are in reversed order then.
1128                  For example, imagine three downloads "A","B" and "C" are waiting in this order. `tell_waiting(0, 1)`
1129                  returns `["A"]`. `tell_waiting(1, 2)` returns `["B", "C"]`. `tell_waiting(-1, 2)` returns `["C", "B"]`.
1130              num: An integer to specify the maximum number of downloads to be returned.
1131              keys: The keys to return. Please refer to the [`tell_status()`][aria2p.client.Client.tell_status] method.
1132  
1133          Returns:
1134              An array of the same structs as returned by [`tell_status()`][aria2p.client.Client.tell_status] method.
1135          """
1136          return self.call(self.TELL_WAITING, [offset, num, keys])  # type: ignore
1137  
1138      def tell_stopped(self, offset: int, num: int, keys: list[str] | None = None) -> list[dict]:
1139          """Return the list of stopped downloads.
1140  
1141          This method returns a list of stopped downloads. offset is an integer and specifies the offset from the
1142          least recently stopped download.
1143  
1144          Original signature:
1145  
1146              aria2.tellStopped([secret], offset, num[, keys])
1147  
1148          Parameters:
1149              offset: Same semantics as described in the [`tell_waiting()`][aria2p.client.Client.tell_waiting] method.
1150              num: An integer to specify the maximum number of downloads to be returned.
1151              keys: The keys to return. Please refer to the [`tell_status()`][aria2p.client.Client.tell_status] method.
1152  
1153          Returns:
1154              An array of the same structs as returned by the [`tell_status()`][aria2p.client.Client.tell_status] method.
1155          """
1156          return self.call(self.TELL_STOPPED, [offset, num, keys])  # type: ignore
1157  
1158      def change_position(self, gid: str, pos: int, how: str) -> int:
1159          """Change position of a download.
1160  
1161          This method changes the position of the download denoted by `gid` in the queue.
1162  
1163          Original signature:
1164  
1165              aria2.changePosition([secret], gid, pos, how)
1166  
1167          Parameters:
1168              gid: The download to change the position of.
1169              pos: An integer.
1170              how: `POS_SET`, `POS_CUR` or `POS_END`.
1171  
1172                  - If `how` is `POS_SET`, it moves the download to a position relative to the beginning of the queue.
1173                  - If `how` is `POS_CUR`, it moves the download to a position relative to the current position.
1174                  - If `how` is `POS_END`, it moves the download to a position relative to the end of the queue.
1175                  - If the destination position is less than 0 or beyond the end of the queue,
1176                    it moves the download to the beginning or the end of the queue respectively.
1177  
1178                  For example, if GID#0000000000000001 is currently in position 3,
1179                  `change_position('0000000000000001', -1, 'POS_CUR')` will change its position to 2. Additionally
1180                  `change_position('0000000000000001', 0, 'POS_SET')` will change its position to 0 (the beginning of the queue).
1181  
1182          Returns:
1183              An integer denoting the resulting position.
1184  
1185          Examples:
1186              **Original JSON-RPC Example**
1187  
1188              The following examples move the download GID#0000000000000001 to the front of the queue.
1189  
1190              >>> import urllib2, json
1191              >>> from pprint import pprint
1192              >>> jsonreq = json.dumps(
1193              ...     {
1194              ...         "jsonrpc": "2.0",
1195              ...         "id": "qwer",
1196              ...         "method": "aria2.changePosition",
1197              ...         "params": ["0000000000000001", 0, "POS_SET"],
1198              ...     }
1199              ... )
1200              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1201              >>> pprint(json.loads(c.read()))
1202              {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': 0}
1203          """
1204          return self.call(self.CHANGE_POSITION, [gid, pos, how])  # type: ignore
1205  
1206      def change_uri(
1207          self,
1208          gid: str,
1209          file_index: int,
1210          del_uris: list[str],
1211          add_uris: list[str],
1212          position: int | None = None,
1213      ) -> list[int]:
1214          """Remove the URIs in `del_uris` from and appends the URIs in `add_uris` to download denoted by gid.
1215  
1216          Original signature:
1217  
1218              aria2.changeUri([secret], gid, fileIndex, delUris, addUris[, position])
1219  
1220          Parameters:
1221              gid: The download to change URIs of.
1222              file_index: Used to select which file to remove/attach given URIs. `file_index` is 1-based.
1223              del_uris: List of strings.
1224              add_uris: List of strings.
1225              position: Used to specify where URIs are inserted in the existing waiting URI list. `position` is 0-based.
1226                  When position is omitted, URIs are appended to the back of the list.
1227                  This method first executes the removal and then the addition.
1228                  `position` is the position after URIs are removed, not the position when this
1229                  method is called.
1230  
1231          A download can contain multiple files and URIs are attached to each file.
1232          When removing an URI, if the same URIs exist in download, only one of them is removed for
1233          each URI in `del_uris`. In other words, if there are three URIs http://example.org/aria2 and you want
1234          remove them all, you have to specify (at least) 3 http://example.org/aria2 in `del_uris`.
1235  
1236          Returns:
1237              A list which contains two integers.
1238              The first integer is the number of URIs deleted.
1239              The second integer is the number of URIs added.
1240  
1241          Examples:
1242              **Original JSON-RPC Example**
1243  
1244              The following examples add the URI http://example.org/file to the file whose index is 1 and belongs to the
1245              download GID#0000000000000001.
1246  
1247              >>> import urllib2, json
1248              >>> from pprint import pprint
1249              >>> jsonreq = json.dumps({'jsonrpc':'2.0', 'id':'qwer',
1250              ...                       'method':'aria2.changeUri',
1251              ...                       'params':['0000000000000001', 1, [],
1252                                                 ['http://example.org/file']]})
1253              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1254              >>> pprint(json.loads(c.read()))
1255              {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': [0, 1]}
1256          """
1257          return self.call(self.CHANGE_URI, [gid, file_index, del_uris, add_uris, position])  # type: ignore
1258  
1259      def get_option(self, gid: str) -> dict:
1260          """Return options of a download.
1261  
1262          Original signature:
1263  
1264              aria2.getOption([secret], gid)
1265  
1266          Parameters:
1267              gid: The download to get the options of.
1268  
1269          Returns:
1270              A struct where keys are the names of options. The values are strings.
1271              Note that this method does not return options which have
1272              no default value and have not been set on the command-line, in configuration files or RPC methods.
1273  
1274          Examples:
1275              **Original JSON-RPC Example**
1276  
1277              The following examples get options of the download GID#0000000000000001.
1278  
1279              >>> import urllib2, json
1280              >>> from pprint import pprint
1281              >>> jsonreq = json.dumps(
1282              ...     {
1283              ...         "jsonrpc": "2.0",
1284              ...         "id": "qwer",
1285              ...         "method": "aria2.getOption",
1286              ...         "params": ["0000000000000001"],
1287              ...     }
1288              ... )
1289              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1290              >>> pprint(json.loads(c.read()))
1291              {u'id': u'qwer',
1292               u'jsonrpc': u'2.0',
1293               u'result': {u'allow-overwrite': u'false',
1294                           u'allow-piece-length-change': u'false',
1295                           u'always-resume': u'true',
1296                           u'async-dns': u'true',
1297               ...
1298          """
1299          return self.call(self.GET_OPTION, [gid])  # type: ignore
1300  
1301      def change_option(self, gid: str, options: dict) -> str:
1302          """Change a download options dynamically.
1303  
1304          Original signature:
1305  
1306              aria2.changeOption([secret], gid, options)
1307  
1308          Parameters:
1309              gid: The download to change options of.
1310              options: The options listed in Input File subsection are available, except for following options:
1311  
1312                  - `dry-run`
1313                  - `metalink-base-uri`
1314                  - `parameterized-uri`
1315                  - `pause`
1316                  - `piece-length`
1317                  - `rpc-save-upload-metadata`
1318  
1319                  Except for the following options, changing the other options of active download makes it restart (restart
1320                  itself is managed by aria2, and no user intervention is required):
1321  
1322                  - `bt-max-peers`
1323                  - `bt-request-peer-speed-limit`
1324                  - `bt-remove-unselected-file`
1325                  - `force-save`
1326                  - `max-download-limit`
1327                  - `max-upload-limit`
1328  
1329          Returns:
1330              `"OK"` for success.
1331  
1332          Examples:
1333              **Original JSON-RPC Example**
1334  
1335              The following examples set the max-download-limit option to 20K for the download GID#0000000000000001.
1336  
1337              >>> import urllib2, json
1338              >>> from pprint import pprint
1339              >>> jsonreq = json.dumps(
1340              ...     {
1341              ...         "jsonrpc": "2.0",
1342              ...         "id": "qwer",
1343              ...         "method": "aria2.changeOption",
1344              ...         "params": ["0000000000000001", {"max-download-limit": "10K"}],
1345              ...     }
1346              ... )
1347              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1348              >>> pprint(json.loads(c.read()))
1349              {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': u'OK'}
1350          """
1351          return self.call(self.CHANGE_OPTION, [gid, options])  # type: ignore
1352  
1353      def get_global_option(self) -> dict:
1354          """Return the global options.
1355  
1356          Note that this method does not return options which have no default value and have not
1357          been set on the command-line, in configuration files or RPC methods. Because global options are used as a
1358          template for the options of newly added downloads, the response contains keys returned by the
1359          [`get_option()`][aria2p.client.Client.get_option] method.
1360  
1361          Original signature:
1362  
1363              aria2.getGlobalOption([secret])
1364  
1365          Returns:
1366              The global options. The response is a struct. Its keys are the names of options.
1367              Values are strings.
1368          """
1369          return self.call(self.GET_GLOBAL_OPTION)  # type: ignore
1370  
1371      def change_global_option(self, options: dict) -> str:
1372          """Change the global options dynamically.
1373  
1374          Original signature:
1375  
1376              aria2.changeGlobalOption([secret], options)
1377  
1378          Parameters:
1379              options: The following options are available:
1380  
1381                  - `bt-max-open-files`
1382                  - `download-result`
1383                  - `keep-unfinished-download-result`
1384                  - `log`
1385                  - `log-level`
1386                  - `max-concurrent-downloads`
1387                  - `max-download-result`
1388                  - `max-overall-download-limit`
1389                  - `max-overall-upload-limit`
1390                  - `optimize-concurrent-downloads`
1391                  - `save-cookies`
1392                  - `save-session`
1393                  - `server-stat-of`
1394  
1395                  In addition, options listed in the Input File subsection are available, except for following options:
1396                  `checksum`, `index-out`, `out`, `pause` and `select-file`.
1397  
1398                  With the log option, you can dynamically start logging or change log file. To stop logging, specify an
1399                  empty string ("") as the parameter value. Note that log file is always opened in append mode.
1400  
1401          Returns:
1402              `"OK"` for success.
1403          """
1404          return self.call(self.CHANGE_GLOBAL_OPTION, [options])  # type: ignore
1405  
1406      def get_global_stat(self) -> dict:
1407          """Return global statistics such as the overall download and upload speeds.
1408  
1409          Original signature:
1410  
1411              aria2.getGlobalStat([secret])
1412  
1413          Returns:
1414              A struct that contains the following keys (values are strings):
1415  
1416              - `downloadSpeed`: Overall download speed (byte/sec).
1417              - `uploadSpeed`: Overall upload speed(byte/sec).
1418              - `numActive`: The number of active downloads.
1419              - `numWaiting`: The number of waiting downloads.
1420              - `numStopped`: The number of stopped downloads in the current session. This value is capped by the
1421                  [`--max-download-result`][aria2p.options.Options.max_download_result] option.
1422              - `numStoppedTotal`: The number of stopped downloads in the current session and not capped by the
1423                  [`--max-download-result`][aria2p.options.Options.max_download_result] option.
1424  
1425          Examples:
1426              **Original JSON-RPC Example**
1427  
1428              >>> import urllib2, json
1429              >>> from pprint import pprint
1430              >>> jsonreq = json.dumps(
1431              ...     {"jsonrpc": "2.0", "id": "qwer", "method": "aria2.getGlobalStat"}
1432              ... )
1433              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1434              >>> pprint(json.loads(c.read()))
1435              {u'id': u'qwer',
1436               u'jsonrpc': u'2.0',
1437               u'result': {u'downloadSpeed': u'21846',
1438                           u'numActive': u'2',
1439                           u'numStopped': u'0',
1440                           u'numWaiting': u'0',
1441                           u'uploadSpeed': u'0'}}
1442          """
1443          return self.call(self.GET_GLOBAL_STAT)  # type: ignore
1444  
1445      def purge_download_result(self) -> str:
1446          """Purge completed/error/removed downloads from memory.
1447  
1448          Original signature:
1449  
1450              aria2.purgeDownloadResult([secret])
1451  
1452          Returns:
1453              `"OK"`.
1454          """
1455          return self.call(self.PURGE_DOWNLOAD_RESULT)  # type: ignore
1456  
1457      def remove_download_result(self, gid: str) -> str:
1458          """Remove a completed/error/removed download from memory.
1459  
1460          Original signature:
1461  
1462              aria2.removeDownloadResult([secret], gid)
1463  
1464          Parameters:
1465              gid: The download result to remove.
1466  
1467          Returns:
1468              `"OK"` for success.
1469  
1470          Examples:
1471              **Original JSON-RPC Example**
1472  
1473              The following examples remove the download result of the download GID#0000000000000001.
1474  
1475              >>> import urllib2, json
1476              >>> from pprint import pprint
1477              >>> jsonreq = json.dumps(
1478              ...     {
1479              ...         "jsonrpc": "2.0",
1480              ...         "id": "qwer",
1481              ...         "method": "aria2.removeDownloadResult",
1482              ...         "params": ["0000000000000001"],
1483              ...     }
1484              ... )
1485              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1486              >>> pprint(json.loads(c.read()))
1487              {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': u'OK'}
1488          """
1489          return self.call(self.REMOVE_DOWNLOAD_RESULT, [gid])  # type: ignore
1490  
1491      def get_version(self) -> str:
1492          """Return aria2 version and the list of enabled features.
1493  
1494          Original signature:
1495  
1496              aria2.getVersion([secret])
1497  
1498          Returns:
1499              A struct that contains the following keys:
1500  
1501              - `version`: Version number of aria2 as a string.
1502              - `enabledFeatures`: List of enabled features. Each feature is given as a string.
1503  
1504          Examples:
1505              **Original JSON-RPC Example**
1506  
1507              >>> import urllib2, json
1508              >>> from pprint import pprint
1509              >>> jsonreq = json.dumps(
1510              ...     {"jsonrpc": "2.0", "id": "qwer", "method": "aria2.getVersion"}
1511              ... )
1512              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1513              >>> pprint(json.loads(c.read()))
1514              {u'id': u'qwer',
1515               u'jsonrpc': u'2.0',
1516               u'result': {u'enabledFeatures': [u'Async DNS',
1517                                                u'BitTorrent',
1518                                                u'Firefox3 Cookie',
1519                                                u'GZip',
1520                                                u'HTTPS',
1521                                                u'Message Digest',
1522                                                u'Metalink',
1523                                                u'XML-RPC'],
1524                           u'version': u'1.11.0'}}
1525          """
1526          return self.call(self.GET_VERSION)  # type: ignore
1527  
1528      def get_session_info(self) -> dict:
1529          """Return session information.
1530  
1531          Returns:
1532              A struct that contains the `sessionId` key, which is generated each time aria2 is invoked.
1533  
1534          Original signature:
1535  
1536              aria2.getSessionInfo([secret])
1537  
1538          Examples:
1539              **Original JSON-RPC Example**
1540  
1541              >>> import urllib2, json
1542              >>> from pprint import pprint
1543              >>> jsonreq = json.dumps(
1544              ...     {"jsonrpc": "2.0", "id": "qwer", "method": "aria2.getSessionInfo"}
1545              ... )
1546              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1547              >>> pprint(json.loads(c.read()))
1548              {u'id': u'qwer',
1549               u'jsonrpc': u'2.0',
1550               u'result': {u'sessionId': u'cd6a3bc6a1de28eb5bfa181e5f6b916d44af31a9'}}
1551          """
1552          return self.call(self.GET_SESSION_INFO)  # type: ignore
1553  
1554      def shutdown(self) -> str:
1555          """Shutdown aria2.
1556  
1557          Original signature:
1558  
1559              aria2.shutdown([secret])
1560  
1561          Returns:
1562              `"OK"`.
1563          """
1564          return self.call(self.SHUTDOWN)  # type: ignore
1565  
1566      def force_shutdown(self) -> str:
1567          """Force shutdown aria2.
1568  
1569          This method shuts down aria2. This method behaves like [`shutdown()`][aria2p.client.Client.shutdown] without performing any
1570          actions which take time, such as contacting BitTorrent trackers to unregister downloads first.
1571  
1572          Original signature:
1573  
1574              aria2.forceShutdown([secret])
1575  
1576          Returns:
1577              `"OK"`.
1578          """
1579          return self.call(self.FORCE_SHUTDOWN)  # type: ignore
1580  
1581      def save_session(self) -> str:
1582          """Save the current session to a file.
1583  
1584          This method saves the current session to a file specified
1585          by the [`--save-session`][aria2p.options.Options.save_session] option.
1586  
1587          Original signature:
1588  
1589              aria2.saveSession([secret])
1590  
1591          Returns:
1592              `"OK"` if it succeeds.
1593          """
1594          return self.call(self.SAVE_SESSION)  # type: ignore
1595  
1596      # system
1597      def multicall(self, methods: list[dict]) -> list[CallReturnType]:
1598          """Call multiple methods in a single request.
1599  
1600          This methods encapsulates multiple method calls in a single request.
1601  
1602          Original signature:
1603  
1604              system.multicall(methods)
1605  
1606          Parameters:
1607              methods: An array of structs. The structs contain two keys: `methodName` and `params`.
1608                  - `methodName` is the method name to call and
1609                  - `params` is array containing parameters to the method call.
1610  
1611          Returns:
1612              An array of responses.
1613              The elements will be either a one-item array containing the return value of the method call or a struct of fault
1614              element if an encapsulated method call fails.
1615  
1616          Examples:
1617              **Original JSON-RPC Example**
1618  
1619              In the following examples, we add 2 downloads. The first one is http://example.org/file and the second one is
1620              file.torrent.
1621  
1622              >>> import urllib2, json, base64
1623              >>> from pprint import pprint
1624              >>> jsonreq = json.dumps(
1625              ...     {
1626              ...         "jsonrpc": "2.0",
1627              ...         "id": "qwer",
1628              ...         "method": "system.multicall",
1629              ...         "params": [
1630              ...             [
1631              ...                 {
1632              ...                     "methodName": "aria2.addUri",
1633              ...                     "params": [["http://example.org"]],
1634              ...                 },
1635              ...                 {
1636              ...                     "methodName": "aria2.addTorrent",
1637              ...                     "params": [base64.b64encode(open("file.torrent").read())],
1638              ...                 },
1639              ...             ]
1640              ...         ],
1641              ...     }
1642              ... )
1643              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1644              >>> pprint(json.loads(c.read()))
1645              {u'id': u'qwer', u'jsonrpc': u'2.0', u'result': [[u'0000000000000001'], [u'd2703803b52216d1']]}
1646  
1647              JSON-RPC additionally supports Batch requests as described in the JSON-RPC 2.0 Specification:
1648  
1649              >>> jsonreq = json.dumps(
1650              ...     [
1651              ...         {
1652              ...             "jsonrpc": "2.0",
1653              ...             "id": "qwer",
1654              ...             "method": "aria2.addUri",
1655              ...             "params": [["http://example.org"]],
1656              ...         },
1657              ...         {
1658              ...             "jsonrpc": "2.0",
1659              ...             "id": "asdf",
1660              ...             "method": "aria2.addTorrent",
1661              ...             "params": [base64.b64encode(open("file.torrent").read())],
1662              ...         },
1663              ...     ]
1664              ... )
1665              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1666              >>> pprint(json.loads(c.read()))
1667              [{u'id': u'qwer', u'jsonrpc': u'2.0', u'result': u'0000000000000001'},
1668               {u'id': u'asdf', u'jsonrpc': u'2.0', u'result': u'd2703803b52216d1'}]
1669          """
1670          return self.call(self.MULTICALL, [methods])  # type: ignore
1671  
1672      def list_methods(self) -> list[str]:
1673          """Return the available RPC methods.
1674  
1675          This method returns all the available RPC methods in an array of string. Unlike other methods,
1676          this method does not require secret token. This is safe because this method just returns the available
1677          method names.
1678  
1679          Original signature:
1680  
1681              system.listMethods()
1682  
1683          Returns:
1684              The list of available RPC methods.
1685  
1686          Examples:
1687              **Original JSON-RPC Example**
1688  
1689              >>> import urllib2, json
1690              >>> from pprint import pprint
1691              >>> jsonreq = json.dumps(
1692              ...     {"jsonrpc": "2.0", "id": "qwer", "method": "system.listMethods"}
1693              ... )
1694              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1695              >>> pprint(json.loads(c.read()))
1696              {u'id': u'qwer',
1697               u'jsonrpc': u'2.0',
1698               u'result': [u'aria2.addUri',
1699                           u'aria2.addTorrent',
1700               ...
1701          """
1702          return self.call(self.LIST_METHODS)  # type: ignore
1703  
1704      def list_notifications(self) -> list[str]:
1705          """Return all the available RPC notifications.
1706  
1707          This method returns all the available RPC notifications in an array of string. Unlike other methods,
1708          this method does not require secret token. This is safe because this method just returns the available
1709          notifications names.
1710  
1711          Original signature:
1712  
1713              system.listNotifications()
1714  
1715          Returns:
1716              The list of available RPC notifications.
1717  
1718          Examples:
1719              **Original JSON-RPC Example**
1720  
1721              >>> import urllib2, json
1722              >>> from pprint import pprint
1723              >>> jsonreq = json.dumps(
1724              ...     {"jsonrpc": "2.0", "id": "qwer", "method": "system.listNotifications"}
1725              ... )
1726              >>> c = urllib2.urlopen("http://localhost:6800/jsonrpc", jsonreq)
1727              >>> pprint(json.loads(c.read()))
1728              {u'id': u'qwer',
1729               u'jsonrpc': u'2.0',
1730               u'result': [u'aria2.onDownloadStart',
1731                           u'aria2.onDownloadPause',
1732               ...
1733          """
1734          return self.call(self.LIST_NOTIFICATIONS)  # type: ignore
1735  
1736      # notifications
1737      def listen_to_notifications(
1738          self,
1739          on_download_start: Callable | None = None,
1740          on_download_pause: Callable | None = None,
1741          on_download_stop: Callable | None = None,
1742          on_download_complete: Callable | None = None,
1743          on_download_error: Callable | None = None,
1744          on_bt_download_complete: Callable | None = None,
1745          timeout: int = 5,
1746          handle_signals: bool = True,  # noqa: FBT001,FBT002
1747      ) -> None:
1748          """Start listening to aria2 notifications via WebSocket.
1749  
1750          This method opens a WebSocket connection to the server and wait for notifications (or events) to be received.
1751          It accepts callbacks as arguments, which are functions accepting one parameter called "gid", for each type
1752          of notification.
1753  
1754          Stop listening to notifications with the [`stop_listening`][aria2p.client.Client.stop_listening] method.
1755  
1756          Parameters:
1757              on_download_start: Callback for the `onDownloadStart` event.
1758              on_download_pause: Callback for the `onDownloadPause` event.
1759              on_download_stop: Callback for the `onDownloadStop` event.
1760              on_download_complete: Callback for the `onDownloadComplete` event.
1761              on_download_error: Callback for the `onDownloadError` event.
1762              on_bt_download_complete: Callback for the `onBtDownloadComplete` event.
1763              timeout: Timeout when waiting for data to be received. Use a small value for faster reactivity
1764                  when stopping to listen. Default is 5 seconds.
1765              handle_signals: Whether to add signal handlers to gracefully stop the loop on SIGTERM and SIGINT.
1766          """
1767          self.listening = True
1768          ws_server = self.ws_server
1769          log_prefix = f"Notifications ({ws_server})"
1770  
1771          logger.debug(f"{log_prefix}: opening WebSocket with timeout={timeout}")
1772          try:
1773              socket = websocket.create_connection(ws_server, timeout=timeout)
1774          except (ConnectionRefusedError, ConnectionResetError):
1775              logger.error(f"{log_prefix}: connection refused. Is the server running?")
1776              return
1777  
1778          callbacks = {
1779              NOTIFICATION_START: on_download_start,
1780              NOTIFICATION_PAUSE: on_download_pause,
1781              NOTIFICATION_STOP: on_download_stop,
1782              NOTIFICATION_COMPLETE: on_download_complete,
1783              NOTIFICATION_ERROR: on_download_error,
1784              NOTIFICATION_BT_COMPLETE: on_bt_download_complete,
1785          }
1786  
1787          stopped = SignalHandler(["SIGTERM", "SIGINT"]) if handle_signals else False
1788  
1789          while not stopped:
1790              logger.debug(f"{log_prefix}: waiting for data over WebSocket")
1791              try:
1792                  message = socket.recv()
1793              except websocket.WebSocketConnectionClosedException:
1794                  logger.error(f"{log_prefix}: connection to server was closed. Is the server running?")
1795                  break
1796              except websocket.WebSocketTimeoutException:
1797                  logger.debug(f"{log_prefix}: reached timeout ({timeout}s)")
1798              else:
1799                  notification = Notification.get_or_raise(json.loads(message))
1800                  logger.info(
1801                      f"{log_prefix}: received {notification.type} with gid={notification.gid}",
1802                  )
1803                  callback = callbacks.get(notification.type)
1804                  if callable(callback):
1805                      logger.debug(f"{log_prefix}: calling {callback} with gid={notification.gid}")
1806                      callback(notification.gid)
1807                  else:
1808                      logger.debug(f"{log_prefix}: no callback given for type " + notification.type)
1809  
1810              if not self.listening:
1811                  logger.debug(f"{log_prefix}: stopped listening")
1812                  break
1813  
1814          if stopped:
1815              logger.debug(f"{log_prefix}: stopped listening after receiving a signal")
1816              self.listening = False
1817  
1818          logger.debug(f"{log_prefix}: closing WebSocket")
1819          socket.close()
1820  
1821      def stop_listening(self) -> None:
1822          """Stop listening to notifications.
1823  
1824          Although this method returns instantly, the actual listening loop can take some time to break out,
1825          depending on the timeout that was given to [`Client.listen_to_notifications`][aria2p.client.Client.listen_to_notifications].
1826          """
1827          self.listening = False
1828  
1829  
1830  class Notification:
1831      """A helper class for notifications.
1832  
1833      You should not need to use this class. It simply provides methods to instantiate a notification with a
1834      message received from the server through a WebSocket, or to raise a ClientException if the message is invalid.
1835      """
1836  
1837      def __init__(self, event_type: str, gid: str) -> None:
1838          """Initialize the object.
1839  
1840          Parameters:
1841              event_type: The notification type. Possible types are available in the NOTIFICATION_TYPES variable.
1842              gid: The GID of the download related to the notification.
1843          """
1844          self.type = event_type
1845          self.gid = gid
1846  
1847      @staticmethod
1848      def get_or_raise(message: dict) -> Notification:
1849          """Raise a ClientException when the message is invalid or return a Notification instance.
1850  
1851          Parameters:
1852              message: The JSON-loaded message received over WebSocket.
1853  
1854          Returns:
1855              A Notification instance if the message is valid.
1856  
1857          Raises:
1858              ClientException: When the message contains an error.
1859          """
1860          if "error" in message:
1861              raise Client.response_as_exception(message)
1862          return Notification.from_message(message)
1863  
1864      @staticmethod
1865      def from_message(message: dict) -> Notification:
1866          """Return an instance of Notification.
1867  
1868          This method expects a valid message (not containing errors).
1869  
1870          Parameters:
1871              message: A valid message received over WebSocket.
1872  
1873          Returns:
1874              A Notification instance.
1875          """
1876          return Notification(event_type=message["method"], gid=message["params"][0]["gid"])