Тема: AHK: Мониторинг стены ВКонтакта
Скрипт предназначен для отслеживания постов и комментариев в ВК-сообществе/профиле. После запуска логинится один раз (нужно для работы execute-процедуры, которая в зависимости от настроек, получает за один запрос изменения о до 2500 последних постов со стены), скачивает указанное количество постов и в последующем докачивает в них комментарии в случае изменений. Комментарии могут докачиваться не именно с добавленного и ниже, а на указанное количество комментариев выше него — на случай, если несколько комментов со стены были удалены и их позиция сместилась.
Таким образом, один вызов для получения общей картины об изменениях на стене, и при их наличии — точное обращение к добавленным комментариям.
В прикреплении скрипт можно скачать вместе с CSS-стилями, нужными для страниц. Для наглядности работают некоторые Tooltip.
;*******************************************************************************
; WallMonitor.ahk
;*******************************************************************************
#Persistent
#MaxMem 200
SetBatchLines -1
#SingleInstance Force
HTTP := ComObjCreate("WinHTTP.WinHTTPRequest.5.1")
; ========================= НАСТРОЙКИ ПОЛЬЗОВАТЕЛЯ =========================
email := "ВАШ_ИМЭЙЛ_НА_ВК"
password := "ВАШ_ПАРОЛЬ_НА_ВК"
OwnerId := -29534144 ; айди сообщества, которое мониторим
Pause := 20 ; пауза В СЕКУНДАХ между проверками (за слишком частые обращения блокируют)
PstIters := 1 ; До 25-ти. Количество итераций при получении инфы о постах и комментах,
; увеличивать на один только если отслеживаемых постов больше 100
PstCount := 2 ; Сколько последних постов со стены отслеживать
; До 2500, в зависмости от итерации, которую
; с каждой сотней постов надо увеличивать на единицу.
CmtMinus := 3 ; На сколько комментариев отступать назад при их докачке
; (на случай удаления на сервере)
Dest_Path = %A_ScriptDir% ; путь к целевой папке (куда сохранять результат)
; ========================= КОНЕЦ НАСТРОЕК ПОЛЬЗОВАТЕЛЯ =========================
SetTimer, gogo, % Pause*1000
Return
gogo:
If not (LoginMode == 1) ;при первом запуске логиниться однократно:
{
gosub login
LoginMode = 1
}
;--------------------------------------------
;ПОСТ:
;--------------------------------------------
HTTP.Open("POST", "https://vk.com/dev?act=a_run_method&al=1&hash=" PostHash "&method=execute¶m_code=var%20owner_id%3D%22" OwnerId "%22%3Bvar%20offset%3D0%3Bvar%20ITERS%3D" PstIters "%3Bvar%20COUNT%3D" PstCount "%3Bvar%20posts%3D%5B%5D%3Bvar%20req_params%3D%7B%22owner_id%22%3Aowner_id%2C%22count%22%3ACOUNT%2C%22offset%22%3Aoffset%2C%22v%22%3A%225.78%22%7D%3Bvar%20i%3D0%3Bwhile(i%20%3C%20ITERS)%7Breq_params.offset%3Di*COUNT%2BITERS*COUNT*offset%3Bvar%20items%3DAPI.wall.get(req_params).items%3Bif%20(items.length%20%3D%3D%200)%20%7Breturn%20posts%3B%7Dvar%20ids%3Ditems%40.id%3Bvar%20tmp%3D%7B%7D%3Btmp.ids%3Dids%3Btmp.comments%3Ditems%40.comments%40.count%3Bposts.push(tmp)%3Bi%3Di%2B1%3B%7Dreturn%20posts%3B¶m_v=5.78", true)
HTTP.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko)")
HTTP.SetRequestHeader("Pragma", "no-cache")
HTTP.SetRequestHeader("Cache-Control", "no-cache, no-store")
HTTP.SetRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT")
HTTP.Send()
HTTP.WaitForResponse()
Wall_Info_List := HTTP.ResponseText
If NOT InStr(Wall_Info_List, """ids"":[") ;если прежний хэш не действует — перелогиниться:
{
sleep, 5000
gosub login
fileappend login в %a_now%`n, %Dest_Path%\_login.txt, UTF-8
Return
}
;Обработка вывода execute-процедуры
Wall_Info_List := RegExReplace(Wall_Info_List, "s)(""\w+"":\[)", "`n$1")
Wall_Info_List := RegExReplace(Wall_Info_List, "s)[\][^`n]*`n|\]|\}]", "`n")
Cont := Wall_Info_List, arrA := [], arrC := []
Loop, parse, Cont, `n, `r
{
str := A_LoopField
Loop, parse, str, [`,]
if A_LoopField !=
{
if InStr(str, "ids")
arrA[A_Index] := A_LoopField, LastA := A_Index
if InStr(str, "comments")
arrC[A_Index] := A_LoopField, LastC := A_Index
}
if InStr(str, "comments")
{
num ++
Loop, % LastA
if A_Index > 1
Wall_Info_List_Out .= arrA[A_Index] "-" arrC[A_Index] "`n"
if (LastA != LastC) ;ПРОВЕРКА
Msgbox, 0x1030, , Ошибка в блоке %num%`, строка ~ %A_Index% исходных данных.`n`nКоличество чисел не совпало.
arrA := [], arrC := [], LastA :=LastC := str :=""
}
}
Wall_Info_List_Out1 := Wall_Info_List_Out
If (FirstMode == 1) ;при первом запуске пропустить
{
Loop, parse, Wall_Info_List_Out_Prev, `n, `r
Wall_Info_List_Out := RegExReplace(Wall_Info_List_Out, "m`a)^\n|^\Q" A_LoopField "\E$")
Wall_Info_List_Out_Uniq := Wall_Info_List_Out
tooltip Обновление в постах: `n%Wall_Info_List_Out_Uniq%
sleep 1500
if (Wall_Info_List_Out_Uniq = "") ;если перемен пуста
Return
}
FirstMode = 1
if (Wall_Info_List_Out_Uniq = "") ;если перемен пуста
Wall_Info_List_Out_Uniq := Wall_Info_List_Out1
Wall_Info_List_Out_Uniq := RegExReplace(Wall_Info_List_Out_Uniq, "\R+\s*", "`n") ;удал пуст строки
Wall_Info_List_Out_Uniq := RegExReplace(Wall_Info_List_Out_Uniq, "\R$", "") ;удал последн пуст
Wall_Info_List_Out =
Loop, parse, Wall_Info_List_Out_Uniq, `n, `r
{
RegExMatch(A_LoopField, "s)(\d+)-(\d+)", match)
PostId := match1
CmtCount := match2
If NOT PostId~="\d"
Continue
pfile = %Dest_Path%\%OwnerId%_%PostId%.html
if (NOT FileExist(pfile))
{
HTTP.Open("GET", "https://vk.com/wkview.php?act=show&al=1&w=wall" OwnerId "_" PostId "&offset=999999", true)
HTTP.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko)")
HTTP.SetRequestHeader("Pragma", "no-cache")
HTTP.SetRequestHeader("Cache-Control", "no-cache, no-store")
HTTP.SetRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT")
HTTP.Send()
HTTP.WaitForResponse()
ResponseText := HTTP.ResponseText
Gosub, PstProc
Gosub, NewTitle
Gosub, htmlcode
fileappend, %htmlcode%%ResponseText%`n`n`n`n, %Dest_Path%\%OwnerId%_%PostId%.html, UTF-8
FileCreate = 1
CmtCountInFile := 0
}
Else
{
FileCreate = 0
FileRead, Post_File, %pfile%
StrReplace(Post_File, "<div id=""post-",, CmtCountInFile) ;подсчёт комментов в файле
}
ResponseText =
if StrLen(CmtCount) > 0
{
loop % CmtCount
{
if (FileCreate == 0)
{
cn := A_Index - 1 + CmtCountInFile - CmtMinus
tooltip файл сущ----`nВсего коментов: %CmtCount%/%cn%`nPostId: %PostId%
sleep 900
If InStr(cn, "-")
Continue
if (cn = CmtCount)
break
if cn > CmtCount
break
}
if (FileCreate == 1)
{
cn := A_Index - 1
tooltip файл НЕ сущ----`nВсего коментов: %CmtCount%/%cn%`nPostId: %PostId%
}
;--------------------------------------------
;ОПРЕДЕЛЕННЫЙ КОММ ПОСТА
;--------------------------------------------
HTTP.Open("POST", "https://vk.com/al_wall.php", true)
;HTTP.SetRequestHeader("Cookie", "remixlang=0") ; имена на русском
HTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
HTTP.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko)")
HTTP.SetRequestHeader("Pragma", "no-cache")
HTTP.SetRequestHeader("Cache-Control", "no-cache, no-store")
HTTP.SetRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT")
HTTP.Send("act=get_replies&al=1&count=1&from=wkview&offset=" cn "&post=" OwnerId "_" PostId "&rev=0")
HTTP.WaitForResponse()
ResponseText := HTTP.ResponseText
RegExMatch(ResponseText, "s)class=""post_link"" href=""[^""]*(_\d+\?reply=\d+"")", match)
Cmt_Id := match1
If InStr(Post_File, Cmt_Id)
Continue
ResponseText := RegExReplace(ResponseText, "s)^.*?(<div.*?)<!><!json>.*?$", "$1")
Gosub, CmtProc
fileappend, %ResponseText%, %Dest_Path%\%OwnerId%_%PostId%.html, UTF-8
ResponseText =
match =
match1 =
mattch =
mattch1 =
NewTitle =
tSTRING =
DateTime =
arrA =
str =
Cont =
Cmt_Id =
Wall_Info_List =
Wall_Info_List_Out_Uniq =
;Post_File =
;!!PostId
;!!CmtCount
cn =
}
}
}
Wall_Info_List_Out_Prev := Wall_Info_List_Out1
FileCreate = 0
Return
NewTitle:
;захватить не более 100 символов в секции, с границей получаемого текста вне угловых скобок
RegExMatch(ResponseText, "<div class=""wall_post_text"">(?!</div>)((.(?!</div>)){0,350}.)(?=.*</div>)", match)
NewTitle := RegExReplace(match1, "<[^>]*$")
NewTitle := RegExReplace(NewTitle, "<[^>]*>", " ")
NewTitle := RegExReplace(NewTitle, " ", " ")
NewTitle := UnHTM(NewTitle)
return
PstProc:
ResponseText := RegExReplace(ResponseText, "s)<div id=""wl_replies_header"".+?</div>", "")
ResponseText := RegExReplace(ResponseText, "s).*?<!bool><!>(<div.*?</div>)<!><!json>.*", "$1")
ResponseText := RegExReplace(ResponseText, "s)<a([^>]*)base":"([^>]*)(","[^>]*\[")([^}]*)("[^>]*)>", "<a$1base&huot;:&huot;$2$3$4$5 href=""$2$4.jpg""><img class=""my"" src=""$2$4.jpg""></a>`n`n`n")
ResponseText := RegExReplace(ResponseText, "s)(<a[^>]* )(style=""[^>]*><img class=""my"")", "$1X$2")
ResponseText := RegExReplace(ResponseText, "s)(<div[^>]* )(style=""[^>]*><a[^>]*><img class=""my"")", "$1X$2")
ResponseText := RegExReplace(ResponseText, "s) href=""(/away[^""]*)""([^>]* title=)""(https?:[^""]*)""", " href=""$3""$2""$3""") ;!!
ResponseText := RegExReplace(ResponseText, "s)aria-label=", "title=") ;!!
ResponseText := RegExReplace(ResponseText, "s)<div class=""ui_actions_menu_icons"".*?</a></div>|<div class=""ui_actions_menu _ui_menu"".*?</a></div>|<div id=""wl_reply_form_wrap"" class=""wl_reply_form_wrap""><div class=""wl_post_reply_form_forbidden"">.*?</div></div>|<button class=""flat_button.*?</button>|<span class=""blind_label""[^>]*>Показать список оценивших</span>|<span class=""blind_label"">Нравится</span>|\s+<span class=""post_like_link _link"">Нравится</span>\R", "")
ResponseText := RegExReplace(ResponseText, "s)<br><a class=""wall_\w+_more"" X?onclick=""hide\(this, domPS\(this\)\); show\(domNS\(this\)\);"">Показать полностью…</a><span style=""display: none"">", "")
ResponseText := RegExReplace(ResponseText, "s)<div[^>]*><div[^>]*>Автор ограничил возможность комментирования</div></div>", "")
ResponseText := RegExReplace(ResponseText, "m`a)^\s+<button class=""flat_button.*?>Вы подписаны</.*?</button>$", "")
ResponseText := RegExReplace(ResponseText, "s)<div id=""wl_replies_header_toggler"".*?$", "</div></div>`r`n</div></div>")
ResponseText := RegExReplace(ResponseText, "s)https://sun\d+-\d+\.userapi\.com/(https://sun\d+-\d+\.userapi\.com/)", "$1")
ResponseText := RegExReplace(ResponseText, "s) (onmouse[a-z]+=|onclick=|data-from-id=)""[^""]*""", " ") ;!!
ResponseText := RegExReplace(ResponseText, "s)=""/", "=""https://vk.com/")
ResponseText := RegExReplace(ResponseText, "s)<div id=""wl_post_actions_wrap"".*\Z", "</div><hr>")
return
CmtProc:
ResponseText := RegExReplace(ResponseText, "s) href=""(/away[^""]*)""([^>]* title=)""(https?:[^""]*)""", " href=""$3""$2""$3""") ;!!
ResponseText := RegExReplace(ResponseText, "s)<a([^>]*)base":"([^>]*)(","[^>]*\[")([^}]*)("[^>]*)>", "<a$1base&huot;:&huot;$2$3$4$5 href=""$2$4.jpg""><img class=""my"" src=""$2$4.jpg""></a>`n`n`n")
ResponseText := RegExReplace(ResponseText, "s)(<a[^>]* )(style=""[^>]*><img class=""my"")", "$1X$2")
ResponseText := RegExReplace(ResponseText, "s)(<div[^>]* )(style=""[^>]*><a[^>]*><img class=""my"")", "$1X$2")
ResponseText := RegExReplace(ResponseText, "s)aria-label=", "title=") ;!!
ResponseText := RegExReplace(ResponseText, "s)data-from-id=""(\d+)"">", "data-from-id=""$1"">id$1|")
;ResponseText := RegExReplace(ResponseText, "s) rid=""(\d+)(""[^>]*>)", " rid=""$1$2to$1|")
ResponseText := RegExReplace(ResponseText, "s)</a> (ответил.?) <a href=""([^""]*)""[^>]*'(-?\d+_\d+)', event\)"" rid=""(\d+)""[^>]*>", "</a> <a class=""reply_to"" href=""#post$3"">$1</a> <a class=""reply_to"" href=""$2"">to$4|")
ResponseText := RegExReplace(ResponseText, "s) <div class=""reply_link_wrap""> <a[^>]*>Ответить</a>\R</div>", "")
ResponseText := RegExReplace(ResponseText, "s)<br><a class=""wall_\w+_more"" X?onclick=""hide\(this, domPS\(this\)\); show\(domNS\(this\)\);"">Показать полностью…</a><span style=""display: none"">", "")
ResponseText := RegExReplace(ResponseText, "s)<span class=""blind_label""[^>]*>Показать список оценивших</span>\s?|\s?<span class=""blind_label""[^>]*>Нравится</span>", "")
ResponseText := RegExReplace(ResponseText, "s)https://sun\d+-\d+\.userapi\.com/(https://sun\d+-\d+\.userapi\.com/)", "$1")
;ПРАВКА "минут назад" и "только что"
If RegexMatch(ResponseText, "s)\s?time=""(\d+)"">(только.+?|.+?назад)</span>", mattch)
{
DateTime := FormatSeconds(mattch1)
ResponseText := RegExReplace(ResponseText, "s)\s?time=""(\d+)"">(.+?)</span>", " time=""" mattch1 """>" DateTime "</span>")
}
;ПРАВКА "сегодня в"
If RegexMatch(ResponseText, "s)<span class=""rel_date"">сегодня в\s", mattch)
{
FormatTime, DateTime,, LongDate
StringReplace, DateTime, DateTime, %A_Space%г., `,, All
ResponseText := RegExReplace(ResponseText, "s)<span class=""rel_date"">сегодня в", "<span class=""rel_date"">" DateTime)
}
;ПРАВКА "вчера в"
If RegexMatch(ResponseText, "s)<span class=""rel_date"">вчера в\s", mattch)
{
FormatTime, DateTime,, LongDate
StringReplace, DateTime, DateTime, %A_Space%г., `,, All
ResponseText := RegExReplace(ResponseText, "s)<span class=""rel_date"">вчера в", "<span class=""rel_date"">[" DateTime "] вчера в")
}
ResponseText := RegExReplace(ResponseText, "s) (onmouse[a-z]+=|onclick=|data-from-id=)""[^""]*""", " ") ;!!
ResponseText := RegExReplace(ResponseText, "s)=""/", "=""https://vk.com/")
ResponseText := RegExReplace(ResponseText, "s)<div class=""like_wrap .*?</div> <div class=""reply_date"">", " <div class=""reply_date"">")
return
login:
SplashImage,, y-2 x200 w40 h14 M C11 ZH0 ZW0 ZX1 ZY1 B0 CT000000 CWffff00 FM8 FS6 WM600 WS400,, login
;--------------------------------------------
;Авторизация:
;--------------------------------------------
;email := "*****************"
;password := "*****************"
;HTTP := ComObjCreate("WinHTTP.WinHTTPRequest.5.1")
HTTP.Open("GET", "https://vk.com/", true)
HTTP.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko)")
HTTP.SetRequestHeader("Pragma", "no-cache")
HTTP.SetRequestHeader("Cache-Control", "no-cache, no-store")
HTTP.SetRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT")
HTTP.Send()
HTTP.WaitForResponse()
RegexMatch(HTTP.ResponseText, "s)name=""ip_h"" value=""(.+?)"".+?name=""lg_h"" value=""(.+?)""", match)
PostData := "act=login&role=al_frame&expire=&recaptcha=&captcha_sid=&captcha_key=&_origin=https`%3A`%2F`%2Fvk.com&ip_h=" match1 "&lg_h=" match2 "&email=" email "&pass=" password
HTTP.Open("POST", "https://login.vk.com/?act=login", true)
HTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
HTTP.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko)")
HTTP.SetRequestHeader("Pragma", "no-cache")
HTTP.SetRequestHeader("Cache-Control", "no-cache, no-store")
HTTP.SetRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT")
HTTP.Send(PostData)
HTTP.WaitForResponse()
sleep, 1000
;третья — для получения хэша:
HTTP.Open("GET", "https://vk.com/dev/execute", true)
HTTP.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko)")
HTTP.SetRequestHeader("Pragma", "no-cache")
HTTP.SetRequestHeader("Cache-Control", "no-cache, no-store")
HTTP.SetRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT")
HTTP.Send()
HTTP.WaitForResponse()
RegexMatch(HTTP.ResponseText, "s)Dev.methodRun\('([^']*)'", match)
StringReplace, match1, match1, :, `%3A, All
PostHash := match1
ResponseText =
;--------------------------------------------
SplashImage, OFF
Return
htmlcode:
htmlcode=
(
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>%NewTitle%</title>
<link type="text/css" rel="stylesheet" href="css\common.css"></link>
<link type="text/css" rel="stylesheet" href="css\fonts-cnt.css"></link>
<link type="text/css" rel="stylesheet" href="css\page.css"></link>
<link type="text/css" rel="stylesheet" href="css\post.css"></link>
<link type="text/css" rel="stylesheet" href="css\ui-common.css"></link>
<link type="text/css" rel="stylesheet" href="css\uncommon.css"></link>
<link type="text/css" rel="stylesheet" href="css\wall.css"></link>
<link type="text/css" rel="stylesheet" href="css\wide-dd.css"></link>
<Xbase href="https://vk.com/">
</head>
<body style="background-color:#ffffff!important;">
<div class="wall_wrap clear_fix">
<div class="big_wall">
<div class="wall_module">`n`n`n`n
)
Return
FormatSeconds(TimeInSec)
{
TimeInSec := TimeInSec, UTCOffset_sec :="", DateTime := 19700101 ; первое января 1970 года
DateTime += TimeInSec, sec
UTCOffset_sec -= A_NowUTC, sec ; учитываем часовой пояс
DateTime += UTCOffset_sec, sec
FormatTime, DateTime1, %DateTime%, LongDate
FormatTime, DateTime2, %DateTime%, H:mm ;:ss
StringReplace, DateTime1, DateTime1, %A_Space%г., , All
DateTime = %DateTime1%`, %DateTime2%
return DateTime
}
UnHTM(HTM) { ; Remove HTML formatting / Convert to ordinary text by SKAN 19-Nov-2009
Static HT ; Forum Topic: www.autohotkey.com/forum/topic51342.html
IfEqual,HT,, SetEnv,HT, % "ááââ´´ææàà&ååãã&au"
. "mlä&bdquo„¦¦&bull•ç縸¢¢&circˆ©©¤¤&dagger†&dagger‡°"
. "°÷÷ééêêèèððëë&euro€&fnofƒ½½¼¼¾¾>>&h"
. "ellip…ííîî¡¡ìì¿¿ïï««&ldquo“&lsaquo‹&lsquo‘<<&m"
. "acr¯&mdash—µµ··  &ndash–¬¬ññóóôô&oeligœòò&or"
. "dfªººøøõõöö¶¶&permil‰±±££"""»»&rdquo”®"
. "®&rsaquo›&rsquo’&sbquo‚&scaronš§§­¹¹²²³³ßßþþ&tilde˜&tim"
. "es×&trade™úúûûùù¨¨üüýý¥¥ÿÿ"
HTM := RegExReplace( HTM,"&(\w+;)", "&$1" ) ;!! для обработки &lt;
HTM := RegExReplace( HTM,"&(#\d+;)", "&$1" ) ;!! для обработки &#60;
TXT := RegExReplace( HTM,"<[^>]+>", " " ) ; Remove all tags between "<" and ">"
Loop, Parse, TXT, &`; ; Create a list of special characters
L := "&" A_LoopField ";", R .= (!(A_Index&1)) ? ( (!InStr(R,L,1)) ? L:"" ) : ""
StringTrimRight, R, R, 1
Loop, Parse, R , `; ; Parse Special Characters
If F := InStr( HT, A_LoopField ) ; Lookup HT Data
StringReplace, TXT,TXT, %A_LoopField%`;, % SubStr( HT,F+StrLen(A_LoopField), 1 ), All
Else If ( SubStr( A_LoopField,2,1)="#" )
StringReplace, TXT, TXT, %A_LoopField%`;, % Chr(SubStr(A_LoopField,3)), All
TXT := RegExReplace(TXT, " +", " ") ;!! множественные пробелы на один
TXT := RegExReplace(TXT, "m)(*UCP)(?<=\s|^)""(?=\w)", "«") ;!! кавычки
TXT := RegExReplace(TXT, "m)(*UCP)(?<=[^\s""])""(?=\W|$)", "»") ;!! кавычки
;!! кавычки
loop, 10
{
TXT := RegExReplace(TXT, "«([^«»]*)«([^«»]*)»", "«$1«$2»")
TXT := RegExReplace(TXT, " ""([\s?\)\]\}\,\.\:\;\!\?…""'])", "n~b~s~p""$1")
TXT := RegExReplace(TXT, "((^|[\s]| )([\(\[\{""]|\d+)*)([""])(\S([^""]*?|.*?\x20[""]\x20.*?)\S|[^""\s])[""]((\d+|[\)\]\}\,\.\:\;\!\?…""])*($|[\s]| ))", "$1«$5»$7")
TXT := RegExReplace(TXT, "n~b~s~p", " ")
TXT := RegExReplace(TXT, "«([^«»]*)«([^«»]*)»", "«$1„$2“")
}
Return RegExReplace( TXT, "(^\s*|\s*$)") ; Remove leading/trailing white spaces
}