
    NQji                        d Z ddlZddlZddlZddlZddl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mZ ddlmZmZ ddlmZ  ej        e          ZdZd	Zd
ZdZd
ZdZdZ edd          Zi ddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-d.d/d0d1d2d3iZ d4e!d5ee!         fd6Z"d7e!d5e#fd8Z$d4e!d9e!d5dfd:Z%d4e!d9e!d5dfd;Z&d<e
d5e'fd=Z(d>e
d?e
d5dfd@Z)dFdAZ*d<e
dBe!d5dfdCZ+ G dD dE          Z,dS )Ga  
DM Pairing System

Code-based approval flow for authorizing new users on messaging platforms.
Instead of static allowlists with user IDs, unknown users receive a one-time
pairing code that the bot owner approves via the CLI.

Security features (based on OWASP + NIST SP 800-63-4 guidance):
  - 8-char codes from 32-char unambiguous alphabet (no 0/O/1/I)
  - Cryptographic randomness via secrets.choice()
  - 1-hour code expiry
  - Max 3 pending codes per platform
  - Rate limiting: 1 request per user per 10 minutes
  - Lockout after 5 failed approval attempts (1 hour)
  - File permissions: chmod 0600 on all data files
  - Codes are never logged to stdout

Storage: ~/.hermes/pairing/
    N)Path)Optional)expand_whatsapp_aliasesnormalize_whatsapp_identifier)get_hermes_dirget_hermes_home)atomic_replace ABCDEFGHJKLMNPQRSTUVWXYZ23456789   i  iX        zplatforms/pairingpairingtelegramTELEGRAM_ALLOWED_USERSdiscordDISCORD_ALLOWED_USERSwhatsappWHATSAPP_ALLOWED_USERSwhatsapp_cloudWHATSAPP_CLOUD_ALLOWED_USERSslackSLACK_ALLOWED_USERSsignalSIGNAL_ALLOWED_USERSemailEMAIL_ALLOWED_USERSsmsSMS_ALLOWED_USERS
mattermostMATTERMOST_ALLOWED_USERSmatrixMATRIX_ALLOWED_USERSdingtalkDINGTALK_ALLOWED_USERSfeishuFEISHU_ALLOWED_USERSwecomWECOM_ALLOWED_USERSwecom_callbackWECOM_CALLBACK_ALLOWED_USERSweixinWEIXIN_ALLOWED_USERSbluebubblesBLUEBUBBLES_ALLOWED_USERSqqbotQQ_ALLOWED_USERSyuanbaoYUANBAO_ALLOWED_USERSplatformreturnc                    | pd                                                                 } t                              |           }|r|S 	 ddlm} |                    |           }|r|j        r|j        S n# t          $ r Y nw xY wdS )zReturn the per-platform allowlist env var name, or None.

    Falls back to the platform registry for plugin platforms so a plugin's
    own ``allowed_users_env`` is honored too.
     r   )platform_registryN)lowerstrip_PLATFORM_ALLOWLIST_ENVgetgateway.platform_registryr7   allowed_users_env	Exception)r3   env_varr7   entrys       3/home/alina/.hermes/hermes-agent/gateway/pairing.py_allowlist_env_for_platformrB   W   s     B%%''--//H%))(33G ??????!%%h// 	+U, 	+**   4s   *A4 4
B Brawc                 @    d |                      d          D             S )Nc                 ^    g | ]*}|                                 |                                 +S  )r9   ).0uids     rA   
<listcomp>z$_split_allowlist.<locals>.<listcomp>m   s-    AAACSYY[[ACIIKKAAA    ,)split)rC   s    rA   _split_allowlistrM   l   s     AA399S>>AAAArJ   user_idc                    t          |           }|sdS t          j        |d                                          }|sdS t	          |          }d|v st          |          |v rdS |                    t          |                     	 ddlm}  ||d	                    |                     dS # t          $ r Y dS w xY w)u  Add ``user_id`` to the platform allowlist env var IF one is configured.

    Option (i): only materialize the grant into the allowlist when the operator
    already runs an allowlist for this platform. On an open gateway (no
    allowlist) we do nothing — the pairing store remains the grant record and
    the authz union honors it, so we never silently convert an open gateway into
    a locked one on first pairing.
    Nr6   *r   )save_env_valuerK   )rB   osgetenvr9   rM   strappendhermes_cli.configrQ   joinr>   )r3   rN   r?   currentidsrQ   s         rA   _sync_allowlist_addrZ   p   s     *(33G i$$**,,G 
7
#
#C
czzS\\S((JJs7||444444w.....    	s   %B/ /
B=<B=c                    t          |           }|sdS t          j        |d                                          }|sdS t	          |          }fd|D             }t          |          t          |          k    rdS 	 ddlm}m} |r! ||d	                    |                     dS  ||           dS # t          $ r Y dS w xY w)zBRemove ``user_id`` from the platform allowlist env var if present.Nr6   c                 :    g | ]}|t                    k    |S rF   )rT   )rG   irN   s     rA   rI   z*_sync_allowlist_remove.<locals>.<listcomp>   s)    555q1G#4#4#4#4#4rJ   r   )rQ   remove_env_valuerK   )rB   rR   rS   r9   rM   lenrV   rQ   r^   rW   r>   )r3   rN   r?   rX   rY   	remainingrQ   r^   s    `      rA   _sync_allowlist_removera      s   )(33G i$$**,,G 
7
#
#C5555C555I
9~~S!!FFFFFFFF 	&N7CHHY$7$788888W%%%%%   s    )B8 +B8 8
CCpathc                     |                                  r`	 t          j        |                     d                    }t	          |t
                    r|ni S # t          j        t          f$ r i cY S w xY wi S )Nutf-8encoding)existsjsonloads	read_text
isinstancedictJSONDecodeErrorOSError)rb   datas     rA   _load_json_filerp      s~    {{}} 	:dnngn>>??D%dD11944r9$g. 	 	 	III	Is   A A A21A2
active_diralternate_dirc           	         |                                 r*|                                 |                                k    rdS |                     dd           |                    d          D ]}|                                s| |j        z  }t          |          }|s3t          |          }t          |          }|                    |           ||k    r%t          |t          j        |dd                     dS )a  Merge split legacy/new pairing data into the active PairingStore dir.

    Older installs use ``{HERMES_HOME}/pairing`` while newer code/docs may
    write ``{HERMES_HOME}/platforms/pairing``. If both directories exist, the
    gateway must not silently ignore approved users sitting in the inactive
    location; otherwise already-paired Feishu users get asked for a fresh code.
    NTparentsexist_okz*.json   Findentensure_ascii)rg   resolvemkdirglobis_filenamerp   rl   update_secure_writerh   dumps)rq   rr   srcdestmergedrX   befores          rA   _merge_pairing_dirr      s    !! Z%7%7%9%9]=R=R=T=T%T%TTD111!!(++ R R{{}} 	CH$ %% 	!$''ggV$
6!% P P PQQQR RrJ   c                      t                      } | dz  }| dz  dz  }t          }|                                |                                k    r|n|}t          ||           d S )Nr   	platforms)r   PAIRING_DIRr{   r   )homeold_dirnew_diractive	alternates        rA   _migrate_split_pairing_dirsr      sh    DYG[ 9,GF!>>++w/@/@@@gIvy)))))rJ   ro   c                 j   | j                             dd           t          j        t	          | j                   d          \  }}	 t          j        |dd          5 }|                    |           |                                 t          j	        |
                                           ddd           n# 1 swxY w Y   t          ||            	 t          j        | d	           dS # t          $ r Y dS w xY w# t          $ r( 	 t          j        |           n# t          $ r Y nw xY w w xY w)
u   Write data to file with restrictive permissions (owner read/write only).

    Uses a temp-file + atomic rename so readers always see either the old
    complete file or the new one — never a partial write.
    Trt   z.tmp)dirsuffixwrd   re   Ni  )parentr|   tempfilemkstemprT   rR   fdopenwriteflushfsyncfilenor	   chmodrn   BaseExceptionunlink)rb   ro   fdtmp_pathfs        rA   r   r      s    	KdT222#DK(8(8HHHLBYr3111 	!QGGDMMMGGIIIHQXXZZ   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	x&&&	HT5!!!!! 	 	 	DD	   	Ih 	 	 	D	ss   	D   AB<0D  <C  D  C D  C/ /
C=9D  <C==D   
D2D D2 
D-*D2,D--D2c            
       <   e Zd ZdZd)dee         fdZedee         fd            Zdede	fdZ
dede	fd	Zde	fd
Zde	defdZde	deddfdZdededefdZdededee         fdZdedededefdZdededefdZd)dedefdZd*dedededdfdZdededefdZedededefd            Z	 d*dedededee         fdZdededee         fdZd)dedefd Zd)dede fd!Z!dededefd"Z"dededdfd#Z#dedefd$Z$deddfd%Z%deddfd&Z&d'edefd(Z'dS )+PairingStorea[  
    Manages pairing codes and approved user lists.

    Data files per platform:
      - {platform}-pending.json   : pending pairing requests
      - {platform}-approved.json  : approved (paired) users
      - _rate_limits.json         : rate limit tracking

    When constructed with ``profile="<name>"``, storage lives under
    ``<HERMES_HOME>/profiles/<name>/pairing/`` (per-profile, used by
    multiplexing gateways so each profile has its own whitelist).
    Without a profile, storage is the global ``<HERMES_HOME>/pairing/``
    directory (backward-compat for the ``hermes pairing`` CLI).
    Nprofilec                     |rddl m}  |            dz  |z  dz  | _        nt          | _        | j                            dd           |st                       t          j                    | _        || _	        d S )Nr   )r   profilesr   Trt   )
hermes_constantsr   _dirr   r|   r   	threadingRLock_lock_profile)selfr   r   s      rA   __init__zPairingStore.__init__   s      	$888888'))J6@9LDII#DI	t444 	* ())) _&&
rJ   r4   c                     | j         S )zCProfile name this store is scoped to, or None for the global store.)r   r   s    rA   r   zPairingStore.profile  s     }rJ   r3   c                     | j         | dz  S )Nz-pending.jsonr   r   r3   s     rA   _pending_pathzPairingStore._pending_path  s    yh55555rJ   c                     | j         | dz  S )Nz-approved.jsonr   r   s     rA   _approved_pathzPairingStore._approved_path  s    yh66666rJ   c                     | j         dz  S )Nz_rate_limits.jsonr   r   s    rA   _rate_limit_pathzPairingStore._rate_limit_path  s    y...rJ   rb   c                    |                                 r	 t          j        |                    d                    S # t          $ r}	 |                                }d|j         dt          |j                  dd           }n# t          $ r d}Y nw xY wt          t          d          rt          j                    nd}t                              d	||||           i cY d }~S d }~wt          j        t          f$ r i cY S w xY wi S )
Nrd   re   z
owner_uid=z mode=z<stat failed>geteuidzn/aa)  Pairing file %s exists but is not readable as uid=%s (%s; %s). If you ran `docker exec <container> hermes pairing approve ...` as root, re-run with `docker exec -u hermes <container> ...` and chown the existing file to the hermes user, or restart the container so the entrypoint can fix ownership.)rg   rh   ri   rj   PermissionErrorstatst_uidoctst_modern   hasattrrR   r   loggerwarningrm   )r   rb   est
owner_infoeuids         rA   
_load_jsonzPairingStore._load_json  s4   ;;== 	z$..'."B"BCCC"   1B!Ubi!U!Us2:rss?S!U!UJJ 1 1 1!0JJJ1 (/r9'='=Hrz|||5E
 $
A   						('2   				s@   '> 
D	;BC&BC&BAC& D&DDro   c                 P    t          |t          j        |dd                     d S )Nrw   Frx   )r   rh   r   )r   rb   ro   s      rA   
_save_jsonzPairingStore._save_json<  s)    dDJtAEJJJKKKKKrJ   rN   c                 z    t          |pd                                          }|dk    rt          |          p|S |S )z<Normalize platform-specific user IDs before persisting them.r6   r   )rT   r9   r   )r   r3   rN   raw_user_ids       rA   _normalize_user_idzPairingStore._normalize_user_id?  sC    '-R((..00z!!0==LLrJ   c                    t          |pd                                          }|st                      S ||                     ||          h}|dk    r"|                    t          |                     |                    d           |S )z@Return all known equivalent user IDs for auth/rate-limit checks.r6   r   )rT   r9   setr   r   r   discard)r   r3   rN   r   aliasess        rA   _user_id_aliaseszPairingStore._user_id_aliasesF  s    '-R((..00 	55L 7 7+ N NOz!!NN2;??@@@rJ   leftrightc                     |                      ||          }|                      ||          }t          |o|o||z            S )z;Return True when two user IDs represent the same principal.)r   bool)r   r3   r   r   left_aliasesright_aliasess         rA   _user_ids_matchzPairingStore._user_ids_matchR  sI    ,,Xt<<--h>>LU]U}8TVVVrJ   c                     |                      |                     |                    }|D ]}|                     |||          r dS dS )z3Check if a user is approved (paired) on a platform.TF)r   r   r   )r   r3   rN   approvedapproved_user_ids        rA   is_approvedzPairingStore.is_approvedZ  s]    ??4#6#6x#@#@AA ( 	 	##H.>HH tturJ   c                     g }|r|gn|                      d          }|D ]^}|                     |                     |                    }|                                D ]\  }}|                    ||d|            _|S )z5List approved users, optionally filtered by platform.r   )r3   rN   )_all_platformsr   r   itemsrU   )r   r3   resultsr   pr   rH   infos           rA   list_approvedzPairingStore.list_approvedb  s    "*OXJJ0C0CJ0O0O	 	H 	HAt':':1'='=>>H%^^-- H H	TA#FFFGGGGHrJ   r6   	user_namec                 \                                                                   }                     |           fd|D             }|D ]}||= |t          j                    d|<                                                       |           t                     dS )zAAdd a user to the approved list. Must be called under self._lock.c                 B    g | ]}                     |          |S rF   r   )rG   r   normalized_user_idr3   r   s     rA   rI   z.PairingStore._approve_user.<locals>.<listcomp>p  sC     
 
 
 ##H.>@RSS

 
 
rJ   )r   approved_atN)r   r   r   timer   rZ   )r   r3   rN   r   r   duplicate_idsr   r   s   ``     @rA   _approve_userzPairingStore._approve_userl  s    ??4#6#6x#@#@AA!44XwGG
 
 
 
 
 
$,
 
 

 !. 	+ 	+)** #9;;(
 (
#$ 	++H55x@@@
 	H&899999rJ   c                 <                                    } j        5                       |          } fd|D             }|r<|D ]}||=                      ||           t	                     	 ddd           dS 	 ddd           n# 1 swxY w Y   dS )z<Remove a user from the approved list. Returns True if found.c                 B    g | ]}                     |          |S rF   r   )rG   r   r3   r   rN   s     rA   rI   z'PairingStore.revoke.<locals>.<listcomp>  sB       $''2BGLL   rJ   NTF)r   r   r   r   ra   )r   r3   rN   rb   r   matching_idsr   s   ```    rA   revokezPairingStore.revoke  s;   ""8,,Z 	 	t,,H     (0  L
  (4 3 3$ !122h/// 'x999	 	 	 	 	 	 	 		 	 	 	 	 	 	 	 	 	 	 	 	 	 	  us   ABBBcodesaltc                 z    t          j        ||                     d          z                                             S )z6Hash a pairing code with the given salt using SHA-256.rd   )hashlibsha256encode	hexdigest)r   r   s     rA   
_hash_codezPairingStore._hash_code  s1     ~dT[[%9%99::DDFFFrJ   c                    | j         5  |                     |           |                     ||          }|                     |          r	 ddd           dS |                     ||          r	 ddd           dS |                     |                     |                    }t          |          t          k    r	 ddd           dS d	                    d t          t                    D                       }t          j        d          }|                     ||          }t          j        d          }	||                                ||t%          j                    d||	<   |                     |                     |          |           |                     ||           |cddd           S # 1 swxY w Y   dS )a  
        Generate a pairing code for a new user.

        Returns the code string, or None if:
          - User is rate-limited (too recent request)
          - Max pending codes reached for this platform
          - User/platform is in lockout due to failed attempts

        The code is NOT stored in plaintext.  Only a salted SHA-256 hash is
        persisted so that reading the pending file does not reveal codes.
        Nr6   c              3   H   K   | ]}t          j        t                    V  d S N)secretschoiceALPHABET)rG   _s     rA   	<genexpr>z-PairingStore.generate_code.<locals>.<genexpr>  s,      PP7>(33PPPPPPrJ      r   )hashr   rN   r   
created_at)r   _cleanup_expiredr   _is_locked_out_is_rate_limitedr   r   r_   MAX_PENDING_PER_PLATFORMrW   rangeCODE_LENGTHrR   urandomr   r   	token_hexhexr   r   _record_rate_limit)
r   r3   rN   r   r   pendingr   r   	code_hashentry_ids
             rA   generate_codezPairingStore.generate_code  sN    Z (	 (	!!(+++!%!8!87!K!K ""8,, (	 (	 (	 (	 (	 (	 (	 (	 $$Xw77 (	 (	 (	 (	 (	 (	 (	 (	 ood&8&8&B&BCCG7||777(	 (	 (	 (	 (	 (	 (	 (	$ 77PPU;=O=OPPPPPD :b>>Dd33I (++H "

-&"ikk! !GH OOD..x88'BBB ##Hg666Q(	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	s&   AF3F3;AF3	CF33F7:F7c           	         | j         5  |                     |           |                                                                }|                     |          r	 ddd           dS |                     |                     |                    }d}d}|                                D ]\  }}t          |t                    sd|vsd|vr$	 t                              |d                   }n# t          $ r Y Rw xY w|                     ||          }	t          j        |	|d                   r|}|} n|#|                     |           	 ddd           dS ||= |                     |                     |          |           |                     ||d         |                    dd                     |d         |                    dd          dcddd           S # 1 swxY w Y   dS )u  
        Approve a pairing code. Adds the user to the approved list.

        Returns ``{user_id, user_name}`` on success, ``None`` if the code is
        invalid/expired OR the platform is currently locked out after
        ``MAX_FAILED_ATTEMPTS`` failed approvals (#10195). Callers can
        disambiguate with ``_is_locked_out(platform)``.

        Verification: the user-provided code is hashed with each stored
        entry's salt and compared to the stored hash using constant-time
        comparison. Pre-hash entries (legacy plaintext-key format from
        pre-upgrade pending.json files) are silently ignored — they get
        pruned at TTL by ``_cleanup_expired``.
        Nr   r  rN   r   r6   )rN   r   )r   r  upperr9   r  r   r   r   rk   rl   bytesfromhex
ValueErrorr   r   compare_digest_record_failed_attemptr   r   r;   )
r   r3   r   r  matched_keymatched_entryr  r@   r   candidate_hashs
             rA   approve_codezPairingStore.approve_code  s    Z 3	 3	!!(+++::<<%%''D ""8,, 3	 3	 3	 3	 3	 3	 3	 3	 ood&8&8&B&BCCG K M#*==??  %!%.. &&&*=*= ==v77DD!   H!%t!<!<).%-HH "*K$)ME
 "++H555O3	 3	 3	 3	 3	 3	 3	 3	R $OOD..x88'BBB xy)A,00bAAC C C )3*..{B?? a3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	 3	sE   AG"'A$G" C-,G"-
C:7G"9C::AG"A;G""G&)G&c                    g }| j         5  |r|gn|                     d          }|D ]A}|                     |           |                     |                     |                    }|                                D ]\  }}t          |t                    s|                    d          }t          |t          t          f          sMt          t          j                    |z
  dz            }	|                    d          }
t          |
t                    r
|
dd         nd}|                    |||                    dd	          |                    d
d	          |	d           C	 ddd           n# 1 swxY w Y   |S )u  List pending pairing requests, optionally filtered by platform.

        Codes are stored hashed — the ``code`` field is replaced with the
        first 8 hex characters of the hash so admins can distinguish entries
        without revealing the original code. Legacy plaintext-key entries
        (pre-hash format) are shown with a "legacy" placeholder so admins
        can see them age out without crashing on a missing ``hash`` field.
        r  r  <   r  Nr   legacyrN   r6   r   )r3   r   rN   r   age_minutes)r   r   r  r   r   r   rk   rl   r;   intfloatr   rT   rU   )r   r3   r   r   r   r  r  r   r  age_minhash_valcode_displays               rA   list_pendingzPairingStore.list_pending  s    Z 	 	&.R

D4G4G	4R4RI  %%a(((//$*<*<Q*?*?@@&-mmoo  NHd%dD11 ! !%,!7!7J%j3,?? ! !49;;#;r"ABBG#xx//H3=h3L3L#Z8BQB<<RZLNN$% ,#'88Ir#:#:%)XXk2%>%>'.$ $    	 	 	 	 	 	 	 	 	 	 	 	 	 	 	* s   E E77E;>E;c                 N   | j         5  d}|r|gn|                     d          }|D ]e}|                     |                     |                    }|t	          |          z  }|                     |                     |          i            f	 ddd           n# 1 swxY w Y   |S )z2Clear all pending requests. Returns count removed.r   r  N)r   r   r   r   r_   r   )r   r3   countr   r   r  s         rA   clear_pendingzPairingStore.clear_pending<  s    Z 	; 	;E&.R

D4G4G	4R4RI ; ;//$*<*<Q*?*?@@W% 2 21 5 5r::::;	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; s   BBB!Bc                    |                      |                                           }|                     ||          D ]A}| d| }|                    |d          }t	          j                    |z
  t
          k     r dS BdS )z2Check if a user has requested a code too recently.:r   TF)r   r   r   r;   r   RATE_LIMIT_SECONDS)r   r3   rN   limitsaliaskeylast_requests          rA   r  zPairingStore._is_rate_limitedI  s    !6!6!8!899**8W== 	 	E''''C!::c1--L	l*.@@@tt AurJ   c                    |                      |                                           }t          j                    }|                     ||          D ]}| d| }|||<   |                     |                                 |           dS )z7Record the time of a pairing request for rate limiting.r-  N)r   r   r   r   r   )r   r3   rN   r/  nowr0  r1  s          rA   r  zPairingStore._record_rate_limitS  s    !6!6!8!899ikk**8W== 	 	E''''CF3KK--//88888rJ   c                     |                      |                                           }d| }|                    |d          }t          j                    |k     S )zBCheck if a platform is in lockout due to failed approval attempts.	_lockout:r   )r   r   r;   r   )r   r3   r/  lockout_keylockout_untils        rA   r  zPairingStore._is_locked_out\  sP    !6!6!8!899,(,,

;22y{{]**rJ   c           	         |                      |                                           }d| }|                    |d          dz   }|||<   |t          k    rMd| }t	          j                    t
          z   ||<   d||<   t          d| dt
           dt           dd	
           |                     |                                 |           dS )zMRecord a failed approval attempt. Triggers lockout after MAX_FAILED_ATTEMPTS.z
_failures:r      r6  z[pairing] Platform z locked out for zs after z failed attemptsT)r   N)r   r   r;   MAX_FAILED_ATTEMPTSr   LOCKOUT_SECONDSprintr   )r   r3   r/  fail_keyfailsr7  s         rA   r  z#PairingStore._record_failed_attemptc  s   !6!6!8!899***

8Q''!+ x'''0h00K"&)++"?F; F8 A A A/ A A.A A AHLN N N N--//88888rJ   c                 &   |                      |          }|                     |          }t          j                    }g }|                                D ]\  }}t	          |t
                    s|                    |           0|                    d          }t	          |t          t          f          s|                    |           w||z
  t          k    r|                    |           |r |D ]}||= |                     ||           dS dS )u   Remove expired pending codes.

        Tolerant of malformed / legacy entries — anything without a numeric
        ``created_at`` is treated as expired (it's effectively unusable
        with the new hash-keyed schema anyway).
        r  N)r   r   r   r   rk   rl   rU   r;   r#  r$  CODE_TTL_SECONDSr   )	r   r3   rb   r  r4  expiredr  r   r  s	            rA   r  zPairingStore._cleanup_expireds  s     !!(++//$''ikk%mmoo 		) 		)NHddD)) x(((,//Jj3,77 x(((j $444x((( 	+# & &H%%OOD'*****	+ 	+rJ   r   c                    g }t                                           D ]i}|j                            d| d          rI|j                            d| dd          }|                    d          s|                    |           j|S )z:List all platforms that have data files of a given suffix.-z.jsonr6   r  )r   iterdirr   endswithreplace
startswithrU   )r   r   r   r   r3   s        rA   r   zPairingStore._all_platforms  s    	$$&& 	/ 	/Av0600011 /6>>*;f*;*;*;R@@**3// /$$X...rJ   r   )r6   )(__name__
__module____qualname____doc__r   rT   r   propertyr   r   r   r   r   rl   r   r   r   r   r   r   r   r   listr   r   r   staticmethodr  r   r  r  r(  r#  r+  r  r  r  r  r  r   rF   rJ   rA   r   r      s9                   & #    X6c 6d 6 6 6 67s 7t 7 7 7 7/$ / / / /t     @Lt L4 LD L L L L3      
 
s 
s3x 
 
 
 
W W3 Ws Wt W W W WC # $     c T    : :c :C :C :QU : : : :.s S T    . G GE Gc G G G \G
 =?6 66&)6696	#6 6 6 6pBS B B B B B BH S D    B	 	c 	S 	 	 	 	 s t    93 9 9 9 9 9 9+s +t + + + +9s 9t 9 9 9 9 + + + + + +4S T      rJ   r   )r4   N)-rL  r   rh   loggingrR   r   r   r   r   pathlibr   typingr   gateway.whatsapp_identityr   r   r   r   r   utilsr	   	getLoggerrI  r   r   r  rA  r.  r<  r	  r;  r   r:   rT   rB   rN  rM   rZ   ra   rl   rp   r   r   r   r   rF   rJ   rA   <module>rV     s   (    				                           = < < < < < < <            		8	$	$ .      n0)<<(& ( 4	
 " $ " 
 , $ ( $ " 4 $  .!" #$ &%  ,# (3-    *B# B$ B B B B#      :S 3 4    .$ 4    R4 R R R R R R4* * * * C D    4j j j j j j j j j jrJ   