Обычным вечером заглянул в комментарии одного из сообществ Вконтакте и решился поучаствовать в дискуссии. Но не тут-то было! Чтобы прочесть „беседу“ нескольких ораторов потребовалось пролистать обсуждение и отсеять десятки лишних реплик, не участвовавших в нужном мне диалоге. Очевидная рутина, которую очень хочется спихнуть на механические мозги. Но инструмента, позволяющего вычленить только нужное у Вконтакта почему-то нет. „Что ж? За дело!“ — прокричал один из внутренних голосов, а остальные единогласно поддержали.
Так я начал пилить расширение для браузера 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 к Вконтакту в гости за списком не отображённых комментариев
Во время прогулки до 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. Если ещё и упомянет автора, то будет отлично.
UPD: Отличные новости! Расширение стало доступно каждому! MadDAD выложил его в chrome store — ссылка.
Ура, товарищи! MadDAD, спасибо большое. Я безгранично признателен.
Комментарии (21)
Psychosynthesis
16.09.2016 11:41А для лисы сделаете?
zakmamontov
16.09.2016 12:35В любом случае посмотрю, сложно ли адаптировать. С файрфоксом дел не имел раньше. Откровенно говоря, я не очень много дел имел с JS даже — просто знаю базовый синтаксис. Я питонист. Бэкендер. Сложно гарантировать, в общем, будет или нет для лисы оно, но обязательно взгляну. Вообще, там 95% логики не опирается на браузерные инструменты. Если есть желание, можете запилить форк :).
em92
16.09.2016 11:51>когда обсуждение разветвляется и затем вновь «схлапывается» к итоговому комментарию.
Это как? Насколько я понимаю, схлапывание в один комментарий — это когда комментарий формально является ответом на сразу 2 или более комментариев, что не предусмотрено самим ВК.zakmamontov
16.09.2016 12:24Технически, можно «упоминания» оставить в комментарии, чтобы уведомление пришло нескольким собеседникам. Конечно, этим редко пользуются. Я же писал про следующую ситуацию: есть строгая цепочка комментариев, принадлежащих пользователям A, B и C. В середине цепочки пользователь A формально выпадает. Его комментарии не принадлежат цепи, но он обменивается репликами с пользователем C, который параллельно оставляет комменты, участвующие в строгой цепочке. Спустя k комментов A снова начинает оставлять комментарии, которые принадлежат формальной цепи. Считаю, что обмен репликами между A и C, который технически не попал в «обсуждение», всё равно стоит отображать.
bustEXZ
16.09.2016 11:52Какая у Вас лютая цепочка, можно было просто добавить кнопочку к крестику «удали/заблокировать», как раз как линк :) А за расширение спасибо, попробуем!
zakmamontov
16.09.2016 12:55Она при наведении яркая такая. В обычном состоянии в глаза бросается куда меньше. Но не могу отрицать, что в дизайнерском ремесле я звёзд с неба не хватаю. И ведь этот вариант лучший. Клянусь, не меньше 3х часов — почти весь вечер(!) провёл в попытках сделать красивую «иконку». :) Попрошу кого-нибудь из дизайнеров поправить внешний вид целиком. Эта проблема обязательно решится :)
MadDAD
16.09.2016 13:52+1Выложил: https://chrome.google.com/webstore/detail/vkcommentschain/ojhbaaoencoeidckdklfmglkkidmndeb
kicumkicum
16.09.2016 14:20это законно вообще, выкладывать чужие расширения?
zakmamontov
16.09.2016 14:24Конечно. Я же открыто согласился, что можно делать с ним всё, что угодно. Без ограничений. Open-source :)
xtala
16.09.2016 14:40Наверняка Хабр читают разработчики VK. Могли бы и внедрить вашу разработку, пользователи наверняка бы оценили. Но к сожалению они могут только ломать, то что хорошо работало годами, предлагая фесбучные решения пятилетней давности.
zakmamontov
16.09.2016 14:43Спасибо. Мне приятно. Но про вконташевых разрабов Вы зря так. Лично знаю прогеров оттуда — очень крутые профессионалы. Специалисты, каких крайне мало. Без задач не сидят — бэклог везде бесконечен :) И не они же придумывают себе задачи и направление развития части или всего вконтача. Это как и везде. Ну а конкретно вчерашние блэкауты связаны не с их косорукостью, а с крупными изменениями в архитектуре. Не буду продолжать адвокатствовать — нудновато получается. Если каким-то образом эта или подобная идея запилится в самом вконташе, то буду рад. Действительно же удобнее так комментарии читать. Пусть всем удобно будет :)
xtala
16.09.2016 15:22Я не про backend имел ввиду. Я просто смотрю и оцениваю внешний вид и удобство сайта, как пользователь, в стиле «было — стало». Павел Дуров обоснованно высказал претензии к новому VK. То, что стало с VK это шаг назад, стратегических улучшений в плане удобства пользования я вообще не могу назвать. Нововведений кроме редизайна тоже. Ведь есть же люди, вот вы например, предложили идею выделений цепочек. Слабо верится, что в такой компании как VK нет людей генерирующих хорошие идеи. Вопрос скорее всего стоит в том, что начальство не хочет их принимать и внедрять. Так, что мой комментарий не о профессиональных качествах тамошних разработчиков в чьем высоком уровне я не сомневаюсь.
zakmamontov
16.09.2016 15:51Я слишком узко понял слово «разработчики», значит. Прошу прощения.
А люди с идеями, конечно, там есть. Безусловно. И их не мало. Но нужно понимать, что вконташ сейчас — это не вконташ 2006-2007го. Теперь это крупный бизнес. Все решения принимаются исходя из этой позиции. Это не очень оправдывает чьи-либо испорченные впечатления, конечно. Но и критиковать людей за то, что они делают свою работу, мне трудно :)
Вот цепочки эти. Даже может и придумали их там и может уже давно давно, но приоритет у задачи слабый, есть более важные для бизнеса. Ну ничего! Я вот сделал цепочки, и они теперь есть. Ещё и время отлично провёл. И так с любой вещью в мире. Не нравится — берём и меняем сами :)
776166
Оффтопик: Вконтакт, Фейсбуки и прочее современное социальное говно не предназначены для «обсуждений» в принципе. Нужен или форум, или более приспособленный для этого инструмент с древовидной структурой комментариев, типа disqus для встравивания, или ЖЖ, как платформа.
MonkAlex
Я вас расстрою, но большинство формумов тоже не умеет показать цепочку диалога конкретного. Потому как обычно это не цепочка, а дерево, причем разветвленное в обе стороны от выбранной точки.
776166
Оффтопик: Я не расстроюсь, потому что давно поставил крест на среднем уровне интеллекта людей. В моём понимании приличный и уважающий себя человек не может использовать твиттер ни для каких целей.
На сколько я помню, проблема форумов (в основном на bb) была в том, что там по-умолчанию выбирался не древовидный способ отображения информации, но сам форму это поддерживал. Собственно, вы не можете сделать серьёзную площадку для обсуждения чего-либо, если она не наглядно древовидная в комментариях. Все серьёзные площадки древовидные. Недревовидные — сборник фигни. Дверовидные, которые услужливо делают отступ для первой пары уровней комментариев — туда же. Рано или поздно серьёзные люди, которым есть, что обсудить, оттуда сваливают. Единственное, для чего можно использовать современную соцхрень — для диалога с одним человеком. Больше двух — проблема.
MonkAlex
Вы слишком узко подходите к проблеме. Диалог не обязан быть строго древовидным и однонаправленным. Однонаправленный диалог — неконструктивен.
776166
Диалог однонаправленный по определению. :) Если в дискуссию страивается ещё кто-то, то нет никаких способов визуальной организации пространства, чем дерево. Но дискуссия с несколькими членами, это уже не диалог. :)
Плоский диалог удобен. Плоская дискуссия с несколькими членами — нет.
Впрочем, в диалоге могут обсуждаться отдельные моменты. Их удобно выносить в отдельные ветки. Тут тоже на плоскости делать нечего. Только ветки.
SerCe
Ох тыж. Вот это заявление. Может подкрепите аргументами? Вам вот пары ссылок достаточно, или мне продолжить перечислять (боюсь это может затянуться)?
776166
Да, вполне. На всякий случай уточняю, что это моё личное мнение, никак не претендующее на истинность. Более того, т.к. твиттер ещё существует, это доказывает, что я неправ, а всеобщая социализация пока побеждает мою социопатию. :)
Вы дали ссылки на твиттеры известных личностей. Они генерируют контент (на мой взгляд довольно бесполезный), который теоретически имеет хоть какую-то ценность, потому что личность известная. Но быть известным человеком, это не значит, что ты генерируешь полезный контент, который имеет хоть какой-то смысл читать. Твиттер не предназначен для обсуждений (в контексте обсуждаемой теме), только для репостов. Репосты мне не интересны. Новости предпочитаю узнавать только те, которые мне интересны, без дополнительного информационного шума, которого итак много. Быть в курсе жизни людей, с которыми я даже не знаком, считаю пустой тратой времени.
Более того, обсуждения в массе своей являются полным шлаком.
Помню как-то пытался дать ссылку на что-то из твиттера. Ковырялся минут 10. Много бутстрапа, копипастить невозможно, названия дикие (вместо «пост», «сообщение» они использовали «статус»). Сейчас не знаю, что как. Для меня глагол “twitting” из новояза является ругательством.