a
    }'9i&h                     @   s  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m	Z	m
Z
mZmZ d dlZd dlmZmZmZ d dlmZ d dlmZmZmZ d dlmZ d dlmZmZ d d	lmZ ed
Z e!dddZ"e!ee!e!f dddZ#de!ee!e	f dddZ$e$ Z%e%d Z&e'e%d Z(e!e%d )dZ*e!e%d Z+e!e%d Z,e'e%d Z-ej./e+dZ0ej./e+dZ1ej./e+dZ2e 3 Z4dd  Z5e!e	e	d!d"d#Z6e!e	d$d%d&Z7e!e	e	d!d'd(Z8e!e	d$d)d*Z9ee!e	f dd+d,Z:ee!e	f d-d.d/Z;ee!e	f dd0d1Z<ee!e	f d-d2d3Z=ee!e	f dd4d5Z>ee!e	f d-d6d7Z?e
ee!e	f  e!eee!e	f  d8d9d:Z@e
ee!e	f  e'eee!e	f  d;d<d=ZAe
ee!e	f  e'e!eee!e	f  d>d?d@ZBe
ee!e	f  e'eee!e	f  dAdBdCZCdDZDee!e	f e!eeEe!f dEdFdGZFee!e	f e!dHdIdJZGee&dKZHe ZIe'eEdLdMdNZJe'dOdPdQZKeILedRedSdTdUZMeILedVedSdWdXZNeILedYedSdZd[ZOeILed\edSd]d^ZPeILed_edSd`daZQeILedbedSdcddZReILedeedSdfdgZSeILedhedSdidjZTeILedkedSdldmZUeILednedSdodpZVeIWejXYdqedrdsdtZZeIWejXYduedrdvdwZ[G dxdy dyeZ\edzd{Z]e!e!ee!e	f d|d}d~Z^e]_ddd Z`e]ade!e!dddZbe]ade!e!dddZce]dde!e\dddZee]dde\edddZfegdkrd dlhZhe5  ehjide,e-dd dS )    N)datetime)DictAnyListOptionalTuple)FastAPIHTTPExceptionRequest)	BaseModel)Bot
DispatcherF)Command)MessageCallbackQuery)InlineKeyboardBuilderz^[^@\s]+@[^@\s]+\.[^@\s]+$)returnc                   C   s   t   S N)r   utcnow	isoformat r   r   7/var/www/chinnov/data/www/s.chinnov.ru/email/bot/bot.pynow_iso   s    r   )textr   c                 C   sB   i }|   D ]0}d|v r| dd\}}| ||  < q|S )N=   )splitstriplower)r   outpartkvr   r   r   parse_kv   s    r$   config.json)pathr   c                 C   s   t j| std|  t| ddd}t|}W d    n1 sH0    Y  |dsdtd|dsvtd|d	d
 |dd |dd |dd |S )Nzconfig.json not found at rutf-8encoding	BOT_TOKENz Missing BOT_TOKEN in config.jsonADMIN_IDzMissing ADMIN_ID in config.jsonBASE_URLzhttp://localhost:8000DATA_DIRdataHOSTz0.0.0.0PORTi@  )	osr&   existsRuntimeErroropenjsonloadget
setdefault)r&   fcfgr   r   r   load_config,   s    (

r<   r+   r,   r-   /r.   r0   r1   zprofiles.jsonzemails.jsonz
minus.jsonc                  C   s  t jtdd t jts`ttddd(} tjdg d| dd	d
 W d    n1 sV0    Y  t jt	stt	ddd(} tjdg d| dd	d
 W d    n1 s0    Y  t jt
stt
ddd&} tjdg i| dd	d
 W d    n1 s0    Y  d S )NTexist_okwr(   r)   r   last_iditemsF   ensure_asciiindentrC   )r2   makedirsr.   r&   r3   PROFILES_FILEr5   r6   dumpEMAILS_FILE
MINUS_FILE)r:   r   r   r   ensure_data_filesS   s    66rM   )r&   defaultr   c                 C   sL   t j| s|S t| ddd}t|W  d    S 1 s>0    Y  d S )Nr'   r(   r)   )r2   r&   r3   r5   r6   r7   )r&   rN   r:   r   r   r   _read_json_syncc   s    rO   r&   r/   c                 C   s   t j| }t j|dd tjd|d\}}t | z~t|ddd"}tj	||dd	d
 W d    n1 sn0    Y  t 
||  W t j|rzt | W q ty   Y q0 n0t j|rzt | W n ty   Y n0 0 d S )NTr>   z.tmp_)prefixdirr@   r(   r)   FrD   rE   )r2   r&   dirnamerH   tempfilemkstempcloser5   r6   rJ   replacer3   removeOSError)r&   r/   dfdtmp_pathr:   r   r   r   _atomic_write_syncj   s$    
0r]   c                    s   t t| |I d H S r   )asyncio	to_threadrO   )r&   rN   r   r   r   	read_json|   s    r`   c                    s   t t| |I d H  d S r   )r^   r_   r]   rP   r   r   r   
write_json   s    ra   c                      s   t tdg dI d H S Nr   rA   )r`   rI   r   r   r   r   get_profiles_data   s    rc   r/   c                    s   t t| I d H  d S r   )ra   rI   rd   r   r   r   save_profiles_data   s    re   c                      s   t tdg dI d H S rb   )r`   rK   r   r   r   r   get_emails_data   s    rf   c                    s   t t| I d H  d S r   )ra   rK   rd   r   r   r   save_emails_data   s    rg   c                      s   t tdg iI d H S )NrC   )r`   rL   r   r   r   r   get_minus_data   s    rh   c                    s   t t| I d H  d S r   )ra   rL   rd   r   r   r   save_minus_data   s    ri   )rC   inbound_keyr   c                 C   s$   | D ]}| d|kr|  S qd S )Nrj   r8   )rC   rj   pr   r   r   find_profile_by_key   s    
rm   )rC   pidr   c                 C   s*   | D ] }t |dd|kr|  S qd S Nidr   intr8   )rC   rn   rl   r   r   r   find_profile_by_id   s    
rs   )rC   
profile_idemailr   c                 C   sF   |  }| D ]4}t|dd|kr|dd  |kr|  S qd S )Nrt   r   ru    )r   rr   r8   )rC   rt   ru   eler   r   r   find_email_record   s
    (
ry   )rC   eidr   c                 C   s*   | D ] }t |dd|kr|  S qd S ro   rq   )rC   rz   rx   r   r   r   find_email_by_id   s    
r{   z*https://api.unisender.com/ru/api/subscribe)profileru   r   c              
      sl  d| d | d t | ddt | dd|d}| d	p>d
 }|rP||d< ztjdd4 I d H }|jt|dI d H }|j}|jdkrdd|j d|d d  fW  d   I d H  W S d| v rd|d d fW  d   I d H  W S d|d d fW  d   I d H  W S 1 I d H s&0    Y  W n4 t	yf } zdd|fW  Y d }~S d }~0 0 d S )Nr6   apikeylist_iddouble_optin   	overwriter   )formatapi_keylist_idsr   r   zfields[email]tagrv   tags   )timeout)params   FzHTTP z: i  z"error"i  TzException: )
strr8   r   httpxAsyncClientUNISENDER_SUBSCRIBE_URLr   status_coder   	Exception)r|   ru   r   r   clientr'   r   rx   r   r   r   call_unisender   s*    
4&Br   )r|   r   c                 C   s^   |  dpd }|r d| nd}d| d  d| d  d| d	|  d
d d|  dd 
S )Nr   rv   z&tags=z?https://api.unisender.com/ru/api/subscribe?format=json&api_key=r}   z
&list_ids=r~   z&fields[email]={EMAIL}z&double_optin=r   r   z&overwrite=r   r   )r8   r   )r|   r   	tags_partr   r   r   outgoing_template   s    

r   )token)user_idr   c                 C   s   | t kS r   )r,   )r   r   r   r   
admin_only   s    r   )email_idc                 C   s@   t  }|jdd|  d |jdd|  d |d | S )Nu   ✅ Подтвердитьapprove:)r   callback_datau   ❌ Отклонитьreject:rD   )r   buttonadjust	as_markup)r   kbr   r   r   approval_kb   s
    
r   startmessagec                    s2   t | jjsd S | dt dt dI d H  d S )Nuc  Бот подтверждения email → Unisender.

Профили:
/wh_new name=site1 apikey=XXX list_id=49 tag=TAG double=3 overwrite=1
/wh_list
/wh_set <id> key=value ...
/wh_del <id>
/wh_stats <id>
/wh_show <id>

Минус-фильтры:
/minus <фраза>
/minus_list
/minus_del <фраза1> <фраза2> ...

Входящие ссылки:
z,/inbound/INBOUND_KEY?email=test@example.com
z//inbound?key=INBOUND_KEY&email=test@example.com)r   	from_userrp   answerr-   r   r   r   r   	cmd_start   s    r   wh_newc                    s  t | jjsd S | jddd }t|}|d}|d}|d}|dd}t|d|d	d
}t|dd}|r|r|s| 	dI d H  d S |dvr| 	dI d H  d S |dvr| 	dI d H  d S |dpt
d t4 I d H  t I d H }	|	d }
t fdd|
D rJ| 	dI d H  W d   I d H  d S |	d  d7  < |	d }|
|||t|||| t d	 t|	I d H  W d   I d H  q1 I d H s0    Y  t d  d}| 	d| d| d  d| I d H  d S ) Nz/wh_newrv   r   namer}   r~   r   doubler   3r   1u   Нужно минимум: name, apikey, list_id.
Пример:
/wh_new name=site1 apikey=XXX list_id=49 tag=PAY double=3 overwrite=1r   r      '   double_optin допустим: 0, 3, 4.r   r   rD   $   overwrite допустим: 0, 1, 2.rj   
   rC   c                 3   s   | ]}| d  kV  qdS )rj   Nrk   .0rl   rj   r   r   	<genexpr>=      zcmd_wh_new.<locals>.<genexpr>uL   Такой inbound_key уже существует. Укажи другой.rB   )	rp   r   r}   r~   r   r   r   rj   
created_at	/inbound/z?email=example@mail.comu!   Профиль создан.
ID: z
Name: z
Inbound key: uF   

Готовая ссылка для входящего вызова:
)r   r   rp   r   rW   r   r$   r8   rr   r   secretstoken_urlsafe	DATA_LOCKrc   anyappendr   r   re   r-   )r   argskvr   r}   r~   r   r   r   profiles_datarC   rn   inbound_urlr   r   r   
cmd_wh_new  sl    



<r   wh_listc                    s  t | jjsd S t4 I d H 6 t I d H }t|d dd d}W d   I d H  qh1 I d H s^0    Y  |s| dI d H  d S g }|D ]t}t d|d  d}|d	|d
  d|d  d|d  d|	dpd d|	dd d|	dd d| d q| d
|I d H  d S )NrC   c                 S   s   t | ddS ro   rq   xr   r   r   <lambda>d  r   zcmd_wh_list.<locals>.<lambda>keyu9   Профилей нет. Создай через /wh_new.r   rj   z?email=test@example.com#rp    r   z	
list_id=r~   z tag=r   -z double=r   r    overwrite=r   r   z

inbound: 
)r   r   rp   r   rc   sortedr   r-   r   r8   join)r   r   rC   linesrl   r   r   r   r   cmd_wh_list]  s2    >

r   wh_setc              	      s  t | jjsd S | jjdd}t|dk s6|d  sJ| dI d H  d S t|d t	|d }h d  fdd|
 D }|s| d	I d H  d S t4 I d H  t I d H }|d
 }t|}|s| dI d H  W d   I d H  d S d|v rJ|d tfdd|D rB| dI d H  W d   I d H  d S |d< d|v r`|d |d< d|v rv|d |d< d|v rt|d |d< d|v r|d |d< d|v sd|v rt|d|d}|dvr | dI d H  W d   I d H  d S ||d< d|v rVt|d }|dvrN| dI d H  W d   I d H  d S ||d< t|I d H  W d   I d H  q1 I d H s0    Y  | d dI d H  d S )NrD   )maxsplitr   r   u+   Пример: /wh_set 1 tag=NEW overwrite=2>   r~   r   rj   r   r   r   r}   r   c                    s   i | ]\}}| v r||qS r   r   )r   r"   r#   )allowedr   r   
<dictcomp>  r   zcmd_wh_set.<locals>.<dictcomp>u1   Нет допустимых параметров.rC   !   Профиль не найден.rj   c                 3   s0   | ](}| d  ko&t| ddkV  qdS )rj   rp   r   N)r8   rr   )r   r   )new_keyrn   r   r   r     r   zcmd_wh_set.<locals>.<genexpr>u)   Такой inbound_key уже занят.r   r}   r~   r   r   r   r   r   r   r   r      Профиль #u    обновлён.)r   r   rp   r   r   lenisdigitr   rr   r$   rC   r   rc   rs   r   r   r8   re   )r   partsr   r   rC   rl   rZ   or   )r   r   rn   r   
cmd_wh_setw  s`    








<r   wh_delc              	      sN  t | jjsd S | j }t|dk s2|d  sF| dI d H  d S t|d  t	4 I d H  t
 I d H }t|d } fdd|d D |d< t|d }t|I d H  t I d H } fdd|d D |d< t|I d H  W d   I d H  q1 I d H s0    Y  ||kr2| d  d	I d H  n| d  d
I d H  d S )NrD   r   u   Пример: /wh_del 1rC   c                    s$   g | ]}t |d d kr|qS )rp   r   rq   r   rn   r   r   
<listcomp>  r   zcmd_wh_del.<locals>.<listcomp>c                    s$   g | ]}t |d d kr|qS rt   r   rq   r   rx   r   r   r   r     r   r   u    не найден.u-    удалён вместе с данными.)r   r   rp   r   r   r   r   r   rr   r   rc   re   rf   rg   )r   r   r   beforeafteremails_datar   r   r   
cmd_wh_del  s&    
<
r   wh_showc              	      s   t | jjsd S | j }t|dk s2|d  sF| dI d H  d S t|d }t	4 I d H 0 t
 I d H }t|d |}W d   I d H  q1 I d H s0    Y  |s| dI d H  d S | d|d  d|d	  d
t| dI d H  d S )NrD   r   u   Пример: /wh_show 1rC   r   r   rp   r   r   u2   

Шаблон исходящего вызова:
uW   

Где {EMAIL} бот подставляет подтверждённый адрес.)r   r   rp   r   r   r   r   r   rr   r   rc   rs   r   )r   r   rn   r   rl   r   r   r   cmd_wh_show  s$    
8r   wh_statsc           	         s  t | jjsd S | j }t|dk s2|d  sF| dI d H  d S t|d  t	4 I d H | t
 I d H }t|d  }|s| dI d H  W d   I d H  d S t I d H } fdd|d D }W d   I d H  q1 I d H s0    Y  ddddd	}|D ]&}|d
d}||dd ||< q| d  d|d  d|dd d|dd d|dd d|dd I d H  d S )NrD   r   u   Пример: /wh_stats 1rC   r   c                    s$   g | ]}t |d d kr|qS r   rq   r   r   r   r   r      r   z cmd_wh_stats.<locals>.<listcomp>r   )pendingapprovedrejectedskippedstatusr   u%   Статистика профиля #z (r   z):
pending: z
approved: r   z
rejected: r   z

skipped: r   )r   r   rp   r   r   r   r   r   rr   r   rc   rs   rf   r8   )	r   r   r   rl   r   rC   countsrx   sr   r   r   cmd_wh_stats  s<    
@



r   minusc              	      s   t | jjsd S | jddd }|s<| dI d H  d S t4 I d H Z t I d H }|d }||vr|	| |j
dd d t|I d H  W d   I d H  q1 I d H s0    Y  | d	| I d H  d S )
Nz/minusrv   r   u   Пример: /minus spamrC   c                 S   s   |   S r   )r   r   r   r   r   r   #  r   zcmd_minus_add.<locals>.<lambda>r   u"   Добавлено в минус: )r   r   rp   r   rW   r   r   r   rh   r   sortri   )r   phrase
minus_datarC   r   r   r   cmd_minus_add  s    
8r   
minus_listc              	      s   t | jjsd S t4 I d H * t I d H }|d }W d   I d H  q\1 I d H sR0    Y  |st| dI d H  d S | dddd |D  I d H  d S )NrC   u!   Минус-условий нет.u   Минус-условия:
r   c                 s   s   | ]}d | V  qdS )z- Nr   r   r   r   r   r   6  r   z!cmd_minus_list.<locals>.<genexpr>)r   r   rp   r   rh   r   r   )r   r   rC   r   r   r   cmd_minus_list)  s    2r   	minus_delc              	      s   t | jjsd S | j }t|dk r:| dI d H  d S |dd   t4 I d H J t I d H } fdd|d D |d< t	|I d H  W d   I d H  q1 I d H s0    Y  | dd
  I d H  d S )	NrD   u"   Пример: /minus_del spam tempr   c                    s   g | ]}| vr|qS r   r   r   targetsr   r   r   G  r   z!cmd_minus_del.<locals>.<listcomp>rC   u$   Удалено (если было): z, )r   r   rp   r   r   r   r   r   rh   ri   r   )r   r   r   r   r   r   cmd_minus_del9  s    
8r   r   )queryc           	         s  t | jjs$| jdddI d H  d S t| jddd }t4 I d H $ t I d H }t	 I d H }t
|d |}|s| jdddI d H  W d   I d H  d S |dd	kr| jd
ddI d H  W d   I d H  d S t|d t|d }|sVd|d< d|d< t |d< t|I d H  | jdI d H  | jdddI d H  W d   I d H  d S W d   I d H  q1 I d H sz0    Y  t||d I d H \}}t4 I d H  t	 I d H }t
|d |}|s| jdddI d H  W d   I d H  d S |rd|d< d|d< nd|d< d|d< ||d< t |d< t|I d H  W d   I d H  qj1 I d H s`0    Y  |r| jd|d  d|d  d|d  I d H  | dI d H  nN| jd|d  d|d  d|d  d|d d   I d H  | d!I d H  d S )"N   Нет доступаT
show_alert:r   rC       Запись не найденаr   r      Уже обработаноrt   r   profile_missingreason
decided_atu8   ❌ Профиль удалён. Email отклонён.u    Профиль не найденru   u   Запись исчезлаr   rv   unisender_errorunisender_responseuS   ✅ Подтверждено и отправлено в Unisender.
Профиль: r    (#rp   	)
Email: u   ГотовоuG   ⚠️ Ошибка Unisender, email отклонён.
Профиль: u   

Ответ:   u   Ошибка отправки)r   r   rp   r   rr   r/   r   r   rc   rf   r{   r8   rs   r   rg   r   	edit_textr   )	r   rz   r   r   rx   rl   ok	resp_texte2r   r   r   
cb_approveQ  sz    
D

<


r  r   c              	      s  t | jjs$| jdddI d H  d S t| jddd }t4 I d H  t I d H }t	 I d H }t
|d |}|s| jdddI d H  W d   I d H  d S |dd	kr| jd
ddI d H  W d   I d H  d S t|d t|d }d|d< d|d< t |d< t|I d H  W d   I d H  q@1 I d H s60    Y  |rN|d nd}| jd| d|d  I d H  | dI d H  d S )Nr   Tr   r  r   rC   r  r   r   r  rt   r   admin_rejectr  r  r   unknownu(   ❌ Отклонено.
Профиль: z
Email: ru   u   Отклонено)r   r   rp   r   rr   r/   r   r   rc   rf   r{   r8   rs   r   rg   r   r  )r   rz   r   r   rx   rl   pnamer   r   r   	cb_reject  s0    
<
r  c                   @   s.   e Zd ZU dZee ed< dZee ed< dS )InboundPayloadNru   r   )__name__
__module____qualname__ru   r   r   __annotations__r   r   r   r   r   r    s   
r  z%Email approval bot (GET link inbound))title)rj   ru   r   c                    s  |pd  }| stddd|s,tdddt|sBtdddt4 I d H  t I d H }t I d H }t I d H }t|d | }|stdd	dt	|d t
|d
 |}|r|dd|ddW  d   I d H  S | }d }|d D ]}	|	 |v r|	} qq|d  d7  < |d }
|r|d |
t
|d
 |dd| dt t d t|I d H  dd| |ddW  d   I d H  S |d |
t
|d
 |dddt dd t|I d H  W d   I d H  q1 I d H s0    Y  zTtjtd|d  d|d
  d| d|dd d|dd 
t|
dI d H  W n> ty } z$dd||dd W  Y d }~S d }~0 0 d|dd!S )"Nrv   r  zkey is required)r   detailzemail is requiredzinvalid emailrC   i  zprofile not foundrp   r   	duplicater   )r   r  r|   rB   r   r   zminus:)rp   rt   ru   r   r  r  r   r  )r   r  r|   r   uD   Новый email для подтверждения:
Профиль: r	  r
  z
double_optin=r   r   r   r   )chat_idr   reply_markupzadmin_notify_failed: )r   warningr|   )r   r|   )r   r	   EMAIL_REmatchr   rc   rf   rh   rm   ry   rr   r8   r   r   r   rg   botsend_messager,   r   r   )rj   ru   r   r   r   r|   existinglowmatchedr   rz   rx   r   r   r   process_inbound  s    


,

<

.r'  startupc                      s   t   ttt d S r   )rM   r^   create_taskdpstart_pollingr"  r   r   r   r   r(    s    z/inbound/{inbound_key}rj   ru   c                    s   t | |I dH S )u?   
    Пример:
    /inbound/KEY?email=test@example.com
    Nr'  r,  r   r   r   inbound_by_path"  s    r.  z/inboundr   ru   c                    s   t | |I dH S )uC   
    Пример:
    /inbound?key=KEY&email=test@example.com
    Nr-  r/  r   r   r   inbound_by_query+  s    r0  )rj   payloadc                    s   |j }t| |I d H S r   )ru   r'  )rj   r1  ru   r   r   r   inbound_post_path6  s    r2  )r1  requestc                    s(   | j p|jd}| j}t||I d H S )Nr   )r   query_paramsr8   ru   r'  )r1  r3  r   ru   r   r   r   inbound_post_query<  s    r5  __main__zbot:appF)hostportreload)r%   )jr^   r6   r2   rer   rT   r   typingr   r   r   r   r   r   fastapir   r	   r
   pydanticr   aiogramr   r   r   aiogram.filtersr   aiogram.typesr   r   aiogram.utils.keyboardr   compiler   r   r   r$   r<   CONFIGr+   rr   r,   rstripr-   r.   r0   r1   r&   r   rI   rK   rL   Lockr   rM   rO   r]   r`   ra   rc   re   rf   rg   rh   ri   rm   rs   ry   r{   r   boolr   r   r"  r*  r   r   r   r   r   r   r   r   r   r   r   r   r   callback_queryr/   
startswithr  r  r  appr'  on_eventr(  r8   r.  r0  postr2  r5  r  uvicornrunr   r   r   r   <module>   s   
**,*"
D>&D&
U


