+
    ;deje#              
           R t ^ RIt^ RIt^ RIt^ RIHt ^ RIt^ RIH	t	 ]P                  ! ]4      tRt]R,           tRtRtRRR	RR
RRRRR/tRtRRRRRR/tR^RRR^ RRRR/t^t ! R R]4      t ! R R4      tR# ) uY  Publicações societárias via o portal novo do IRN.

Substitui o `mj_publications.py`, que estava dormente desde abril: o
`publicacoes.mj.pt` tem reCAPTCHA **e** AJAX Control Toolkit NoBot, e nem os
POSTs que não envolvem captcha passam. Não vale a pena insistir nesse caminho.

O IRN migrou a pesquisa para uma app OutSystems em
`meu.registo.justica.gov.pt/empresa/pesquisapublica`, cujos screenservices
devolvem JSON **sem captcha, a partir de IP de datacenter** — testado do próprio
CT sobre as 94 empresas monitorizadas, 94/94 sem erros, 2105 publicações.

Isto torna obsoletos o proxy residencial (~€3-10/mês) e a extensão Chrome que
obrigava alguém a navegar o portal à mão.

## Protocolo

1. `GET moduleservices/moduleinfo` -> `manifest.versionToken` = moduleVersion.
   Lido em runtime porque muda a cada deploy do IRN.
2. Um POST a qualquer screenservice devolve **403 mas estabelece a sessão
   anónima** (cookies `nr1Users`/`nr2Users`).
3. O token CSRF vem **dentro** do cookie `nr2Users` (`crf=…`, url-encoded) e vai
   no header `X-CSRFToken`. Sem ele tudo devolve 403 "Invalid Login" — é o
   detalhe que destranca o acesso.

Cada resposta traz `versionInfo.hasApiVersionChanged`; quando vier `true`, o IRN
fez deploy e os `apiVersion` abaixo têm de ser reextraídos de
`/EOL/scripts/EOL.PublicSearchFlow.PublicSearchBlock.mvc.js`.
NAny)random_delayz+https://meu.registo.justica.gov.pt/empresa/z$screenservices/EOL/PublicSearchFlow/zPublicSearchFlow.PublicSearchz#PublicSearchFlow.PublicationContentpublicationssite_propertiescontent_wrapperfetch_inputscontentz1900-01-01T00:00:00Code LabelGenericCodeStructureIdOrderCodigoTODOSGUIDc                       ] tR t^>tRtRtR# )IrnVersionChangedzDO IRN fez deploy e os apiVersion codificados aqui ficaram obsoletos. N)__name__
__module____qualname____firstlineno____doc____static_attributes__r       app/scrapers/mj_irn.pyr   r   >   s    Nr   r   c                      a  ] tR t^Bt o RtRV 3R lR lltV 3R lR ltV 3R lR lt]V 3R lR	 l4       t	V 3R
 lR lt
V 3R lR lt]3V 3R lR lltRV 3R lR lltV 3R lR ltRtV tR# )MjIrnClientu=   Segura a sessão anónima e o token CSRF durante uma corrida.c                $   < V ^8  d   QhRS[ RR/# )   timeoutreturnN)float)format__classdict__s   "r   __annotate__MjIrnClient.__annotate__E   s     & & & &r   c                <    R V n         Wn        R V n        R V n        R # N)_client_timeout_module_version_csrf)selfr"   s   &&r   __init__MjIrnClient.__init__E   s    15+/!%
r   c                   < V ^8  d   QhRR/# )r!   r#   r   r   )r%   r&   s   "r   r'   r(   K   s      - r   c                   "   \         P                  ! \         P                  ! V P                  R R7      RRRR/RR7      V n        V P                  4       G Rj  xL
  V #  L5i)	g      .@)connectz
User-AgentzuMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36zAccept-Languagezpt-PT,pt;q=0.9T)r"   headersfollow_redirectsN)httpxAsyncClientTimeoutr,   r+   
_handshaker/   s   &r   
__aenter__MjIrnClient.__aenter__K   s`     ((MM$-->I "#3 "

 oo 	 s   AA!AA!c                $   < V ^8  d   QhRS[ RR/# )r!   excr#   Nr   )r%   r&   s   "r   r'   r(   Z   s        C  D  r   c                   "   V P                   e,   V P                   P                  4       G R j  xL
  R V n         R # R #  L5ir*   )r+   aclose)r/   r?   s   &*r   	__aexit__MjIrnClient.__aexit__Z   s4     <<#,,%%'''DL $'s   ,A >A c                4   < V ^8  d   QhRS[ P                  /# )r!   r#   )r7   r8   )r%   r&   s   "r   r'   r(   `   s      )) r   c                L    V P                   f   \        R4      hV P                   # )Nz$MjIrnClient usado fora do async with)r+   RuntimeErrorr;   s   &r   clientMjIrnClient.client_   s"    <<EFF||r   c                   < V ^8  d   QhRR/# )r!   r#   Nr   )r%   r&   s   "r   r'   r(   e   s     R R$ Rr   c                  "   V P                   P                  \        R ,           4      G Rj  xL
 pVP                  4        VP	                  4       R,          R,          V n        \        R,          w  r#V P                  W#\        / RR7      G Rj  xL
  V P                   P                  P                  R4      ;'       g    Rp\        P                  P                  V4      p\        R VP                  R	4       4       4      pVP                  R
4      V n        V P                  '       g   \!        R4      h\"        P%                  RV P
                  4       R#  EL, L5i)zmoduleservices/moduleinfoNmanifestversionTokenr   csrfnr2Usersr   c              3   X   "   T F   pR V9   g   K  VP                  R ^4      x  K"  	  R# 5i)=N)split).0ps   & r   	<genexpr>)MjIrnClient._handshake.<locals>.<genexpr>q   s(      
%73!8OAGGCOO%7s   	**;crfu=   IRN: não foi possível obter o token CSRF do cookie nr2Usersz#IRN handshake ok (moduleVersion=%s))rG   getBASEraise_for_statusjsonr-   API	_raw_postVIEW_SEARCHcookiesurllibparseunquotedictrR   r.   rF   loggerdebug)r/   infopathapirawdecodedpartss   &      r   r:   MjIrnClient._handshakee   s    [[__T,G%GHH#yy{:6~F )*	nnTRdnCCCkk!!%%j177R,,&&s+ 
%,]]3%7
 
 YYu%
zzz^__:D<P<PQ# I 	Ds)   *EEA"EE/E BEEc                v   < V ^8  d   QhRS[ RS[ RS[ RS[S[ S[3,          RS[ R,          RS[P                  /# )r!   rh   api_versionviewinputsrN   Nr#   )strrd   r   r7   Response)r%   r&   s   "r   r'   r(   y   sW     \ \\&)\14\>B38n\TWZ^T^\	\r   c           
       "   R RRRR\         R,           RRRR	/pV'       d   WVR
&   RRV P                  RV/RVRR/ /RV/pV P                  P                  \        V,           \
        P                  ! V4      VR7      G Rj  xL
 #  L5i)zContent-Typezapplication/json; charset=UTF-8Acceptzapplication/jsonRefererpesquisapublicaOriginz"https://meu.registo.justica.gov.ptzX-Requested-WithXMLHttpRequestzX-CSRFTokenversionInfomoduleVersion
apiVersionviewName
screenData	variablesinputParameters)r	   r5   N)rZ   r-   rG   postSSr\   dumps)r/   rh   ro   rp   rq   rN   r5   bodys   &&&&&&  r   r^   MjIrnClient._raw_posty   s      =(t//: 0
 %)M"OT-A-A<Q\];+v	
 [[%%b4iD9ISZ%[[[[s   A>B BBc          
      ^   < V ^8  d   QhRS[ RS[S[ S[3,          RS[ RS[S[ S[3,          /# )r!   keyrq   rp   r#   )rr   rd   r   )r%   r&   s   "r   r'   r(      s>     $ $c $4S> $ $W[\_ad\dWe $r   c                  "   \         V,          w  rEV P                  WEW2V P                  R 7      G Rj  xL
 pVP                  4        VP	                  4       pVP                  R/ 4      P                  R4      '       d   \        RV R24      hRV9   d(   \        RV RVR,          P                  R	4       24      hVP                  R
/ 4      #  L5i)rM   Nrz   hasApiVersionChangedzo IRN mudou o apiVersion de zI; reextrair de /EOL/scripts/EOL.PublicSearchFlow.PublicSearchBlock.mvc.js	exceptionzIRN z: messagedata)r]   r^   r.   r[   r\   rY   r   rF   )r/   r   rq   rp   rh   ro   rr   s   &&&&    r   callMjIrnClient.call   s     H..Dtzz.RR	vvx88M2&**+ABB#.se 4M M  $cU"T+->-B-B9-M,NOPPxx## Ss   1CCA
C?ACc          	      R   < V ^8  d   QhRS[ RS[RS[S[S[ S[3,          ,          /# )r!   nif	max_pagesr#   )rr   intlistrd   r   )r%   r&   s   "r   r'   r(      s4      c c 4PTUXZ]U]P^K_ r   c                  "   . p^ p\        V4       F  pV P                  RRRVR\        R\        RRR\        R	\        R
\        /RV/4      G Rj  xL
 pVP                  R4      ;'       g    / P                  R4      ;'       g    . pVP                  V4       \        VP                  R4      ;'       g    ^ 4      pV\        ,          pWH8  g	   V'       g    V# \        4       G Rj  xL
  K  	  V#  L L5i)u:   Todas as publicações de um NIPC, do histórico completo.r   FiltersNIF_NIPC	StartDateEndDateEntityr   DistrictCouncilPublicationTypeIndexNResultsList
TotalCount)
ranger   NO_DATE
EMPTY_CODETYPE_ALLrY   extendr   	PAGE_SIZEr   )	r/   r   r   outindex_r   rowstotals	   &&&      r   publications_for_nif MjIrnClient.publications_for_nif   s     $&y!A"C#W!7 ""J!:)8  U D HHY'--2226:@@bDJJt.33!4EYE~T
 .  - ". 
-* !s<   AC8C4C8-C80C87!C8C8)C6*C86C8c                J   < V ^8  d   QhRS[ S[S[3,          RS[R,          /# )r!   pubr#   N)rd   rr   r   )r%   r&   s   "r   r'   r(      s'     ! !T#s(^ !d
 !r   c                ,  "    V P                  RRVR,          RVP                  R4      RVP                  R4      RVP                  R4      R\        VP                  R4      4      /4      G R	j  xL
 pVP                  R
4      pV'       g   R	# V P                  RR
V/\        R7      G R	j  xL
  V P                  RRVR,          RR/\        R7      G R	j  xL
 pVP                  R4      ;'       g    / P                  R4      ;'       g    / P                  R4      ;'       g    / pVP                  R4      ;'       g    R	#  L L Lp  \         d    h \
         d2   p\        P                  RTP                  R4      T4        R	p?R	# R	p?ii ; i5i)u   HTML do conteúdo da publicação.

São três passos porque o portal passa o contexto por estado de sessão:
o wrapper devolve um `Resource` opaco, o ecrã de conteúdo consome-o, e
só depois o conteúdo fica acessível.
r   PublicationIdr   Dater   r   HasDocument	DocumentsNResourcer   )rp   r	   IsDocFr   DataDetalhesContentu   IRN: conteúdo de %s falhou: %s)r   rY   boolVIEW_CONTENTr   	Exceptionre   rg   )r/   r   wrapperresourcer   detalheses   &&     r   content_forMjIrnClient.content_for   si    	 II!#SYCGGFO
 3cggh/!4(<#=	 	G {{:.H))NZ,B)VVV #d)We<! #  D
 ),2277?EE2JJ:V\\Z\H<<	*22d2)	 W ! 	 	KK93774=!L	s   FA+E
 /E0E
 FE
 ,E-*E
 EE
 4E
 E
 &E
 E
 FE
 E
 E
 
FF &FFFF)r+   r.   r-   r,   N)g     F@)   )r   r   r   r   r   r0   r<   rB   propertyrG   r:   r^   r_   r   r   r   r   __classdictcell__)r&   s   @r   r   r   B   sw     G& &    
  
R R(\ \( HS $ $ :! !r   r   )z*PublicSearchBlock/ActionGetPublications_BLzNb1USv39LhQg2W+YLlRcZQ)z/PublicSearchBlock/DataActionFetchSitePropertiesYTd4bJSKvOnEVq2gKCSdrg)z-PublicSearchBlock/ActionContentOnClickWrapperwDBJUImUiVj7A6B9lyWJTg)z(PublicationContent/DataActionFetchInputsz1rg35SzC9Ko_++rqVMb2TQ)z7PublicationContentBlock/ActionGetContentByPublicationIddpgHT4hwrImeMWiGxr8jNQ)r   r\   loggingurllib.parsera   typingr   r7   app.scrapers.baser   	getLoggerr   re   rZ   r   r_   r   r]   r   r   r   r   rF   r   r   r   r   r   <module>r      s   8      *			8	$4	22-4 \dbZd  b'2'=rB
!Wb'1hL	O OX Xr   