Дата: 20 фев 2017 в 00:42
Вышло глобальное обновление платформы до версии 4.0.
     - Полный рефакторинг кода.
     - Поддержка PHP 7.
     - Ускорение в несколько раз.
     - Подключение, как модуль.
     - Работа с контекстом.
<ctx>img,5</ctx>
<preview>

<b>Архитектура:</b>
Помимо значительного обновления кода, была изменена архитектура платформы. 
Все служебные файлы(GC.php, GCF.php, config.php) переехали в корень приложения. 
GC.php взял на себя функционал index.php и api.php, данные файлы были удалены.
Все модули, вместо прошлого server/html, теперь располагаются в modules.
Часть фалов были удалены из админки. В итоге, сборка 4.0 состоит из 34 файлов, в 3-х версиях было 50.

<b>Функционал:</b>
Модули могут наследоваться от основного класса GC.
Появились функции getCtx, setCtx для работы с контекстом. 
Изменились параметры у функции getPage, вместо 3, 4 аргумента: noRender, clear, теперь отправляется массив с данными параметрами. Так же появился параметр fast и функция fastRender, для более быстрой работы с шаблонами в пределах одной страницы.

<b>Скорость:</b>
Были переработаны функции и КЭШ, что позволило ускорить работу более чем в два раза. 
Проведены тесты с загрузкой 10 страниц по 1000 раз. В одном тесте были только параметры, во втором параметры и логика. Сравнения проводились с GC 3.21. Ниже на графике указано полное время загрузки страницы.
<ctx>img,6</ctx>
При том же принципе работы, скорость обработки параметров увеличилась в 2 раза, а скорость обработки логики на 30%.  Так как обработка была в приделах одной страницы, можно воспользоваться параметром fast для функции getPage. Тогда скорость увеличивается до 60%. На PHP 7 скорость увеличивается более чем в 10 раз.

<b>Обновление с 3-х версий:</b>
1) Создать папку modules в корне проекта и скопировать в неё все файлы из server/html.
2) Из сборки 4.0 скопировать в корень проекта config.php. Заполнить его.
3) В server/config.php в $update_server указать "dev" сервер.
3) Обновиться в админке до последней версии.
4) Учесть изменения в базовых функциях.

<b>Стабильность:</b>
На основе GusevCore 4.0 создается крупный проект, и в ближайшее время будут устранены все ошибки, если такие остались. 
К 26 февраля 2017, GC 4.0 будет переведен на test сервер.
Больше
Дата: 12 фев 2017 в 22:02
Продолжаю серию статей о WebRTC. 
Предыдущая статья: <a href="http://gusevcore.ru/blog/prostoy_chat_cherez_WebRTC_DataChannel" target="_blank">Простой чат через WebRTC DataChannel</a>

За основу возьмем код чата, и немного изменим, вместо канала данных будем создавать трансляцию. Серверный скрип остается без изменений. Напомню только, будет работать в соседних вкладках браузера, т.к. используется сессия для хранения данных, а не база данных.
<ctx>demo,webrtc/data/video.html,Демо видео чат</ctx> 
<preview>

HTML страница и немного стилей.
<code>
<style type="text/css">
     #local {
          position: fixed;
          bottom: 0px;
          right: 0px;
     }
</style>
<div>Инструкция: Открыть в одной вкладке, нажать New. Открыть во второй вкладке, выбрать чат.</div>
<button onclick="createChat();">New</button>
<div id="chats"></div>
<video id="remote" autoplay></video>
<video id="local" autoplay width="200"></video>
</code>
Схема действий точно такая же, создаем чат по кнопке New открываем в другой вкладке в #chats отображается список чатов. В #remote видео собеседника. В #local ваше видео, отображается в нижнем правом углу.

Переменные и подписки те же самые, но теперь нет необходимости в создании createDataChannel и подписки на ondatachannel. В место этого подписка на появления трансляции.
<code>
RTC.onaddstream = function (event) {
     GCF.Q('#remote').src = URL.createObjectURL(event.stream);
};
</code>
Как только появится трансляция, установим её в видео собеседника.

Все остальные основные функции без изменений. Исчезли функции связанные с сообщением.
После загрузки страницы, до получения каких либо инструкций(createOffer, createAnswer), необходимо получить доступ к медиа устройствам.
<code>
navigator.getUserMedia({video: true, audio: true}, function(stream) {
     RTC.addStream(stream);
}, err);

navigator.getUserMedia({video: true}, function(stream) {
     GCF.Q('#local').src = URL.createObjectURL(stream);
}, err);
</code>
Сначала получим трансляции с видео и звуком и добавим addStream в наше соединение. Для отображения себя в углу получим трансляцию еще раз, то только одно видео, что бы самих себя не слышать.

Видео чат готов. Скоро продолжение данной темы...

Полезное:
<a href="http://gusevcore.ru/blog/Dostup_k_kamere_iz_Javascript" target="_blank">Доступ к камере из Javascript</a>
Больше
Дата: 11 фев 2017 в 20:36
WebRTC - (web real-time communications — коммуникации в реальном времени).
Данная технология позволяет обмениваться данными напрямую между пользователями, без сервера. Сервер участвует только на начальном этапе, для соединения. Через данную технологию можно передавать видео/аудио трансляцию, файлы, текстовую информацию. В данной статье рассмотрим создание простого текстового чата. Тестировать будем в Google Chrome. 
<ctx>demo,webrtc/data/index.html,Демо чат</ctx> 

Схема действия:
<ctx>img,4</ctx>
<preview>
Создадим простую HTML страницу:
<code>
<button onclick="createChat();">New</button>
<div id="chats"></div>
<textarea id="text"></textarea>
<button id="send" onclick="post();" disabled>Send</button>
<div id="recs"></div>
<script>getChats();</script>
</code>
Кнопка для создания нового чата. В #chats, будет отображать список чатов. #text и #send, поле текста и кнопка отправки сообщения. В #recs отображаем список сообщений. В самом конце, вызовем функцию получения чатов.

Приступим к каналу данных, для начала создадим соединение и канал, и несколько переменных для нашего чата.
<code>
var 
     RTC = new RTCPeerConnection({iceServers: [{url: "stun:stun.l.google.com:19302"}]}),
     channel = RTC.createDataChannel('chat'),
     user = Math.random(),         // Случайное число для идентификации юзера
     chatId = null,                // Ид выбранного чата
     chats = [],                   // Список чатов
     timer = null;                 // Таймер для отслеживания собеседника
</code>
Мы будем работать в соседних вкладках браузера, по этому в RTCPeerConnection, можно ничего не передавать, но для соединения пользователей из разных сетей необходимо добавить iceServers. Мы воспользуемся бесплатным сервером от Google: stun:stun.l.google.com:19302

Подпишемся на обнаружения кандидатов, они могут изменяться при смене сети или оборудования, так что их сразу сохраняем на нашем сервере.
<code>
RTC.onicecandidate = function(candidate) {
     if (candidate.candidate) {
          GCF.AJ('server.php?act=addCandidate&user=' + user + '&chat=' + chatId, candidate.candidate.toJSON());
     }
}

RTC.ondatachannel = function(event) {
     GCF.Q('#send').disabled = false;
     event.channel.onmessage = function(event) {
          // Пришло сообщение, отобразим
          msg(event.data);
     };
};
</code>
Вторая подписка на событие готовности канала, в ней мы подпишемся на входящее сообщение. И вызовем нашу функцию msg, для отображения сообщения(о ней ниже). 

Получение чатов:
<code>
function getChats() {
     GCF.AJ('server.php?act=getChats&user=' + user, {}, function(data){
          if (data.chats) {
               var html = '';
               for (var i = 0; i < data.chats.length; i++) {
                    chats[data.chats[i].id] = data.chats[i];
                    html += '<div onclick="openChat(' + data.chats[i].id + ')">' + data.chats[i].id + '</div>';
               }

               GCF.Q('#chats').innerHTML = html;
          }
     }, true);
}
</code>
Пока все просто, с помощью нашей библиотеке GCF, выполним Ajax запрос на сервер(его опишу ниже) для получения чатов. Если данные пришли, сохраним их с id в качестве ключа в переменной chats и выведем HTML код в #chats.

Далее у пользователя два пути, либо он может создать новый чат, нажав на кнопку New на форме или кликнуть по одному из отображенных чатов.

Пойдем по порядку с создания.
<code>
function createChat() {
     chatId = user; // Установим текущий чат, как ID текущего пользователя
     RTC.createOffer(function(offer) { // Создадим offer
          // Установим данные инструкции в локальный канал
          RTC.setLocalDescription(offer);
          // Оправим на сервер
          GCF.AJ('server.php?act=createChat&user=' + user, offer, function() {
               // Будем ожидать подобных  инструкций от собеседника
               timer = setInterval(getAnswer, 1000);
          });
     }, err);
}
</code>
Создадим инструкции, установим их как локальные и отправим на сервер. После createOffer, начнут вызываться события onicecandidate, которые, благодаря обработчику выше, будут уходить на сервер. Далее этому пользователю остается только ждать собеседника. 

Данный чат теперь отображается в списке, теперь представим, что зашел другой пользователь и выбрал этот чат.
<code>
function openChat(id) {
     chatId = id; // Установим выбранный чат
     // Установим инструкции от 1 пользователя в удаленный канал
     RTC.setRemoteDescription(chats[id].offer);
     RTC.createAnswer(function(answer) { // Создадим answer
          // Установим полученные инструкции в локальный канал
          RTC.setLocalDescription(answer);
          // Отправим инструкции 2 пользователя на сервер
          GCF.AJ('server.php?act=setAnswer&user=' + user + '&chat=' + chatId, answer, function() {
               // Готовы получать кандидатов
               timer = setInterval(getCandidate, 2000);
          });
     }, err);
}
</code>
Тут мы установим инструкции от пользователя создавшего чат, как удаленные, получим свои инструкции, установим их, как локальные, и отправим на сервер. После установки локальных и удаленных инструкций, мы готовы получать и устанавливать кандидатов. Данный порядок важен. Сначала два вида инструкций, потом кандидаты.

Вернемся к пользователю создавшему чат. На сервере появился аnswer.
<code>
function getAnswer() {
     GCF.AJ('server.php?act=getAnswer&user=' + user + '&chat=' + chatId, {}, function(data){
          if (data.answer) {
               // Установим инструкции, пользователя открывшего чат, как удаленные
               RTC.setRemoteDescription(data.answer);
               clearInterval(timer);   // ответ есть таймер больше не нужен
               // Готовы получать кандидатов
               timer = setInterval(getCandidate, 2000);
          }
     }, true);
}
</code>
Аналогично, устанавливаем удаленные инструкции, готовы принимать кандидатов.

Кандидатов будем проверять на сервере, каждые 2 секунды, можно реже, вдруг что-то изменится.
<code>
function getCandidate() {
     GCF.AJ('server.php?act=getCandidate&user=' + user + '&chat=' + chatId, {}, function(data){
          if (data.candidates) {
               for (var i = 0; i < data.candidates.length; i++) {
                    RTC.addIceCandidate(new RTCIceCandidate(data.candidates[i]));
               }
          }
     }, true);
}
</code>
Как кандидаты будут установлены, вызовется ondatachannel, и можно будут отправлять сообщения.

И последний набор функций
<code>
function post() {
     // Отправляем сообщение по каналу
     channel.send(GCF.Q('#text').value);
     msg(GCF.Q('#text').value);
}
function msg(text) {
     GCF.Q('#recs').innerHTML = '<div>' + text + '</div>' + GCF.Q('#recs').innerHTML;
}
function err(e) {
     console.log(e);
}
</code>
Для createOffer и createAnswer обязательно нужен errorback, у нас это функция err.

Архитектура сервера по желанию, приведу решение, для данного примера.
<code>
<?
     session_start();

     // Проверим, что бы сессия с chats, была всегда
     if (!isset($_SESSION['chats'])) {
          $_SESSION['chats'] = array();
     }
     
     if ($_GET['act'] == 'createChat') { 
          // Создание чата, вызывается из createChat
          // Получим данные
          $data = file_get_contents('php://input');
          $data = json_decode($data, true);
          // Создад объект чата
          $_SESSION['chats'][$_GET['user']] = array(
               'id' => $_GET['user'],
               'offer' => $data,
               'answer' => null,
               'offerCandidates' => array(),
               'answerCandidates' => array()
          );
     } else if ($_GET['act'] == 'addCandidate') { 
          // Добавляем кандидата, вызывается из onicecandidate
          $data = file_get_contents('php://input');
          $data = json_decode($data, true);
          // Если ИД чата и юзера равны, значит это кандидаты создателя
          $candidates = $_GET['user'] == $_GET['chat'] ? 'offerCandidates' : 'answerCandidates';
          array_push($_SESSION['chats'][$_GET['chat']][$candidates], $data);
     } else if($_GET['act'] == 'getChats') {
          // Получаем чаты, вызывается из getChats
          $chats = array();
          foreach ($_SESSION['chats'] as $id => $chat) {
               // Будем отображать только с кандидатами создателя
               if (count($chat['offerCandidates'])) {
                    array_push($chats, $chat);
               }
          }
          echo '{"chats": ' . json_encode($chats) . '}';
     } else if ($_GET['act'] == 'setAnswer') {
          // Устанавливаем ответ, вызывается из openChat
          $data = file_get_contents('php://input');
          $data = json_decode($data, true);
          $_SESSION['chats'][$_GET['chat']]['answer'] = $data;
     } else if ($_GET['act'] == 'getAnswer') {
          // Получаем ответ, вызывается из getAnswer
          $answer = $_SESSION['chats'][$_GET['chat']]['answer'];
          if (!$answer) {
               $answer = null;
          }
          echo '{"answer": ' . json_encode($answer) . '}';
     } else if ($_GET['act'] == 'getCandidate') {
          // Получаем кандидата, вызывается из getCandidate
          // Если ИД чата и юзера равно, возвращаем кандидаты не создателя
          $candidates = $_GET['user'] == $_GET['chat'] ? 'answerCandidates' : 'offerCandidates';
          echo '{"candidates": ' . json_encode($_SESSION['chats'][$_GET['chat']][$candidates]) . '}';
          // Стерем их, т.к. уже будут установлены
          $_SESSION['chats'][$_GET['chat']][$candidates] = array();
     }
?>
</code>
Так как используются сессии, работать будет только в соседних вкладках браузера, но ничего не мешает сохранять в БД.

В ближайшее время рассмотрю пример видео трансляции. И возможно, оформлю данный код в виде функции библиотеки GCF.
Больше
Дата: 13 дек 2016 в 18:39
Разберем создание динамической матрицы на cnavas. Которую можно использовать, как шапку сайта, то есть должна должна адаптироваться к ширине окна. По желанию и к высоте.
<ctx>img,2</ctx>
<ctx>demo,matrix/index.html,,matrix/matrix.js,matrix.js</ctx> 
<preview>
В примере буду использовать библиотеку GCF.js, там реализованы функции удобной выборке DOM элементов, случайного числа в пределе, и клонирование объектов.
На HTML форме нам потребуется только сам canvas, в нашем случае с заданной высотой.
<code>
<style>
     #background {
          background: #000;
     }
</style>
<canvas id="background" height="200"></canvas>
</code>
Фон будем задавать на CSS.

Как страница загрузится, вызовем инициализацию матрицы
<code>
window.addEventListener('load', function() {
     matrix.init();
});
</code>

В демо примере в скрипте matrix.js в функции init будем отслеживать изменение размера страницы
<code>
this.resize();
window.onresize = this.resize.bind(this);
</code>
Забавный момент, вероятно есть причина, но при изменении размера canvas сбрасывается установленный стиль текста, а именно его размер, поэтому в функции resize, после изменения размера canvas равного ширине страницы, установим стиль текста
<code>
resize: function() {
     this._box.width = document.body.clientWidth;
     this.initStyle();
},

initStyle: function() {
     this._ctx.font = 'normal 12px Tahoma';
     this._ctx.textBaseline = 'top'; 
},
</code>

Так же в init создается массив this._data, где мы будим хранить нашу матрицу, строки и столбцы.
И устанавливается таймер, каждую 1 десятую секунды будет вызывается функция this.draw, которая выполняет отрисовку одной строки.

В this.draw первым делом добавим в данные новую строку this.addLine.
Добавляем в наш массив с данными пустую строку this._data.unshift([]);
И побегаем циклом по количеству колонок. Каждая строка может отличаться длинной, так как размеры окна браузера могли быть изменены
<code>
getCountColumns: function() {
     return Math.ceil(this._box.width / 20);
}
</code>
20 в нашем случае это ширина колонки в пикселях, зависит от размера шрифта.

Возвращаемся к addLine, в цикле в каждую ячейку помещаем символ, можно брать из заготовленных, мы будем генерировать случайную английскую букву или цифру.

Генерация происходит в this.getnerate, в качестве аргумента будем отправлять предыдущее значение, если нет то пустоту.
<code>
for (var i = 0; i < this.getCountColumns(); i++) {
     this._data[0][i] = this.getnerate(this._data[1] ? this._data[1][i] : null);
}
</code>
Будет 3 варианта генерации:
1) если нет предыдущего значение или оно уже полупрозрачно и случайное число от 0 до 10 будет 0, то мы генерируем новое значение
2) если есть предыдущее значение и оно еще не полностью прозрачно, то уменьшаем его прозрачность
3) будет пустота
<code>
if ((!old || old.a < 0.5) && GCF.random(0, 10) == 0) {
     return {a: 1, s: Math.random().toString(36).slice(2, 3)};  // Новое значение
} else if (old && old.a > 0) {
     old = GCF.clone(old);   // Скопируем их
     old.a -= 0.15;          // Уменьшим прозрачность на 0,15
     return old;
} else {
     return null;  // Пустота
}
</code>
Функция Math.random().toString(36) возвращает случайную строку, например: 0.dzfmnefffz4rfva6dv5lmobt9
GCF.clone(old); Клонирует объект. То есть изменив новый, старый останется неизменный.
a - прозрачность, s - символ.

Данные готовы, теперь отчистим старое, и отрисуем новый кадр.
<code>
this._ctx.clearRect(0, 0, this._box.width, this._box.height);  // Отчистим canvas

for (var i = 0; i < this._data.length; i++) {  // Перебор строк
     for (var c = 0; c < this._data[i].length; c++) {  // Перебор символов
          if (this._data[i][c]) { // Если не пустота
               this._ctx.fillStyle = 'rgba(34, 115, 227, ' + this._data[i][c].a + ')';  // Установим стиль на основе прозрачности символа
               this._ctx.fillText(this._data[i][c].s, c * 20, i * 20);  // Нарисуем сивол
          }
     }
}

// Обежим лишнее от массива
this._data = this._data.slice(0, 10);
</code>
Так как высота фиксирована в 10 строк, обрезаем лишнее, если это используется как фон, стоит сделать функцию по аналогии getCountColumns, которая будет возвращать количество строк. И тогда обрезать лишнее.

В качестве дополнения, можно сделать, что бы иногда проскакивали целые слова.
<ctx>demo,matrix/index2.html,,matrix/matrix2.js,matrix2.js</ctx> 
Отличие в том, что перед заполнением новой строки, на основе предыдущей, можно найти места куда влезут слова.
А при вставке нового символа, проверять нет ли уже на его месте слова.
Больше
Дата: 11 дек 2016 в 01:44
Наконец, на gusevcore.ru появилась возможность размещать изображения в блоге. Для этого написал небольшую штуку для загрузки картинок с предпросмотром.
<ctx>img,1</ctx>
План следующий:
1. Размещаем, но скрываем обычный input.
2. При клике на нашу кнопку, которую мы можем украсить, через JS кликаем на input.
3. Открывается диалог выбора файла.
4. После выбора файла по событию onchange выводим файл в base64 в img.
5. При клике на другую кнопку загрузить, отправляем файл ajax запросом.
6. Сохраняем.
<preview>

<code>
<input id="file" type="file" accept="image/jpeg,image/png" onchange="Imgs.select(this);" style="display: none;" />

<button onclick="Imgs.open();">Выбрать</button>
<button onclick="Imgs.load();">Загрузить</button>

<img id="preview" />
</code>
Благодаря атрибута accept у тега input в окне выбора файла будут отображаться файлы с заданным форматам. В нашем случае изображения формата: png, jpg.

<code>
var Imgs = {
     // При нажатии на кнопку выбрать
     open: function() {
          // Кликним по input, для открытия окна выбора
          document.getElementById('file').click();
     },

     // Вызовется при выбора файла по событию onchange у input
     select: function(button) {
          var 
               fileReader = new FileReader(), // Объект для чтения локальных файлов
               preview = document.getElementById('preview'); // Тег img для предпросмотра

          // После завершения локальной загрузке
          fileReader.onloadend = function() {
               // Поместим в img результат загрузки в base64
               preview.src = fileReader.result;
          };

          // Загрузим файл локально
          fileReader.readAsDataURL(button.files[0]);
     },

     // При нажатии на кнопку загрузить
     load: function() {
          var 
               formData = new FormData(), // Объект формы для отправки на сервер
               file = document.getElementById('file').files[0],  // Выбранный файл
               xhr = new XMLHttpRequest();  // Объект для обмена данными с сервером

          // Добавим наш файл в FormData
          formData.append(file.name, file);

          // Откроем канал
          xhr.open('POST', 'load.php', true);
          // Событие по завершению загрузки
          xhr.onload = function() { 
               console.log('Загружено!');
          };
          // Отправим файл на сервер
          xhr.send(formData); 
     }
}
</code>

Осталось сохранить
<code>
// Переберем все пришедшие файлы
foreach($_FILES as $file) {
     // Выполним перемещение из временного хранилища в нужное место
     if (move_uploaded_file($file['tmp_name'], 'images/' . $file['name'])) {
          echo 'Загружено: ' . $file['name'] . '<br>';
     }
}
</code>

Получилось кода больше текста, для этого на днях сделаю возможность прикреплять демо, что бы не переписывать весь код, а только интересные моменты.
Больше
Дата: 9 дек 2016 в 22:59
Добавлял в библиотеку GCF.js возможность работы с глобальным контекстом. Которая позволит подписывать элементы на так называемый объект, при смене значения в котором, элементы бы меняли значения в себе. Либо хранить информацию в стандартном виде, а в разных местах получать подогнанную под нужный формат. 
Так как сайты за частую динамические, а подписывать на нужное поле контекста хотелось через атрибуты в HTML, значит нужно уметь отслеживать изменение DOM и выполнять определенную функцию.

Речь пойдет о MutationObserver.
Он позволяет отслеживать, как появление, удаление новых узлов, так изменение атрибутов и текстовых блоков.
<preview>
<code>
var 
     MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
     Observer = new MutationObserver(function(mutations) {
          // вызывается при изменении
          // переберем все изменения
          mutations.forEach(function(mutation) {
               console.log(mutation);
          });
     });

Observer.observe(document.querySelector('body'), {
     childList: true,  // отслеживать изменение DOM
     subtree: true    // смотреть за всеми дочерними элементами
});
</code>
Для завершения наблюдения: Observer.disconnect();

В данном примере при появлении или удалении элемента в body выполнится console.log(mutation); выведя все изменения.

Observer.observe передается два аргумента, первый элемент, второй набор опций.
Возможные опции:
childList - Наблюдать за добавлением, удалением элементов или текстовых узлов.
attributes - Наблюдать за изменением атрибутов.
characterData - Наблюдать за изменением текстовых узлов.
subtree - Наблюдать за всеми дочерними элементами.
attributeOldValue - Возвращать предыдущее значение атрибута.
characterDataOldValue - Возвращать предыдущее значение Data атрибута.
attributeFilter - Массив с названиями атрибутов, за которыми наблюдать.

Обязательно необходимо передать один из: childList, attributes, characterData, иначе будет ошибка.
Если нет subtree, изменения будут отслеживаться непосредственно, у элемента переданного первым аргументом. При изменении атрибутов у дочерних или изменения узлов у дочерних, реакции не будет.
Больше
Дата: 18 сен 2016 в 21:19
Создадим бота для переправки сообщений между своими аккаунтами в Telegram и ВК. На самом деле этот бот уже существует, но в связи с выходом <a href="//gusevcore.ru/libs/GCBot" target="_blank">GCBot</a>, переведем его на платформу для ботов. 

Мы уже писали бота для Telegram для обратной связи с сайта, и процесс создания описан в статье <a href="//gusevcore.ru/blog/bot_Telegram_dlya_obratnoy_svyazi_s_sayta" target="_blank">Бот Telegram для обратной связи с сайта</a>. Сегодня разберем особенности бота для ВКонтакте и улучшим GCBot. Для Telegram создается по аналогии с предыдущей статьей.
<preview>

Создадим в ВК группу, у нас это будет <a href="https://vk.com/remailbot" target="_blank">Remailbot</a>. Включаем в настройках группы сообщения. Далее в раздел Работа с API. Кликаем создать ключ и разрешаем доступ к сообщениям. Копируем созданный токен и вставляем его в config.php в $tokenVK. Так же нам потребуется БД, заполняем $db_host, $db_user, $db_password, $db_name.

Вернемся в настройки группы, в разделе Работа с API есть вторая вкладка Callback API. 
Указываем путь до vk.php в Адрес Вашего сервера. Для подтверждения ВК отправит запрос на vk.php с данными: {"type":"confirmation","group_id":127086488}, а ниже указана строка, которую должен вернуть сервер. Копируем её и вставляем в vk.php в $confirmation_token. Так же укажем $key. В настройках на этой же вкладке есть под вкладка: типы событий, уберем все галочки и поставим только напротив: новое сообщение.

Сейчас если все правильно, то написав в сообщение группе, мы получим в ответ: Подробнее о GCBot можно найти на http://gusevcore.ru

План будет такой:
По команде /connect мы отправляем ссылку на профиль противоположной сети, сохраняем её в БД, как только с того профиля сделают аналогичную команду с ссылкой на профиль первой сети, мы создаем связь. Потом при переотправки боту сообщения, мы присылаем его в другую сеть.

Начнем с регистрации. Развиваем GCBot, делаем ему в конфиге поле before_command, эта команды будет вызываться перед основной. В неё будет отправляться название команды, которая определилась в getCommand, на основе текста. Из неё мы можем вернуть другую команду, которая в последствии вызовется.  Так же там реализуем обычную авторизацию, и регистрацию для новых пользователей. 

Пользователь есть, нужна команда связи /connect, определим её в commands.php в массиве $commands
<code>
public $commands = array(
     // Отмена
     'cancel'          => array('/cancel', 'cancel', 'отмена', '0'),
     // Помощь
     'help'            => array('/help', 'help', 'помощь', '1'),
     // Связать страницы
     'connect'         => array('/connect', 'connect', 'связь', '2')
);
</code>
Сама функция connect должна проверить если у пользователя связь. Если связь есть, то говорим, что связь установлена ранее. Если нет, то в переменную с пользователем запишет команду, которая выполнится при следующем сообщении, она должна сохранить логин, который прислал пользователь. А пользователю отправим, что бы он ввел логин от другой сети. 
<code>
$GCBot -> user['info']['next_command'] = 'setConnect';
</code>
Так же у нас есть команда /cancel, которая имеет больший приоритет над другими командами. Она стирает next_command.

Далее нам нужно запомнить все изменения, для этого нам бы не помешала функция наподобие before_command, только вызываемая после основной команды. Добавил в GCBot такую, так же указываем в конфиге after_command. В неё так же будет передаваться название основной команды.

После отправки логина, мы добавляем его во временную таблицу, а пользователю сообщаем, что бы он с другой сети отправил логин от текущей.
Как только в таблице появляются ссылающиеся друг на друга записи, образовываем связь.

Теперь пересылка, эта уже не команда. Значит указываем в конфигурации default_command = 'remail'

Сам remail, должен проверить, если связи нет, то вызвать команду help, либо переслать сообщение в другую сеть.
Так как исходные имеют разный формат, в tg.php и vk.php создадим по функции getMsg(), которая должна получить из $response пересылаемое сообщение выстроить нужный формат и вернуть обычный текст. Этот текст отправляется в другую сеть.

ВКонтакте считает команду успешной, если был ответ OK. Если ВК не получил положительный ответ, он повторит отправку этой команды позже.

По итогам переписания бота, GCBot обновился до 0.2.

Бот который мы создали, называется Remail, добавляйте его в <a href="https://vk.com/remailbot" target="_blank">ВК</a> и в <a href="https://telegram.me/Remail_bot" target="_blank">Telegram</a>.
Больше
Дата: 16 сен 2016 в 04:32
Сегодня, на нашем столе функция плавной прокрутки до определенного DOM элемента. Без всяких jQuery и прочей ерунды.

Для начала получим исходные данные:
<code>
var
     speed = 0.2, // скорость
     startScroll = window.pageYOffset, // начальная позиция, текущее положение сколла
     element = document.getElementById('test'), // DOM элемент.
     finishScroll = element.getBoundingClientRect().top, // положение элемента по Y относительно окна браузера
     start = null; // тут будем считать затраченное время
</code>
<preview>

Теперь нам нужна функция, которая будет обрабатывать каждый шаг плавной прокрутки. Объявим её ниже, сразу под исходными данными.
<code>
function step(time) {
     // в первый кадр запомним время старта
     if (start === null) {
          start = time;
     }
     var 
          progress = time - start,     // определить, сколько прошло времени с начала анимации
          nowScroll = null;              // текущее положение сколла

     // в зависимости от того двигаемся вверх или вниз, определим текущее положение сколла
     if (finishScroll < 0) {
          nowScroll = Math.max(startScroll - progress / speed, startScroll + finishScroll);
     } else {
          nowScroll = Math.min(startScroll + progress / speed, startScroll + finishScroll);
     }
     // прокрутим скролл
     window.scrollTo(0, nowScroll);
     // если прокрутка еще не окончена, повторим шаг
     if (nowScroll != startScroll + finishScroll) {
          requestAnimationFrame(step);     // запланировать отрисовку следующего кадра
     }
}

// Начнем плавную прокрутку
requestAnimationFrame(step);
</code>

requestAnimationFrame - планирует и выполняет перерисовку кадра. Выполняется синхронно относительно другой анимации. Скорость до 60 кадров в секунду.

Данная функция вошла в GCF.js версии 3.14, как <a href="/GCF/function/scroll.to" class="link">GCF.scroll.to</a>
Больше
Дата: 11 сен 2016 в 19:50
Для <a href="http://gusevgroup.ru" target="_blank">gusevgroup.ru</a> потребовалась форма обратной связи. 
E-mail'ы, очень не оперативны, если не жду, какое-нибудь письмо, могу в ящик по недели не заглядывать. А как загляну, требуется минимум 15 минут, для сортировки всей почты.

Как вариант хранить сообщения в БД на сайте, но здесь то же не совсем оперативно. Будем отправлять сообщения в мессенджер Telegram, из которого мы сразу сможем ответить. Отвечать будем на E-mail, так как предположим, что пользователь будет ждать ответа)
<preview>

Вытащил из своих ботов основу, назовем её GCBot. 
Нам потребуется несколько файлов:
GCBot.php - все логика и возможно популярные функции
config.php - конфигурация, данные от базы, команды по умолчанию и т.д.
commands.php - класс с командами
lang.php - будем там хранить сообщения на разных языках и для разных мессенджеров
tg.php - сюда будут приходить запросы от Telegram
vk.php - сюда будут приходить запросы от VK

Да, эта штука будет универсальная, а обновляться будет только GCBot.php, остальные пользовательские файлы, только в редких случаях.
Ниже рассмотрим создания бота только для Telegram, для VK потом, на другом примере.

Cо стороны Telegram, добавляем <a href="https://telegram.me/BotFather" target="_blank">@BotFather</a>, следуя его инструкции создаем бота.
В итоге он пришлет сообщения с токеном вида: 123465:xxxyyy
Копируем токен, вставляем в config.php в $tokenTG, но перед токеном написать "bot", должно получиться так:
<code>
public $tokenTG = 'bot123465:xxxyyy';
</code>

Далее, нам нужно указать на какой адрес обращаться боту. В итоговом сообщении будет ссылка на документацию <a href="https://core.telegram.org/bots/api" target="_blank">Bot API</a>, нам нужен метод setWebhook, на его Telegram будет отправлять запросы, когда кто-нибудь напишет боту сообщение.
В GCBot в качестве проверки безопасности будет ожидать GET параметр key, который мы указываем в tg.php, пускай он будет 987
Итоговая ссылка к Bot API будет следущего вида:
https://api.telegram.org/bot123456:xxxyyy/setWebhook?url=https://site.ru/bot/tg.php?key=987
В которой так же содержится наш токен с приставкой "bot".
После перехода по этой ссылки, должны получить такое сообщение:
{"ok":true,"result":true,"description":"Webhook was set"}

Первая задача:
Пользователь пишет сообщение в текстовом поле на сайте, так же указывает свое имя и E-mail, по кнопке отправить оно должно прийти в Telegram.
Перейдем сразу к формированию данных: на первой строке имя, на второй E-mail, а на 3 и ниже текст.
<code>
$msg = $params['name'] . "⁄n" . $params['email'] . "⁄n" . $params['text'];
</code>

Теперь нам нужна функция отправки сообщения, идем за ней в GCBot.php и копируем содержание функции sendTG
Так же нужен токен и идентификатор нашего чата с ботом. Пора добавиться к нашему боту, если все правильно сделано, на вашу команду /start, он должен ответить: 
More information about GCBot can be found on http://gusevcore.ru
Это выполнилась команда /help, которая в конфигурации указана по умолчанию.
Подправим команду /help, что бы узнать id беседы
<code>
public function help() {
     GLOBAL $GCBot;

     send('Chat id: ' . $GCBot -> user['id']);
}
</code>
Копируем пришедший нам id(для примера пусть будет 666), и соберем функцию воедино. 
<code>
$api_url = 'https://api.telegram.org/bot123456:xxxyyy/';
$sendto = $api_url . 'sendmessage?chat_id=666&text=' . urlencode($msg);
file_get_contents($sendto);
</code>
Отлично, сообщения приходят.

Вторая задача:
Нам необходим ответ на эти сообщения.
Отвечать мы будем через Reply, что бы бот знал, на какое сообщение мы отвечаем и мог достать из него E-mail.
Так как это будет не команда, а обычный текст, изменим команду по умолчанию в config.php на reply $default_command = 'reply';

В commands.php создаем публичную функцию reply. В этом же файле есть массив $commands, если это была бы команда, в него необходимо добавить ключи, по которым будет определяться функция.
Нам нужно добыть E-mail из сообщения на которое мы ответили.
В GLOBAL указываем $response, именно там хранится все что нам нужно, а точнее $response['message']['reply_to_message']['text'], разбиваем строку по переносам и берем вторую(1)
<code>
$email = explode("⁄n", $response['message']['reply_to_message']['text'])[1];
</code>
E-mail есть, текст нашего ответа пришел в первом(0) аргументе к функции reply, осталось отправить письмо.
<code>
public function reply($msg) {
     GLOBAL $response;

     $email = explode("⁄n", $response['message']['reply_to_message']['text'])[1];
     $subject = 'Ответ от GusevGroup';

     $headers  = "Content-type: text/html; charset=utf8 ⁄r⁄n"; 
     $headers .= "From: GusevGroup <andrey@gusevgroup.ru>⁄r⁄n"; 

     mail($email, $subject, $msg, $headers);
}
</code>
В $subject тема письма, в $headers, кодировка и отправитель.

Функция mail простая, но не надежная, к примеру нужно постараться, что бы mail.ru не банил подобные письма, по этому я использовал отправку писем через SMTP сервер. Это довольно громоздкая функция, которую нужно понять и приукрасить. О ней расскажу через пару дней.

Бот готов, остальное по вкусу.
Страница с документацией по GCBot в <a href="/libs/GCBot" class="link">библиотеках</a>.
Больше
Дата: 10 сен 2016 в 22:04
В связи обновлением <a href="http://gusevgroup.ru" targe="_blank">gusevgroup.ru</a> создаются новые решения, которыми хочу поделиться.

Сделал простенькую, но выполняющий свою функцию библиотеку для создания слайдера – <a href="/libs/GCSlider" class="link">GCSlider</a>
Работает на <a href="/GCF" class="link">GCF.js</a>

Пока доступен всего 1 эффект – сдвиг слайдов влево, в какой-нибудь дождливый вечер, по добавляю еще эффектов.

Код библиотеки довольно банальный, по этому не думаю, что стоит расписывать. В любом случае, в исходнике куча комментариев. 

На сайте появился новый раздел – <a href="/libs" class="link">Библиотеки</a>, в котором буду собираться подобные штуки.
Больше
Дата: 4 сен 2016 в 01:12
Осуществился переезд с временного сайта на этот.
Каждое последующее обновление будет отмечаться на вкладке "Обновление", на страницах продуктов.

<b>GusevCore:</b>
     Небольшие доработки в стандартных шаблонах.
     .htaccess - в маску для переадресации на index.php добавился /(слейт)
     Глобальная переменная $_URL - с ссылкой на запрошенную страницу
     Функции:
          <a href="/GusevCore/function/GC/replaceParams" class="link">replaceParams</a> - Заменяет параметры в коде
          <a href="/GusevCore/function/GC/initUserScripts" class="link">initUserScripts</a> - Инициализация пользовательских скриптов
     GCF:
          В функцию codeText, добавился тип baseToHtml
          Функции:
               <a href="/GusevCore/function/GCF/format" class="link">format</a> - Вставляет в строку параметры
     Конфигурация:
          $scripts - Пользовательские библиотеки.

<b>GCF.js:</b>
     По умолчанию инициализируются только прототипы. Для работы с полноэкранным режимом, необходимо вызвать <a href="GCF/function/devise.fs.init" class="link">GCF.devise.fs.init();</a>
     Прототипы:
          Удален прототип Object.prototype.forEach, появилась анологичная функция
          <a href="GCF/proto/Element.subscribe" class="link">Element.prototype.subscribe</a> - Подписка на события
          <a href="GCF/proto/Element.send" class="link">Element.prototype.send</a> - Отправка события
     Функции:
          <a href="GCF/function/clone" class="link">clone</a> - Клонирование данных
          <a href="GCF/function/forEach" class="link">forEach</a> - Перебор элементов объекта
          <a href="GCF/function/devise.media .init" class="link">devise.media.init</a> - Инициализирует работу с медиаустройствами
          <a href="GCF/function/devise.getVideo.init" class="link">devise.media.getVideo</a> - Получить трансляцию с камеры
Больше
Дата: 26 авг 2016 в 18:33
Все больше и больше появляется новых фото-приложений, значит нужно сделать еще одно.
О нем расскажу в следующей статье, а пока о том, как вывести изображение с камеры телефона к себе на сайт.

Важно! Работать с камерой можно только на сайте с защищенным протоколом https.
<preview>

Для начало разберемся с кросс-браузерностью.
Нам понадобится функция для получения списка медиа устройств.
<code>
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
     navigator.enumerateDevices = function(callback) {
          navigator.mediaDevices.enumerateDevices().then(callback);
     };
}

if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources) {
     navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack);
}
</code>
А так же для получение самой трансляции с камеры
<code>
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
</code>
В GCF.js в версии 3.12 от 25.08.2016, код выше выполнит функция <a href="GCF/function/devise.media .init" class="link" target="_blank">GCF.device.media.init</a> 

Теперь получим список медиа устройств девайса и сохраним id только видео входов
<code>
var camers = [];
navigator.enumerateDevices(function(devices) {
     devices.forEach(function(device) {
          if (device.kind == 'videoinput') {
               camers.push(device.deviceId);
          }
     });
});
</code>

Получим трансляцию с камеры по id
<code>
camera = document.getElementById('camera');
function getVideo(id) {
     if (window.stream) {
          camera.src = null;
          window.stream.getTracks()[0].stop();
     }
     if (navigator.getUserMedia) {    
          navigator.getUserMedia({
               video: {
                    optional: [{
                         sourceId: id
                    }],
                    deviceId: id
               }
          }, handleVideo, handleError);
     }
}
function handleVideo(stream) {
     window.stream = stream;
     camera.src = window.URL.createObjectURL(stream);
}
function handleError(){}
</code>
В camera храним DOM элемент с тегом video, в нем и будет отображаться видео с камеры
navigator.getUserMedia - получит трансляцию и передаст её в handleVideo, в случае ошибки, вызовется handleError
Отдельное внимание стоит обратить на аргументы для getUserMedia, deviceId по идее должно быть достаточно, в отличие от остальных браузеров, Chrome требовал не понятно чего, при этом отдавал трансляцию только с одной камеры, причем не обращая внимание на переданный id. Перепробовав множество спецификаций, выяснялось, что ему нужно следующее: optional: [{sourceId: id}]
Совмещенная функция getVideo и handleVideo вошли в GCF.js 3.12, как <a href="GCF/function/devise.getVideo.init" class="link">GCF.device.media.getVideo</a>

Ну и напоследок съемка фото, а точнее кадра с трансляции
<code>
function photo() {
     var
          canvas = document.getElementById('photo'),
          ctx = canvas.getContext('2d');
     ctx.drawImage(camera, 0, 0, camera.videoWidth, camera.videoHeight);
}
</code>
Передадим в первом аргументе к drawImage DOM элемент video с трансляцией.
0, 0 - помещаем в верхний левый угол
camera.videoWidth, camera.videoHeight - ширина и высота видео, для сохранения пропорций.
Больше
Дата: 23 авг 2016 в 21:31
Привет, меня зовут Андрей Гусев.
Достаточно долго подходил к идеи завести технический блог, даже пару лет назад решил довериться WordPress, но наши с ним пути разошлись. Думаю сейчас подходящее время.

Этот блог еще сырой, т.к. сайту и 3 дней нет, но со временем превратим его в космический корабль.

О чем пойдет речь..
Программирую, много, очень много. Пишу на JS, CSS, HTML, PHP, SQL. Python, Delphi, Java, как в добавок, но на них очень редко. В последнее время, много занимаюсь графикой, провожу эксперименты с дополнительной реальностью, и как следствие JS API девайсов, работа с камерой, гео-локация, акселерометры, и другие фичи смартфонов.
Так что речь может идти и о паре строчек кода, и о этапах разработки крупных проектов. По плану есть одна игра, как говорится, серваки могут не выдержать)

Если о менее абстрактном, то в ближайшие дни буду писать визуальный HTML редактор для блога. Так же в очереди на половину готовая библиотека для рисования графиков. Делаю пару ботов для Telegram и ВК, скорее всего тоже превратиться в библиотеку, об этом тоже пару слов расскажу.

Соседом этого блога, является платформа GusevCore, все сайты делаю исключительно на ней. История её довольно большая, первое упоминание о ней фигурирует 2 года назад, а первые функции появились задолго до платформы около 7 лет назад, побывали в сотнях проектах и успешно дошли до наших дней в GusevCore 3.12. Можно сказать, с этих функций и началась платформа. У GCF.js примерно такая же история.

Не давно к нам добавился GCOnline. 
В связи с проектами, где нужно было скачивать и обрабатывать очень большие объемы данных, а стандартный cron планировщик не давал возможность управлять очередью, пришлось написать свой.

Для связи скоро сделаю Telegram бота, а пока пишите:
Telegram: <a href="https://telegram.me/Gusev" target="_blank">@Gusev</a>
ВК: <a href="https://vk.me/ckorpion" target="_blank">Андрей Гусев</a>
Больше