Разумевање ФрееПБКС-а и његово интегрисање са Битрикс24 и више

Битрик24 је огроман комбинат који комбинује ЦРМ, ток посла, рачуноводство и многе друге ствари које менаџери заиста воле, а ИТ особље не воли. Портал користи многа мала и средња предузећа, укључујући мале клинике, произвођаче, па чак и козметичке салоне. Главна функција коју менаџери „воле” је интеграција телефоније и ЦРМ-а, када се сваки позив одмах сними у ЦРМ, креирају се клијентске картице, при доласку се приказују информације о клијенту и одмах можете видети ко је, шта је. може продати и колико дугује. Али телефонија са Битрикс24 и њена интеграција са ЦРМ кошта, понекад много. У чланку ћу вам испричати искуство интеграције са отвореним алатима и популарном ИП ПБКС-ом ФрееПБКС, а такође размотрити логику рада разних делова

Радим као аутсорсер у компанији која продаје и конфигурише, интегрише ИП телефонију. Када су ме питали да ли можемо да понудимо нешто овој и овој компанији за интеграцију Битрикс24 са централама које купци имају, као и са виртуелним централама на разним ВДС компанијама, отишао сам у Гугл. И наравно да ми је дао линк чланак у хабр, где постоји опис, и гитхуб, и изгледа да све ради. Али када смо покушали да искористимо ово решење, испоставило се да Битрикс24 више није исти као раније и да треба много тога да се уради. Поред тога, ФрееПБКС није гола звездица за вас, овде морате да размислите о томе како да комбинујете једноставност коришћења и хардцоре диалплан у конфигурационим датотекама.

Проучавамо логику рада

Дакле, за почетак, како би све требало да функционише. Када се на ПБКС прими позив споља (СИП ИНВИТЕ догађај од провајдера), почиње обрада плана бирања (диалплан, диалплан) – правила шта и којим редоследом треба да се ради са позивом. Од првог пакета можете добити много информација, које се затим могу користити у правилима. Одличан алат за проучавање унутрашњости СИП-а је анализатор снгреп (веза) који се једноставно инсталира у популарним дистрибуцијама преко апт инсталл/иум инсталл и слично, али се такође може изградити из извора. Погледајмо евиденцију позива у снгреп-у

Разумевање ФрееПБКС-а и његово интегрисање са Битрикс24 и више

У поједностављеном облику, диалплан се бави само првим пакетом, понекад и током разговора, преносе се позиви, притискање тастера (ДТМФ), разне занимљиве ствари попут ФолловМе, РингГроуп, ИВР и др.

Шта је унутар Инвите Пацк-а

Разумевање ФрееПБКС-а и његово интегрисање са Битрикс24 и више

У ствари, већина једноставних планова бирања ради са прва два поља, а цела логика се врти око ДИД-а и ЦаллерИД-а. ДИД - где зовемо, ЦаллерИД - ко зове.

Али ипак имамо компанију а не један телефон - што значи да централа највероватније има групе позива (истовремено / узастопно звоно неколико уређаја) на градским бројевима (Ринг Гроуп), ИВР (Здраво, звали сте ... Притисните један за ...), Телефонске секретарице (Фразе), Временски услови, Прослеђивање на друге бројеве или на ћелију (ФолловМе, Форвард). То значи да је веома тешко недвосмислено одредити ко ће заиста примити позив и са ким ће разговарати када позив стигне. Ево примера почетка типичног позива у ПБКС-у наших клијената

Разумевање ФрееПБКС-а и његово интегрисање са Битрикс24 и више

Након што позив успешно уђе у ПБКС, он путује кроз план бирања у различитим „контекстима“. Контекст са тачке гледишта Астериск-а је нумерисани скуп команди, од којих свака садржи филтер према бираном броју (зове се екстен, за екстерни позив у почетној фази ектен=ДИД). Команде у линији за бирање могу бити било које - интерне функције (на пример, позвати интерног претплатника - Dial(), спусти телефон - Hangup()), условни оператори (IF, ELSE, ExecIF и слично), прелази на друга правила овог контекста (Goto, GotoIF), прелазак на друге контексте у облику позива функције (Госуб, Мацро). Посебна директива include имя_контекста, који додаје команде из другог контекста на крај тренутног контекста. Команде укључене преко инцлуде се увек извршавају после команде тренутног контекста.

Цела логика ФрееПБКС-а је изграђена на укључивању различитих контекста један у други путем укључивања и позива преко Госуб, Мацро и Хандлер руковалаца. Размотрите контекст долазних ФрееПБКС позива

Разумевање ФрееПБКС-а и његово интегрисање са Битрикс24 и више

Позив пролази редом кроз све контексте од врха до дна, у сваком контексту могу постојати позиви ка другим контекстима као што су макрои (Макро), функције (Госуб) или само прелази (Гото), тако да право стабло онога што се зове може само бити праћени у евиденцији.

Типичан дијаграм подешавања за типичну ПБКС је приказан испод. Приликом позивања, ДИД се тражи у долазним рутама, проверавају се привремени услови за њега, ако је све у реду, покреће се гласовни мени. Из њега, притиском на дугме 1 или тимеоут, изађите у групу оператера бирања. Након завршетка позива, позива се макро хангупцалл, након чега се ништа не може урадити у дијалплану, осим специјалних руковалаца (хангуп хандлер).

Разумевање ФрееПБКС-а и његово интегрисање са Битрикс24 и више

Где у овом алгоритму позива треба да доставимо информације о почетку позива ЦРМ-у, где да почнемо снимање, где да завршимо снимање и пошаљемо га заједно са информацијама о позиву ЦРМ-у?

Интеграција са екстерним системима

Шта је ПБКС и ЦРМ интеграција? Ово су подешавања и програми који конвертују податке и догађаје између ове две платформе и шаљу их једни другима. Најчешћи начин комуникације независних система је преко АПИ-ја, а најпопуларнији начин приступа АПИ-ју је ХТТП РЕСТ. Али не за звездицу.

Унутар Астериск-а је:

  • АГИ - синхрони позив екстерних програма/компоненти, користи се углавном у дијалплану, постоје библиотеке као пхпаги, ПАГИ

  • АМИ - текстуални ТЦП соцкет који ради на принципу претплате на догађаје и уноса текстуалних команди, изнутра личи на СМТП, може пратити догађаје и управљати позивима, постоји библиотека ПАМИ - најпопуларнији за стварање везе са Астериск-ом

Пример АМИ излаза

Догађај: Нови канал
Привилегија: позвати,све
Канал: ПЈСИП/ВМС_пјсип-0000078б
Стање канала: 4
ЦханнелСтатеДесц: Прстен
ЦаллерИДНум: 111222
ЦаллерИДНаме: 111222
ЦоннецтедЛинеНум:
повезано име линије:
Језик: ен
код рачуна:
Контекст: фром-пстн
Ектен: с
Приоритет: 1
Јединствени број: 1599589046.5244
Линкедид: 1599589046.5244

  • АРИ је мешавина оба, све преко РЕСТ-а, ВебСоцкет-а, у ЈСОН формату - али са свежим библиотекама и омотима, не баш добро, случајно пронађено (пхпариа, пхпари) који је постао у њиховом развоју пре око 3 године.

Пример АРИ излаза када се започне позив

{ "варијабле":"ЦаллМеЦаллерИДНаме", "валуе":"111222", "типе":"ЦханнелВарсет", "тиместамп":"2020-09-09Т09:38:36.269+0000", "цханнел":{ "ид »:»1599644315.5334″, «име»:»ПЈСИП/ВМСпјсип-000007б6″, "стате":"Ринг", "цаллер":{ "наме":"111222″, "нумбер":"111222″ }, "цоннецтед":{ "наме":"", "нумбер" :"" }, "аццоунтцоде":"", "диалплан":{ "цонтект":"фром-пстн", "ектен":"с", "приорити":2, "аппнаме":"Стасис", "аппдата":"хелло-ворлд" }, "цреатионтиме":"2020-09-09Т09:38:35.926+0000", "лангуаге":"ен" }, "астерискид":"48:5б:аа:аа:аа:аа", "апплицатион":"хелло-ворлд" }

Погодност или неугодност, могућност или немогућност рада са одређеним АПИ-јем одређују се задацима које треба решити. Задаци за интеграцију са ЦРМ су следећи:

  • Пратите почетак позива, где је пренет, извуците ЦаллерИД, ДИД, време почетка и завршетка, можда податке из именика (за тражење везе између телефона и ЦРМ корисника)

  • Започните и завршите снимање позива, сачувајте га у жељеном формату, обавестите на крају снимка где се датотека налази

  • Покрените позив на екстерном догађају (из програма), позовите интерни број, екстерни број и повежите их

  • Опционо: интегришите са ЦРМ-ом, групама за бирање бројева и ФолловМЕ за аутоматски пренос позива у одсуству места (према ЦРМ-у)

Сви ови задаци се могу решавати преко АМИ или АРИ, али АРИ пружа много мање информација, нема много догађаја, многе варијабле које АМИ још увек има (на пример, макро позиви, подешавање променљивих унутар макроа, укључујући снимање позива) се не прате. Стога, за исправно и тачно праћење, изаберимо АМИ за сада (али не у потпуности). Поред тога (па где би без овога, ми смо лењи људи) - у оригиналном делу (чланак у хабр) користите ПАМИ. *Затим морате покушати да препишете на АРИ, али не и чињеница да ће то радити.

Поновно проналажење интеграције

Да би наш ФрееПБКС могао на једноставне начине да извештава АМИ о почетку позива, времену завршетка, бројевима, називима снимљених фајлова, најлакше је израчунати трајање позива користећи исти трик као и оригинални аутори - унесите своје варијабле и анализирајте излаз за њихово присуство. ПАМИ предлаже да се то уради једноставно кроз функцију филтера.

Ево примера постављања сопствене променљиве за време почетка позива (с је посебан број у плану бирања који се изводи ПРЕ него што започнете ДИД претрагу)

[ext-did-custom]

exten => s,1,Set(CallStart=${STRFTIME(epoch,,%s)})

Пример АМИ догађаја за ову линију

Догађај: Нови канал

Привилегија: позвати,све

Канал: ПЈСИП/ВМС_пјсип-0000078б

Стање канала: 4

ЦханнелСтатеДесц: Прстен

ЦаллерИДНум: 111222

ЦаллерИДНаме: 111222

ЦоннецтедЛинеНум:

повезано име линије:

Језик: ен

код рачуна:

Контекст: фром-пстн

Ектен: с

Приоритет: 1

Јединствени број: 1599589046.5244

Линкедид: 1599589046.5244

Апликација: Подесите податке апликације:

ЦаллСтарт=1599571046

Зато што ФрееПБКС преписује датотеке ектентион.цонф и ектентион_Аддитионал.цонф, користићемо датотеку проширење_обичај.цонф

Пун код ектентион_цустом.цонф

[globals]	
;; Проверьте пути и права на папки - юзер asterisk должен иметь права на запись
;; Сюда будет писаться разговоры
WAV=/var/www/html/callme/records/wav 
MP3=/var/www/html/callme/records/mp3

;; По этим путям будет воспроизводится и скачиваться запись
URLRECORDS=https://www.host.ru/callmeplus/records/mp3

;; Адрес для калбека при исходящем вызове
URLPHP=https://www.host.ru/callmeplus

;; Да пишем разговоры
RECORDING=1

;; Это макрос для записи разговоров в нашу папку. 
;; Можно использовать и системную запись, но пока пусть будет эта - 
;; она работает
[recording]
exten => ~~s~~,1,Set(LOCAL(calling)=${ARG1})
exten => ~~s~~,2,Set(LOCAL(called)=${ARG2})
exten => ~~s~~,3,GotoIf($["${RECORDING}" = "1"]?4:14)
exten => ~~s~~,4,Set(fname=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H_%M)}-${calling}-${called})
exten => ~~s~~,5,Set(datedir=${STRFTIME(${EPOCH},,%Y/%m/%d)})
exten => ~~s~~,6,System(mkdir -p ${MP3}/${datedir})
exten => ~~s~~,7,System(mkdir -p ${WAV}/${datedir})
exten => ~~s~~,8,Set(monopt=nice -n 19 /usr/bin/lame -b 32  --silent "${WAV}/${datedir}/${fname}.wav"  "${MP3}/${datedir}/${fname}.mp3" && rm -f "${WAV}/${fname}.wav" && chmod o+r "${MP3}/${datedir}/${fname}.mp3")
exten => ~~s~~,9,Set(FullFname=${URLRECORDS}/${datedir}/${fname}.mp3)
exten => ~~s~~,10,Set(CDR(filename)=${fname}.mp3)
exten => ~~s~~,11,Set(CDR(recordingfile)=${fname}.wav)
exten => ~~s~~,12,Set(CDR(realdst)=${called})
exten => ~~s~~,13,MixMonitor(${WAV}/${datedir}/${fname}.wav,b,${monopt})
exten => ~~s~~,14,NoOp(Finish if_recording_1)
exten => ~~s~~,15,Return()


;; Это основной контекст для начала разговора
[ext-did-custom]

;; Это хулиганство, делать это так и здесь, но работает - добавляем к номеру '8'
exten =>  s,1,Set(CALLERID(num)=8${CALLERID(num)})

;; Тут всякие переменные для скрипта
exten =>  s,n,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
exten =>  s,n,ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp())
exten =>  s,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten =>  s,n,Set(CallMeDISPOSITION=${CDR(disposition)})

;; Самое главное! Обработчик окончания разговора. 
;; Обычные пути обработки конца через (exten=>h,1,чтототут) в FreePBX не работают - Macro(hangupcall,) все портит. 
;; Поэтому вешаем Hangup_Handler на окончание звонка
exten => s,n,Set(CHANNEL(hangup_handler_push)=sub-call-from-cid-ended,s,1(${CALLERID(num)},${EXTEN}))

;; Обработчик окончания входящего вызова
[sub-call-from-cid-ended]

;; Сообщаем о значениях при конце звонка
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})

;; Статус вызова - Ответ, не ответ...
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
exten => s,n,Return


;; Обработчик исходящих вызовов - все аналогичено
[outbound-allroutes-custom]

;; Запись
exten => _.,1,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
;; Переменные
exten => _.,n,Set(__CallIntNum=${CALLERID(num)})
exten => _.,n,Set(CallExtNum=${EXTEN})
exten => _.,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten => _.,n,Set(CallmeCALLID=${SIPCALLID})

;; Вешаем Hangup_Handler на окончание звонка
exten => _.,n,Set(CHANNEL(hangup_handler_push)=sub-call-internal-ended,s,1(${CALLERID(num)},${EXTEN}))

;; Обработчик окончания исходящего вызова
[sub-call-internal-ended]

;; переменные
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})

;; Вызов скрипта, который сообщит о звонке в CRM - это исходящий, 
;; так что по факту окончания
exten => s,n,System(curl -s ${URLPHP}/CallMeOut.php --data action=sendcall2b24 --data ExtNum=${CallExtNum} --data call_id=${SIPCALLID} --data-urlencode FullFname='${FullFname}' --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition='${CallMeDISPOSITION}')
exten => s,n,Return

Карактеристике и разлика у односу на оригинални дијаграм аутора оригиналног чланка -

  • Диалплан у .цонф формату, како то ФрееПБКС жели (да, може .аел, али не све верзије и није увек згодно)

  • Уместо обраде краја преко ектен=>х, обрада је уведена преко хангуп_хандлер-а, јер је ФрееПБКС диалплан радио само са њим

  • Фиксни низ позива у скрипти, додани цитати и екстерни позивни број ЕктНум

  • Обрада је премештена у _прилагођене контексте и омогућава вам да не додирујете или мењате конфигурације ФрееПБКС-а - долазе преко [ект-дид-цустом], одлазећи кроз [оутбоунд-аллроутес-цустом]

  • Без везивања за бројеве - датотека је универзална и само треба да се конфигурише за путању и везу до сервера

Да бисте започели, такође морате да покренете скрипте у АМИ-у помоћу корисничког имена и лозинке - за ово ФрееПБКС такође има _цустом датотеку

манагер_цустом.цонф фајл

;;  это логин
[callmeplus]
;; это пароль
secret = trampampamturlala
deny = 0.0.0.0/0.0.0.0

;; я работаю с локальной машиной - но если надо, можно и другие прописать
permit = 127.0.0.1/255.255.255.255
read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
write = system,call,agent,log,verbose,user,config,command,reporting,originate

Обе ове датотеке морају бити смештене у /етц/астериск, а затим поново прочитајте конфигурације (или поново покрените звездицу)

# astrisk -rv
  Connected to Asterisk 16.6.2 currently running on freepbx (pid = 31629)
#freepbx*CLI> dialplan reload
     Dialplan reloaded.
#freepbx*CLI> exit

Сада пређимо на ПХП

Иницијализација скрипти и креирање сервиса

Пошто шема за рад са Битрикс 24, услугом за АМИ, није сасвим једноставна и транспарентна, о њој се мора разговарати посебно. Астериск, када је АМИ активиран, једноставно отвори порт и то је то. Када се клијент придружи, тражи ауторизацију, а затим се пријављује на неопходне догађаје. Догађаји долазе у обичном тексту, који ПАМИ претвара у структуриране објекте и пружа могућност подешавања функције филтрирања само за догађаје од интереса, поља, бројеве итд.

Чим дође позив, НевЕктен догађај се покреће почевши од родитељског [фром-пстн] контекста, а затим сви догађаји иду редоследом редова у контекстима. Када се прими информација од променљивих ЦаллМеЦаллерИДНаме и ЦаллСтарт наведених у _цустом диалплан-у,

  1. Функција захтевања УсерИД-а који одговара броју локала на који је упућен позив. Шта ако је то диал-уп група? Питање је политичко, да ли треба да креирате позив свима одједном (када сви зову одједном) или да креирате како зову када зову редом? Већина клијената има стратегију „Фисрт Аваилабле“, тако да са овим нема проблема, само један позив. Али питање треба решити.

  2. Функција регистрације позива у Битрикс24, која враћа ЦаллИД, који је затим потребан да пријави параметре позива и везу до снимка. Захтева или број локала или УсерИД

Разумевање ФрееПБКС-а и његово интегрисање са Битрикс24 и више

Након завршетка позива, позива се функција преузимања записа, која истовремено извештава о статусу завршетка позива (Заузето, Без одговора, Успешно), а такође преузима везу до мп3 датотеке са записом (ако постоји).

Пошто модул ЦаллМеИн.пхп треба да ради непрекидно, за њега је креирана СистемД датотека за покретање цаллме.сервице, који се мора ставити у /етц/системд/систем/цаллме.сервице

[Unit]
Description=CallMe

[Service]
WorkingDirectory=/var/www/html/callmeplus
ExecStart=/usr/bin/php /var/www/html/callmeplus/CallMeIn.php 2>&1 >>/var/log/callmeplus.log
ExecStop=/bin/kill -WINCH ${MAINPID}
KillSignal=SIGKILL

Restart=on-failure
RestartSec=10s

#тут надо смотреть,какие права на папки
#User=www-data  #Ubuntu - debian
#User=nginx #Centos

[Install]
WantedBy=multi-user.target

иницијализација и покретање скрипте се дешава преко системцтл или сервиса

# systemctl enable callme
# systemctl start callme

Услуга ће се поново покренути по потреби (у случају пада). Услуга праћења пријемног сандучета не захтева инсталирање веб сервера, потребан је само пхп (који се дефинитивно налази на ФееПБКС серверу). Али у недостатку приступа евиденцији позива преко веб сервера (такође са хттпс), неће бити могуће слушати записе позива.

Хајде сада да причамо о одлазним позивима. ЦаллМеОут.пхп скрипта има две функције:

  • Покретање позива када се прими захтев за пхп скрипту (укључујући коришћење дугмета „Позови“ у самом Битриксу). Не ради без веб сервера, захтев се прима преко ХТТП ПОСТ-а, захтев садржи токен

  • Порука о позиву, његовим параметрима и записима у Битриксу. Активира Астериск у [суб-цалл-интернал-ендед] плану за бирање када се позив заврши

Разумевање ФрееПБКС-а и његово интегрисање са Битрикс24 и више

Веб сервер је потребан само за две ствари - преузимање датотека Битрик записа (преко ХТТПС-а) и позивање скрипте ЦаллМеОут.пхп. Можете користити уграђени ФрееПБКС сервер, датотеке за које су /вар/ввв/хтмл, можете инсталирати други сервер или одредити другу путању.

веб сервер

Оставимо подешавање веб сервера за самостално проучавање (титс, титс, титс). Ако немате домен, можете испробати ФрееДомаин( https://www.freenom.com/ru/index.html), који ће вам дати бесплатно име за ваш бели ИП (не заборавите да проследите портове 80, 443 преко рутера ако је спољна адреса само на њему). Ако сте управо креирали ДНС домен, онда морате да сачекате (од 15 минута до 48 сати) док се сви сервери не учитају. Према искуству рада са домаћим провајдерима - од 1 сата до дана.

Аутоматизација инсталације

Инсталатер је развијен на гитхуб-у како би инсталација била још лакша. Али на папиру је било глатко - док ми то све ручно инсталирамо, пошто је после петљања са свим овим постало кристално јасно шта се с ким дружи, ко где иде и како да то отклони. Још нема инсталатера

лучки радник

Ако желите брзо да испробате решење - постоји опција са Доцкер-ом - брзо направите контејнер, дајте му портове напоље, убаците датотеке за подешавања и покушајте (ово је опција са контејнером ЛетсЕнцрипт, ако већ имате сертификат , потребно је само да преусмерите обрнути прокси на ФрееПБКС веб сервер (дали смо му други порт је 88), ЛетсЕнцрипт у доцкер-у на основу овај чланак

Морате да покренете датотеку у преузетој фасцикли пројекта (након гит клона), али прво уђите у конфигурације астериск (фасцикла астериск) и тамо упишите путање до записа и УРЛ вашег сајта

version: '3.3'
services:
  nginx:
    image: nginx:1.15-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/ssl_docker.conf:/etc/nginx/conf.d/ssl_docker.conf
  certbot:
    image: certbot/certbot
  freepbx:
    image: flaviostutz/freepbx
    ports:
      - 88:80 # для настройки
      - 5060:5060/udp
      - 5160:5160/udp
      - 127.0.0.1:5038:5038 # для CallMeOut.php
#      - 3306:3306
      - 18000-18100:18000-18100/udp
    restart: always
    environment:
      - ADMIN_PASSWORD=admin123
    volumes:
      - backup:/backup
      - recordings:/var/spool/asterisk/monitor
      - ./callme:/var/www/html/callme
      - ./systemd/callme.service:/etc/systemd/system/callme.conf
      - ./asterisk/manager_custom.conf:/etc/asterisk/manager_custom.conf
      - ./asterisk/extensions_custom.conf:/etc/asterisk/extensions_custom.conf
#      - ./conf/startup.sh:/startup.sh

volumes:
  backup:
  recordings:

Ова датотека доцкер-цомпосе.иамл се покреће преко

docker-compose up -d

Ако се нгинк не покрене, онда нешто није у реду са конфигурацијом у фасцикли нгинк/ссл_доцкер.цонф

Друге интеграције

А зашто не бисмо у исто време убацили ЦРМ у скрипте, помислили смо. Проучавали смо неколико других ЦРМ АПИ-ја, посебно бесплатни уграђени ПБКС - СхугарЦРМ и Втигер, и да! да, принцип је исти. Али ово је друга прича, коју ћемо касније засебно учитати на гитхуб.

референце

Одрицање одговорности: Свака сличност са стварношћу је фиктивна и то нисам био ја.

Извор: ввв.хабр.цом

Додај коментар