o
    n~b                     @   s  d Z ddlmZ ddlmZ ddlmZmZ ddlm	Z	m
Z
 ddlmZmZ ddlmZ ddlZddlZddlZdd	lmZ dd
lmZ ddlmZ ddlmZ ddlmZ ddlmZmZ ddl m!Z! ddl"m#Z# ddl$m%Z%m&Z& ddl'm(Z(m)Z) ddl*m+Z+ zddl,Z,W n e-y   ddl.m,Z, Y nw dd Z/d&ddZ0d'ddZ1G dd de2Z3G d d! d!e)Z4G d"d# d#e(Z5G d$d% d%e2Z6dS )(a  
The :class:`.CDNClient` class provides a simple API for downloading Steam content from SteamPipe

Initializing :class:`.CDNClient` requires a logged in :class:`.SteamClient` instance

.. warning::
    This module uses :mod:`requests` library, which is not gevent cooperative by default.
    It is high recommended that you use :meth:`steam.monkey.patch_minimal()`.
    See example below

.. code:: python
    import steam.monkey
    steam.monkey.patch_minimal()

    from steam.client import SteamClient, EMsg
    from steam.client.cdn import CDNClient

    mysteam = SteamClient()
    mysteam.cli_login()
    ...
    mycdn = CDNClient(mysteam)


Getting depot manifests for an app

.. code:: python

    >>> mycdn.get_manifests(570)
    [<CDNDepotManifest('Dota 2 Content', app_id=570, depot_id=373301, gid=6397590570861788404, creation_time='2019-06-29 16:03:11')>,
     <CDNDepotManifest('Dota 2 Content 2', app_id=570, depot_id=381451, gid=5769691971272474272, creation_time='2019-06-29 00:19:02')>,
     <CDNDepotManifest('Dota 2 Content 3', app_id=570, depot_id=381452, gid=3194393866044592918, creation_time='2019-06-27 00:05:38')>,
     <CDNDepotManifest('Dota 2 Content 4', app_id=570, depot_id=381453, gid=8005824150061180163, creation_time='2019-06-08 07:49:57')>,
     <CDNDepotManifest('Dota 2 Content 5', app_id=570, depot_id=381454, gid=9003299908441378336, creation_time='2019-06-26 18:56:19')>,
     <CDNDepotManifest('Dota 2 Content 6', app_id=570, depot_id=381455, gid=8000458746487720619, creation_time='2019-06-29 00:19:43')>,
     <CDNDepotManifest('Dota 2 Win32', app_id=570, depot_id=373302, gid=3561463682334619841, creation_time='2019-06-29 00:16:28')>,
     <CDNDepotManifest('Dota 2 Win64', app_id=570, depot_id=373303, gid=6464064782313084040, creation_time='2019-06-29 00:16:43')>,
     <CDNDepotManifest('Dota 2 Mac', app_id=570, depot_id=373304, gid=5979018571482579541, creation_time='2019-06-29 00:16:59')>,
     <CDNDepotManifest('Dota 2 English', app_id=570, depot_id=373305, gid=4435851250675935801, creation_time='2015-06-01 20:15:37')>,
     <CDNDepotManifest('Dota 2 Linux', app_id=570, depot_id=373306, gid=4859464855297921815, creation_time='2019-06-29 00:17:25')>,
     <CDNDepotManifest('Dota 2 Korean', app_id=570, depot_id=373308, gid=8598853793233320583, creation_time='2019-03-05 17:16:49')>,
     <CDNDepotManifest('Dota 2 Simplified Chinese', app_id=570, depot_id=373309, gid=6975893321745168138, creation_time='2019-06-25 21:40:37')>,
     <CDNDepotManifest('Dota 2 Russian', app_id=570, depot_id=381456, gid=5425063725991897591, creation_time='2019-03-05 17:19:53')>,
     <CDNDepotManifest('Dota 2 Workshop tools', app_id=570, depot_id=381450, gid=8629205096668418087, creation_time='2019-06-29 16:04:18')>,
     <CDNDepotManifest('Dota 2 OpenGL Windows', app_id=570, depot_id=401531, gid=6502316736107281444, creation_time='2019-06-07 19:04:08')>,
     <CDNDepotManifest('Dota 2 Vulkan Common', app_id=570, depot_id=401535, gid=6405492872419215600, creation_time='2019-06-07 19:04:11')>,
     <CDNDepotManifest('Dota 2 Vulkan Win64', app_id=570, depot_id=401536, gid=3821288251412129608, creation_time='2019-06-25 21:42:29')>,
     <CDNDepotManifest('Dota 2 Vulkan Linux64', app_id=570, depot_id=401537, gid=3144805829218032316, creation_time='2019-06-17 16:54:43')>,
     <CDNDepotManifest('Dota 2 VR', app_id=570, depot_id=313255, gid=706332602567268673, creation_time='2017-10-04 18:52:14')>,
     <CDNDepotManifest('Dota 2 Vulkan Mac', app_id=570, depot_id=401538, gid=2223235822414824351, creation_time='2019-06-11 19:37:19')>]

    >>> mycdn.get_manifests(570, filter_func=lambda depot_id, info: 'Dota 2 Content' in info['name'])
    [<CDNDepotManifest('Dota 2 Content', app_id=570, depot_id=373301, gid=6397590570861788404, creation_time='2019-06-29 16:03:11')>,
     <CDNDepotManifest('Dota 2 Content 2', app_id=570, depot_id=381451, gid=5769691971272474272, creation_time='2019-06-29 00:19:02')>,
     <CDNDepotManifest('Dota 2 Content 3', app_id=570, depot_id=381452, gid=3194393866044592918, creation_time='2019-06-27 00:05:38')>,
     <CDNDepotManifest('Dota 2 Content 4', app_id=570, depot_id=381453, gid=8005824150061180163, creation_time='2019-06-08 07:49:57')>,
     <CDNDepotManifest('Dota 2 Content 5', app_id=570, depot_id=381454, gid=9003299908441378336, creation_time='2019-06-26 18:56:19')>,
     <CDNDepotManifest('Dota 2 Content 6', app_id=570, depot_id=381455, gid=8000458746487720619, creation_time='2019-06-29 00:19:43')>]


Listing files

.. code:: python

    >>> file_list = mycdn.iter_files(570)
    >>> list(file_list)[:10]
    [<CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota_addons\dungeon\particles\test_particle\generic_attack_crit_blur_rope.vpcf_c', 2134)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota_addons\dungeon\materials\blends\mud_brick_normal_psd_5cc4fe8b.vtex_c', 351444)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota_addons\hero_demo\scripts\vscripts\la_spawn_enemy_at_target.lua', 1230)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota_addons\winter_2018\particles\dark_moon\darkmoon_last_hit_effect_damage_flash_b.vpcf_c', 1386)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota_addons\dungeon\scripts\vscripts\abilities\siltbreaker_line_wave.lua', 3305)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota_addons\dungeon\materials\models\heroes\broodmother\broodmother_body_poison.vmat_c', 10888)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota\resource\cursor\workshop\sltv_shaker_cursor_pack\cursor_spell_default.ani', 4362)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota_addons\overthrow\panorama\images\custom_game\team_icons\team_icon_tiger_01_png.vtex_c', 18340)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota\resource\cursor\valve\ti7\cursor_attack_illegal.bmp', 4152)>,
     <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota_addons\winter_2018\models\creeps\ice_biome\undeadtusk\undead_tuskskeleton01.vmdl_c', 13516)>

Reading a file directly from SteamPipe

.. code:: python

    >>> file_list = mycdn.iter_files(570, r'game\dota\gameinfo.gi')
    >>> myfile = next(file_list)
    <CDNDepotFile(570, 373301, 6397590570861788404, 'game\dota\gameinfo.gi', 6808)>
    >>> print(myfile.read(80).decode('utf-8'))
    "GameInfo"
    {
            game            "Dota 2"
            title           "Dota 2"

            gamelogo 1
            type            multiplayer_only
    ...

    )ZipFile)BytesIO)OrderedDictdeque)
itervalues	iteritems)crc32	unhexlify)datetimeN)Pool)LRUCache)webapi)
SteamError)MsgProto)EResultEType)EMsg)make_requests_session)symmetric_decryptsymmetric_decrypt_ecb)DepotManifest	DepotFile)ContentManifestPayload)lzmac                 C   s   t dt| |d S )zDecrypt manifest gid v2 bytes

    :param encrypted_gid: encrypted gid v2 bytes
    :type  encrypted_gid: bytes
    :param password: encryption password
    :type  password: byt
    :return: manifest gid
    :rtype: int
    z<Qr   )structunpackr   )Zencrypted_gidpassword r   ;/usr/local/lib/python3.10/dist-packages/steam/client/cdn.pydecrypt_manifest_gid_2{   s   
r   cs.steamcontent.comP      c                 C   s   |dkrdnd}d|||| |f }|du rt  n|}||}|jdkr&g S tj|jtd}|dd	kr7g S g }	t|d
 D ];}
t }|
d |_	|
d dkrQdnd|_
|
d |_|
d |_|j
rcdnd|_|
d |_|
d |_|
d |_|	| q?|	S )a  Get a list of CS servers from a single CS server

    :param cell_id: location cell id
    :type  cell_id: bytes
    :param host: CS server host
    :type  host: str
    :param port: server port number
    :type  port: int
    :param num_servers: number of servers to return
    :type  num_servers: int
    :param session: requests Session instance
    :type  session: :class:`requests.Session`
    :return: list of CS servers
    :rtype: :class:`list` [:class:`.ContentServer`]
      httpshttpz%s://%s:%s/serverlist/%s/%s/N   )Zmapperdeferred1Z
serverlisttypehttps_support	mandatoryTFHostvhostr!   cellloadZweightedload)r   getstatus_codevdfloadstextr   r   ContentServerr)   r$   hostr-   portcell_idr/   weighted_loadappend)r8   r6   r7   num_serverssessionprotourlrespkvserversentryserverr   r   r   get_content_servers_from_cs   s,   







rD   c                 C   s   | |d}t jdd|d}g }|d d D ]=}t }|d |_|d d	kr'd
nd|_|d |_|d |_|jr9dnd|_|dd|_|d |_	|d |_
|| q|S )a  Get a list of CS servers from Steam WebAPI

    :param cell_id: location cell id
    :type  cell_id: bytes
    :param num_servers: number of servers to return
    :type  num_servers: int
    :return: list of CS servers
    :rtype: :class:`list` [:class:`.ContentServer`]
    )r8   Zmax_serversZIContentServerDirectoryServiceZGetServersForSteamPipe)paramsresponserA   r)   r*   r+   TFr6   r-   r#   r!   r8   r   r/   r9   )r   r0   r5   r)   r$   r6   r-   r7   r8   r/   r9   r:   )r8   r;   rE   r?   rA   rB   rC   r   r   r   get_content_servers_from_webapi   s   






rG   c                   @   s4   e Zd ZdZdZdZdZdZdZdZ	dZ
dd ZdS )r5   FNr   c                 C   s2   d| j j| jr	dnd| j| jt| jt| jf S )Nz'<%s('%s://%s:%s', type=%s, cell_id=%s)>r$   r%   )	__class____name__r$   r6   r7   reprr)   r8   selfr   r   r   __repr__   s   zContentServer.__repr__)rI   
__module____qualname__r$   r6   r-   r7   r)   r8   r/   r9   rM   r   r   r   r   r5      s    r5   c                   @   s   e Zd Zdd Zdd Zedd Zdd Zd d
dZdd Z	dd Z
dd Zdd Zdd Zdd Zd!ddZdd Zdd ZdS )"CDNDepotFilec                 C   sJ   t |ts	tdt |tjstdt| || d| _d| _d| _	dS )a/  File-like object proxy for content files located on SteamPipe

        :param manifest: parrent manifest instance
        :type  manifest: :class:`.CDNDepotManifest`
        :param file_mapping: file mapping instance from manifest
        :type  file_mapping: ContentManifestPayload.FileMapping
        z.Expected 'manifest' to be of type CDNDepotFilezHExpected 'file_mapping' to be of type ContentManifestPayload.FileMappingr   N    )

isinstanceCDNDepotManifest	TypeErrorr   ZFileMappingr   __init__offset_lc_lcbuff)rL   manifestZfile_mappingr   r   r   rU      s   

zCDNDepotFile.__init__c                 C   s:   d| j j| jj| jj| jjt| j| jrdf S | j	f S )Nz<%s(%s, %s, %s, %s, %s)>zis_directory=True)
rH   rI   rY   app_iddepot_idgidrJ   Zfilename_rawZis_directorysizerK   r   r   r   rM      s   zCDNDepotFile.__repr__c                 C      | j S )z:type: bool)is_filerK   r   r   r   seekable  s   zCDNDepotFile.seekablec                 C   s   | j std| jS )z
:type: intDThis file is not seekable, probably because its directory or symlink)r`   
ValueErrorrV   rK   r   r   r   tell  s   zCDNDepotFile.tellr   c                 C   sp   | j std|dkr|dk rtdn|dkr| j| }n|dkr(| j| }ntdtdt| j|| _dS )zSeen file

        :param offset: file offset
        :type  offset: int
        :param whence: offset mode, see :meth:`io.IOBase.seek`
        :type  whence: int
        ra   r   zInvalid argument      zInvalid value for whenceN)r`   rb   IOErrorrV   r]   maxmin)rL   rV   whencer   r   r   seek  s   zCDNDepotFile.seekc                 C   sB   | j r
| j j|jkr| jj| jj| jj|j | _|| _ | jS N)	rW   sharY   
cdn_client	get_chunkrZ   r[   hexrX   )rL   chunkr   r   r   
_get_chunk)  s   zCDNDepotFile._get_chunkc                 C      | S rk   r   rK   r   r   r   __iter__3     zCDNDepotFile.__iter__c                 C   s   |   S rk   )nextrK   r   r   r   __next__6  s   zCDNDepotFile.__next__c                 C   rr   rk   r   rK   r   r   r   	__enter__9  rt   zCDNDepotFile.__enter__c                 C   s   d S rk   r   )rL   r)   value	tracebackr   r   r   __exit__<  rt   zCDNDepotFile.__exit__c                 C   s   |   }|dkr
t|S )NrQ   )readlineStopIteration)rL   liner   r   r   ru   ?  s   zCDNDepotFile.nextc                 C   s>  |dkr
| j | j }|dks| j| j ks| j dkrdS | j| }| jrH| j| jjkrH|| jj| jj krH| j| j| jj | j| jj |  }nNt }d}| jD ]8}|j|krY n0|j}||j }|| j  krl|k syn ||  k rw|krn qP|du r|j}|| | qP|	| j|  |
|}t| j || _|S )zRead bytes from the file

        :param length: number of bytes to read. Read the whole file if not set
        :type  length: int
        :returns: file data
        :rtype: bytes
        r~   r   rQ   N)r]   rV   rW   Zcb_originalrX   r   chunkswriterq   rj   readrh   )rL   length
end_offsetdataZstart_offsetrp   Zchunk_startZ	chunk_endr   r   r   r   E  s>   
(




zCDNDepotFile.readc                    sp   d}t  fdddD ]*}|d}|dkr1|d7 }||d| 7 }  jt||    |S ||7 }q|S )zTRead a single line

        :return: single file line
        :rtype: bytes
        rQ   c                      s
     dS )N   )r   r   rK   r   r   <lambda>{     
 z'CDNDepotFile.readline.<locals>.<lambda>   
r~   rd   N)iterfindrj   rV   len)rL   bufrp   posr   rK   r   r{   s  s   

zCDNDepotFile.readlinec                 C   s   dd | D S )zzGet file contents as list of lines

        :return: list of lines
        :rtype: :class:`list` [:class:`bytes`]
        c                 S   s   g | ]}|qS r   r   ).0r}   r   r   r   
<listcomp>  s    z*CDNDepotFile.readlines.<locals>.<listcomp>r   rK   r   r   r   	readlines  s   zCDNDepotFile.readlinesN)r   )r~   )rI   rN   rO   rU   rM   propertyr`   rc   rj   rq   rs   rv   rw   rz   ru   r   r{   r   r   r   r   r   rP      s     




.rP   c                   @   s,   e Zd ZeZdZdd Zdd Zdd ZdS )rS   Nc                 C   s   || _ || _t| | dS )a  Holds manifest metadata and file list.

        :param cdn_client: CDNClient instance
        :type  cdn_client: :class:`.CDNClient`
        :param app_id: App ID
        :type  app_id: int
        :param data: serialized manifest data
        :type  data: bytes
        N)rm   rZ   r   rU   )rL   rm   rZ   r   r   r   r   rU     s   
zCDNDepotManifest.__init__c                 C   s   d dt| j dt| j dt| j dtt| jj	
 dd g}| jr3t| jd | }| jr:|d7 }d	| jj|f S )
N, zapp_id=z	depot_id=zgid=zcreation_time=T z, filenames_encrypted=Truez<%s(%s)>)joinstrrZ   r[   r\   rJ   r
   utcfromtimestampmetadataZcreation_time	isoformatreplacenameZfilenames_encryptedrH   rI   )rL   rE   r   r   r   rM     s    	zCDNDepotManifest.__repr__c                 C   s2   t | | | jjD ]}|jjdd dd q
d S )Nc                 S   r^   rk   )rV   xr   r   r   r     s    z.CDNDepotManifest.deserialize.<locals>.<lambda>F)keyreverse)r   deserializepayloadZmappingsr   sort)rL   r   mappingr   r   r   r     s   zCDNDepotManifest.deserialize)	rI   rN   rO   rP   ZDepotFileClassr   rU   rM   r   r   r   r   r   rS     s    rS   c                   @   s   e Zd ZeZed Ze Z	e
dZdZdd Zdd Zdd Zd#d	d
Zd$ddZdd Zdd Zdd Zd%ddZdd Zdd Zd&ddZd'dd Zd!d" ZdS )(	CDNClientr"   r   c                 C   sj   t d| _|| _| jr| jj| _t | _i | _i | _i | _i | _	t
 | _t
 | _| js/|   |   dS )zCDNClient allows loading and reading of manifests for Steam apps are used
        to list and download content

        :param client: logged in SteamClient instance
        :type  client: :class:`.SteamClient`
           N)GPoolgpoolsteamr8   r   web
depot_keys	manifests
app_depotsbeta_passwordssetlicensed_app_idslicensed_depot_idsrA   fetch_content_serversload_licenses)rL   clientr   r   r   rU     s   

zCDNClient.__init__c                 C   s"   | j   | j  | j  dS )zTCleared cached information. Next call on methods with caching will return fresh dataN)r   clearr   r   rK   r   r   r   clear_cache  s   

zCDNClient.clear_cachec                 C   s   | j   | j  | jjjtjkrdg}n| jjs"| j	
d dS ttdd t| jj}t| jj|dd D ]\}}| j |d   | j|d	   q:dS )
zTRead licenses from SteamClient instance, required for determining accessible contentiE  z/No steam licenses found on SteamClient instanceNc                 S   s   | j | jdS )N)Z	packageidaccess_token)
package_idr   )lr   r   r   r     s    z)CDNClient.load_licenses.<locals>.<lambda>)packagesr   ZappidsZdepotids)r   r   r   r   Zsteam_idr)   r   ZAnonUserZlicenses_LOGdebuglistmapr   r   get_product_infoupdatevalues)rL   r   r   infor   r   r   r     s   



zCDNClient.load_licensesc                 C   sL   | j   | jd t| j}tdd |}| j | | j s$tddS )zyUpdate CS server list

        :param num_servers: numbers of CS server to fetch
        :type  num_servers: int
        z.Trying to fetch content servers from Steam APIc                 S   s
   | j dkS )NZ	OpenCache)r)   )rC   r   r   r   r     r   z1CDNClient.fetch_content_servers.<locals>.<lambda>zFailed to fetch content serversN)	rA   r   r   r   rG   r8   filterextendr   )rL   r;   rA   r   r   r   r     s   

zCDNClient.fetch_content_serversFc                 C   s   |r| j d | j d S )zGet a CS server for content download

        :param rotate: forcefully rotate server list and get a new server
        :type  rotate: bool
        r~   r   )rA   rotate)rL   r   r   r   r   get_content_server
  s   
zCDNClient.get_content_serverc                 C   s`   || j vr+| j||}|r|jtjkr|j| j |< ntd|du r%tjt|j| j | S )a  Get depot key, which is needed to decrypt files

        :param app_id: app id
        :type  app_id: int
        :param depot_id: depot id
        :type  depot_id: int
        :return: returns decryption key
        :rtype: bytes
        :raises SteamError: error message
        zFailed getting depot keyN)	r   r   get_depot_keyeresultr   OKZdepot_encryption_keyr   Timeout)rL   rZ   r[   msgr   r   r   r     s   

zCDNClient.get_depot_keyc              
   C   s   |   }	 d|jrdnd|j|j||f }z
| jj|dd}W n ty8 } z| jd| W Y d}~n,d}~ww |j	r>|S d	|j
  krId
k rZn n| jd|j
 td|j
 | jd | j dd}q)a	  Run CDN command request

        :param command: command name
        :type  command: str
        :param args: args
        :type  args: str
        :returns: requests response
        :rtype: :class:`requests.Response`
        :raises SteamError: on error
        Tz%s://%s:%s/%s/%sr$   r%   
   timeoutzRequest error: %sNi  i  zGot HTTP %szHTTP Error %sg      ?)r   )r   r$   r6   r7   r   r0   	Exceptionr   r   okr1   r   r   sleep)rL   commandargsrC   r>   r?   expr   r   r   cdn_cmd*  s.   zCDNClient.cdn_cmdc                 C   s^  ||f| j vr| dd||f }t|j| ||}|dd dkr|dd dkr7tdt|dd  |dd	 d
krKtdt|dd	  ttj	|dd }tj
tj|gd}td|dd \}}	||dd d|	 }t||krtdntt|}
|
|
jd }W d   n1 sw   Y  || j ||f< | j ||f S )a=  Download a single content chunk

        :param app_id: App ID
        :type  app_id: int
        :param depot_id: Depot ID
        :type  depot_id: int
        :param chunk_id: Chunk ID
        :type  chunk_id: int
        :returns: chunk data
        :rtype: bytes
        :raises SteamError: error message
        depotz%s/chunk/%sNre   s   VZs   zvzVZ: Invalid footer: %s      azVZ: Invalid version: %s      )filtersz<IIiiz6VZ: CRC32 checksum doesn't match for decompressed datar   )_chunk_cacher   r   contentr   r   rJ   r   _decode_filter_propertiesFILTER_LZMA1LZMADecompressor
FORMAT_RAWr   r   
decompressr   r   r   r   filelist)rL   rZ   r[   Zchunk_idr?   r   ZvzfilterZvzdecZchecksumZdecompressed_sizezfr   r   r   rn   N  s(   zCDNClient.get_chunkTc                 C   sp   |||f| j vr0| dd||f }|jr0| | ||j}|r(|| || || j |||f< | j |||f S )a  Download a manifest file

        :param app_id: App ID
        :type  app_id: int
        :param depot_id: Depot ID
        :type  depot_id: int
        :param manifest_gid: Manifest gid
        :type  manifest_gid: int
        :param decrypt: Decrypt manifest filenames
        :type  decrypt: bool
        :returns: manifest instance
        :rtype: :class:`.CDNDepotManifest`
        r   z%s/manifest/%s/5)r   r   r   DepotManifestClassr   Zdecrypt_filenamesr   )rL   rZ   r[   manifest_giddecryptr?   rY   r   r   r   get_manifestw  s   zCDNClient.get_manifestc                 C   s   | j ttj||d}|jtjkr7| j	dd
tdd |j |jD ]}t|j| j||j f< q&n| j	dt|j  t|jS )zCheck branch beta password to unlock encrypted branches

        :param app_id: App ID
        :type  app_id: int
        :param password: beta password
        :type  password: str
        :returns: result
        :rtype: :class:`.EResult`
        )rZ   betapasswordz$Unlocked following beta branches: %sr   c                 S   s
   | j  S rk   )betanamelowerr   r   r   r   r     r   z/CDNClient.check_beta_password.<locals>.<lambda>z"App beta password check failed. %r)r   Zsend_job_and_waitr   r   ZClientCheckAppBetaPasswordr   r   r   r   r   r   r   Zbetapasswordsr	   r   r   r   r   )rL   rZ   r   r?   rB   r   r   r   check_beta_password  s   


zCDNClient.check_beta_passwordc                 C   s4   || j vr| j|gd | d | j |< | j | S )NZappsdepots)r   r   r   )rL   rZ   r   r   r   get_app_depot_info  s   
 
zCDNClient.get_app_depot_infopublicNc                    s6    |}d}||di vrtdt||f t|d | dddkrVd}||f jvrV|s8td|  ||}|tjkrItd| ||f jvrVtd	|  fd
d}	g }
i }t	|D ]\}}|
 smqdt|}|ry|||syqd|r| jvr| jvr jdt|d|| qdd|v r|t|d t | qd|r|di |i d}|durtt| j||f }n|di d}n	|di |}|dur|
 j|	|||||d| qdg }|
D ]	}||  qt	|D ]\}}||fdd}| j||d7 }q|S )a  Get a list of CDNDepotManifest for app

        :param app_id: App ID
        :type  app_id: int
        :param branch: branch name
        :type  branch: str
        :param password: branch password for locked branches
        :type  password: str
        :param filter_func:
            Function to filter depots. ``func(depot_id, depot_info)``
        :returns: list of :class:`.CDNDepotManifest`
        :rtype: :class:`list` [:class:`.CDNDepotManifest`]
        :raises SteamError: error message
        FZbranchesz No branch named %s for app_id %sZpwdrequiredr   TzBranch %r requires a passwordz Branch password is not valid. %rz Incorrect password for branch %rc                    s     | |||}||_|S rk   )r   r   )rZ   r[   r   r   r   rY   rK   r   r   async_fetch_manifest  s   z5CDNClient.get_manifests.<locals>.async_fetch_manifestz)No license for depot %s (%s). Skipping...r   ZdepotfromappZencryptedmanifestsZencrypted_gid_2Nr   r   c                 S   s   t | |v o|d u p|| |S rk   )int)r[   
depot_info	depot_idsZffuncr   r   r   nested_ffunc  s   z-CDNClient.get_manifests.<locals>.nested_ffunc)filter_func)r   r0   r   rJ   r   r   r   r   r   r   isdigitr   r   r   r   
setdefaultr   addr   r	   r:   r   spawnget_manifests)rL   rZ   branchr   r   r   r   Zis_enc_branchresultr   tasksZshared_depotsr[   r   Zegidr   r   taskr   r   r   rK   r   r     sx   


	zCDNClient.get_manifestsc                 c   s2    |  ||||D ]}||D ]}|V  qq	dS )a[  Like :meth:`.get_manifests` but returns a iterator that goes through all the files
        in all the manifest.

        :param app_id: App ID
        :type  app_id: int
        :param filename_filter: wildcard filter for file paths
        :type  branch: str
        :param branch: branch name
        :type  branch: str
        :param password: branch password for locked branches
        :type  password: str
        :param filter_func:
            Function to filter depots. ``func(depot_id, depot_info)``
        :returns: generator of of CDN files
        :rtype: [:class:`.CDNDepotFile`]
        N)r   
iter_files)rL   rZ   Zfilename_filterr   r   r   rY   fpr   r   r   r   !  s   zCDNClient.iter_filesc                 C   s   | j jd|gdddddddddd
dd}|jjtjkr't|jjp"d|jj|d	u r-d	n|jj	d }|d	u s=|j
tjkrMtd
|d	u rGtjt|j
|jsVtdtj|j }}| |||j}|j|_|S )a  Get the manifest file for a worshop item that is hosted on SteamPipe

        :param item_id: Workshop ID
        :type  item_id: int
        :returns: manifest instance
        :rtype: :class:`.CDNDepotManifest`
        :raises SteamError: error message
        zPublishedFile.GetDetails#1FTr   )
ZpublishedfileidsZincludetagsZincludeadditionalpreviewsZincludechildrenZincludekvtagsZincludevotesZshort_descriptionZincludeforsaledataZincludemetadatalanguager   r   z
No messageNz!Failed getting workshop file infoz!Workshop file is not on SteamPipe)r   Zsend_um_and_waitheaderr   r   r   r   error_messagebodyZpublishedfiledetailsr   r   Zhcontent_fileZFileNotFoundZconsumer_appidr   titler   )rL   Zitem_idr?   ZwfrZ   Z	ws_app_idrY   r   r   r   get_manifest_for_workshop_item6  s:   	
z(CDNClient.get_manifest_for_workshop_itemr"   )F)T)r   NNT)Nr   NN)rI   rN   rO   rS   r   logging	getLoggerr   r   rA   r   r   r8   rU   r   r   r   r   r   r   rn   r   r   r   r   r   r  r   r   r   r   r     s(    



$
)

ur   )r    r!   r"   Nr  )7__doc__zipfiler   ior   collectionsr   r   sixr   r   binasciir   r	   r
   r  r   r2   Zgevent.poolr   r   Z
cachetoolsr   r   r   Zsteam.exceptionsr   Zsteam.core.msgr   Zsteam.enumsr   r   Zsteam.enums.emsgr   Zsteam.utils.webr   Zsteam.core.cryptor   r   Zsteam.core.manifestr   r   Z$steam.protobufs.content_manifest_pb2r   r   ImportErrorZ	backportsr   rD   rG   objectr5   rP   rS   r   r   r   r   r   <module>   sD    _

/ (0