Скуті одним ланцюгом, або додамо комфорту коментарям Вконтакте

Звичайним ввечері заглянув в коментарі одного з спільнот Вконтакте і вирішив взяти участь у дискусії. Але не тут-то було! Щоб прочитати «бесіду» декількох ораторів треба було перегорнути обговорення і відсіяти десятки зайвих реплік, які не брали участь у потрібному мені діалозі. Очевидна рутина, яку дуже хочеться спихнути на механічні мізки. Але інструмента, що дозволяє вичленувати тільки потрібне в Вконтакту чомусь немає. «Що ж? За справу!» — прокричав один з внутрішніх голосів, а решта одноголосно підтримали. Так я почав пиляти розширення для браузера Google Chrome, що дозволяє дивитися ланцюжка пов'язаних коментарів в обговореннях Вконтакту.

Скуті одним ланцюгом

Кому нецікавий сюжет, можуть відразу ознайомитися з результатом — посилання на джерело. Вона буде продубльована і в кінці статті.

Геть, дрібниці на зразок написання маніфесту! Розпочну відразу до цікавого.

Вбудовування скрипта в код сторінки
Мета — виводити ланцюжок коментарів по кліку на іконку у коментаря. Для цього потрібно це іконку промалювати і повісити на неї подія.

ікона
Так ця „іконка“ виглядає в підсумку

Відразу стало ясно, що код повинен працювати «зсередини». Вмонтує його сторінку звичайний для розширень контент-скрипт. Ось так він з цим впорається:

var s = document.createElement("script");
s.src = chrome.extension.getURL('/js/inject.js');
(document.head || document.documentElement).appendChild(s);

Ще вбудовуваного кодом потрібні якісь статичні дані, начебто урлов фонових зображень і id самого розширення. Їх я пробрасываю через самописное подія, що викликається в контент-скрипті після того, як вбудований скрипт подгрузился. Контент-скрипт цілком тепер виглядає наступним чином:

var s = document.createElement("script");
s.src = chrome.extension.getURL('/js/inject.js');
(document.head || document.documentElement).appendChild(s);
s.onload = function() {
var evt = document.createEvent("CustomEvent");
var url = chrome.extension.getURL('/img/planet.gif');
app_uid = chrome.runtime.id;
evt.initCustomEvent("BindDefData", true, true, { 'url': url, 'app_uid': app_uid });
document.dispatchEvent(evt);
};

Це фінальний його вигляд. А „слухається“ подія на стороні вбудованого скрипта. Це досить банально.

Спілкування між вбудовуваним і фоновим скриптами
Отримання коментарів, само собою, відбувається через api Вконтакте (а саме через метод wall.getComments). Процес їх отримання зовсім не вимагає, щоб код був написаний в встраиваемом сторінку в скрипті. Він буде написаний в так званому «бекграунд-скрипті». Нагадаю структуру. У нас є:

  • контент-скрипт — пробрасывающий безпосередньо на сторінку вбудований код і статичну інформацію
  • вбудований скрипт — код, що відповідає за логіку відображення
  • бекграунд-скрипт — код, що працює у фоні і чекає команди від вбудованого. Ходить по api до Вконтакту в гості за списком не відображених коментарів
preloader
Під час прогулянки до api малюється прелоадер. Затримки зазвичай немає, але часом щось сумнівається в надрах Вконтакту і доводиться чекати пару секунд

Сам запит до api виконується простенької функцією, що реалізує xhr:


var send_api_request = function(data, url, sync) {
var req = new XMLHttpRequest();
req.open('GET', url + dict_to_uri(data), sync);
req.send();
return req.responseText;
}

Набагато цікавіше бекграунд приймає команди від вбудованого скрипта. Для цього був написаний «слухач»:

chrome.extension.onMessageExternal.addListener(function(request, sender, call_back) {
chain = create_chaine(request.ids_list); //побудова ланцюжка коментарів
call_back({ 'chain': chain[0], 'pid': request.ids_list[1], 'persons': chain[1] }); //повернення отриманих даних на бік „клієнта“
})

Зверніть увагу, що використовується onMessageExternal, так як подія викликається з вбудованого скрипта. Іншими словами для браузера ці події викликаються на зовнішньому для нього сайті, а не всередині власного розширення.

З боку клієнта подія викликається так:

chrome.runtime.sendMessage(app_uid, { 'ids_list': ids_list }, function(result) { ... })

В ньому ми передаємо список необхідних id-шників (id оратора, посади, коментарю) і потім обробляємо отриману ланцюжок коментарів. Процес побудови самого діалогу банальний, з ним можна ознайомитися в исходниках.

Отрисовка
Прийшов час „видимої“ частини розширення відтворення спливаючого вікна.


Так виглядає короткий діалог зараз

Я не став винаходити велосипед і після хвилинного реверс-інжинірингу скористався класом MessageBox, який використовує сам Вконтакт:


var draw_box = function(html) {
var box = new MessageBox({
title: false,
width: 670,
onHide: false,
onDestroy: false,
bodyStyle: 'background-color: rgba(79, 113, 152, 0.3); border-radius: 6px',
hideButtons: true,
grey: true,
progress: true,
hideOnBGClick: false
});
box.content(html);
box.show();
return box;
}

Html-шаблон, який вкладається, був «здертий» і трохи перероблений з першого-ліпшого хипстерского сайтику. Він дуже незграбний, щоб цитувати. А він наповнюється з допомогою трохи оттюнингованной вконтактовой функції rs:


var rs_t = function(html, repl) {
each(repl, function(k, v) {
if (k == 'text') {
v = (typeof v === 'undefined' ? ": v);
v = v.replace(/(\r|\n)/g, ' <br /> '); // make newlines
v = v.replace(/((http)?s?(\:\/\/)?((www)?\.?[a-zA-Z0-9]+\.[a-zA-Z]+\/?\S+))/g, '<a href="$1" target="_blank">$4</a>'); //make links
};
html = html.replace(new RegExp('%' + k + '%', 'g'), (typeof v === 'undefined' ? ": v).toString().replace(/\$/g, '$'));
});
html = html.replace(/\[(id[0-9]+)\|([^\]+]+)\]/, '<a href="/$1">$2</a>'); // [id|name] -> <a href="/id">name</a>
return html;
}

Тобто в шаблоні підміняється конструкція виду %data% на значення змінної data. Цим займалася оригінальна функція. Я додав ще пару штучок: генерацію посилань на профіль з записів виду [id000000|Іван Іванов], генерацію посилань з урлов в тексті повідомлення і обробку символів кінця рядка (перенесення).


Обрізаний скрін реального обговорення

Повноцінний. Акуратно, габарити.
В цілому, все вже працює, але немає основного елемента — ручки, потягнувши за яку, ми побачимо всі ці чудеса. І тут знову не обійшлося без дещиці реверс-інжинірингу. У Вконтакту є клас Wall і метод класу _repliesLoaded. Він викликається, коли коментарі подгрузились, про що нам недвозначно натякає назва. Я підмінюю цей метод своїм:

var cloned_function = Wall._repliesLoaded;
Wall._repliesLoaded = function(post, hl, replies, names, data) {
cloned_function(post, hl, replies, names, data);
wall_overloaded = true;
setTimeout(replace_html, 300);
};

Тобто технічно викликаю його ж, але відразу після запускаю функцію, яка малює в потрібних свежеподгруженных коментарях іконку, клікнувши по якій ми і побачимо спливаюче вікно з ланцюжком коментарів.

Що в підсумку?
Розширення працює. Вміє показувати ланцюжка коментарів з зображеннями і посиланнями. Вміє стежити за наявною контентом і додавати кнопку ланцюжка, куди потрібно.


Скрін ланцюжка коментарів з посиланнями, зображеннями і переносами рядків.

Не вміє будувати паралельні ланцюжки для випадків, коли обговорення розгалужується і потім знову «схлапывается» до підсумкового коментарю. Скоро навчиться. В процесі підбору структури даних.

Фреймворками не обмазано. Для кого-то воно плюс, для кого-то мінус. Дуже б хотілося високо підняти голову і гордо заявити: «Та не потрібні мені вони зовсім!». Але немає. Все прозаїчніше — майже не знаю місцевих фреймворків та батарейок. JS — неосновної мову.

Посилання на репозиторій з исходниками. Буду радий форкам і пулл-реквестам.

P. S.: Я не став оплачувати собі аккаунт розробника в гуглі. Мені він ні до чого. Якщо хто бажає, може сміливо виставляти розширення chrome web store. Якщо ще й згадає автора, то буде відмінно.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.