Как организовать хранилище музыкальных групп в Google Таблицах с поддержкой с разных программ?


Дано: Аккаунты в Google, VK, Telegram.
Задача: Создать хранилище музыкальных групп с возможностью удобного пополнения.
Инструменты: Google Apps Script (GAS), VK API, Tampermonkey, Telegram Bot API.

Создание Google Таблицы


Создаём новую Google Таблицу.

Делаем закреплённый заголовок (потом все циклы перебора ячеек из-за этого будут начинаться не с 0, а с 1).

В принципе наш шаблон готов.

Единственное, я не сделал привязку к колонкам через имена (не хотел использовать циклы), так что на протяжении всего скрипта буду обращаться к жёстко зашитому номеру колонки.

Жмём «Инструменты — Редактор скриптов».



Создаём 2 скрипта: Код.gs и Бот.gs.

Код.gs
access_token = '1111111111' // VK token
ss = SpreadsheetApp.getActiveSpreadsheet() // Таблица
sheet = ss.getSheets()[getNumSheet('Группы')] // Лист
data = sheet.getDataRange().getValues() // Все ячейки
numRows = sheet.getLastRow()+1 // Последняя строка
faveTag = 'муз.группы' // Тег закладок в ВК

// Принимаем входящие get запросы
function doGet(e) {
try {
    var link = e.parameters['link']
    /*
      A - Название
      B - English
      C - Ссылка
      D - Город
      E - Жанр
      F - Описание
      G - Примечание
    */
    var n = searchExists(link)
    if (n != false) return ContentService.createTextOutput('Группа '+n+' уже есть!')
    // Запрос в API VK https://vk.com/dev/groups.getById
    var html = UrlFetchApp.fetch('https://api.vk.com/method/groups.getById.json?group_id='+link+'&fields=city,description&access_token='+access_token+'&v=5.107')
    // Парсим в json первый элемент response
    var json = JSON.parse(html).response[0]
    addInTableFromArray(json, link)
    sort()
    return ContentService.createTextOutput('Добавлено')
  } catch (e) {
     return ContentService.createTextOutput('Ошибка из гуглотаблиц doGet! '+e)
  }
}

// Поиск стиля/жанра группы из описания по регулярке
function searchGenre(txt) {
  var t = txt.match(/(?:(?:стил[ь|я|ю|ем|е]|genre|жанр[а|у|ом|е]?)[\s:-]+){1}([a-zа-я\/-]+)/i)
  if(txt != '' && t) return t[1]
}

// Поиск русских букв в названии
function isRus(txt) {
  return txt.search(/[^A-Za-z0-9\/.:_]+/i)
}

// Сортировка
function sort(){
  // Диапазон сортируемых ячеек от левой верхней до правой нижней
  var tableRange = "A2:G"+numRows
  var editedCell = sheet.getActiveCell()
  var range = sheet.getRange(tableRange)
  // Порядок сортировки по номеру столбца. Начинается не с 0, а с 1
  range.sort([{ 
    column : 4,
    ascending: true
  },{
    column: 1,
    ascending: true
  },{
    column: 2,
    ascending: true
  }])
}

// Поиск существующих групп
function searchExists(t) {
  for (var i = 1; i < data.length; i++) {
    if (t == data[i][2] || 'club'+t == data[i][2]){
      return data[i][0]+data[i][1]
    }
  }
  return false
}

// Подтягивание данных массово если их нет
function addInfo(isBot = false) {
  var arr = []
  var j = 0
  var part = 1000 // Кол-во символов, на которое будет делиться блок текста. Из-за ограничений в адресной строке и UrlFetchApp.fetch
  arr[j] = new Array()
  for (var i = 1; i < data.length; i++) {
    var txt = data[i][2].replace('https://vk.com/','').replace('vk.com/','').replace('^club','')
    if (txt != '' && data[i][0] == '' && data[i][1] == '' && data[i][3] == '' && data[i][4] == '' && data[i][5] == '' && data[i][6] == ''){
      arr[j].push(txt)
      if (arr[j].toString().length > part){
        j++
        arr[j] = new Array()
      }
    }
  }
  if (arr[0].length == 0){
    if (isBot) return false
    else{
      SpreadsheetApp.getUi().alert('Нет групп, которые нужно дополнить')
      return false
    }
  }
  // Цикл по ссылкам
  for (var t = 0; t < Math.ceil(arr.toString().length/part); t++) {
    var html = UrlFetchApp.fetch('https://api.vk.com/method/groups.getById.json?group_ids='+arr[t].toString()+'&fields=city,description&access_token='+access_token+'&v=5.107')
    var json = JSON.parse(html).response
    if(json){
      for (var i = 0; i < json.length; i++) {
        var id = json[i].id
        var link = json[i].screen_name
        var name = json[i].name
        var description = (json[i].description)?json[i].description:''
        var city = (json[i].city)?json[i].city.title:''
        var rus = (isRus(name) != -1)?"A":"B"    
        // Скрипт можно попробовать заменить на двумерный массив к arr
        for (var j = 1; j < data.length; j++) {
          var nameCell = data[j][2].replace('https://vk.com/','').replace('vk.com/','').replace('^club','')
          if (nameCell == link || nameCell == id){
            var num = j+1
            break
          }
        }
        sheet.getRange(rus+num).setValue(name.replace('=',''))
        sheet.getRange("C"+num).setValue('=HYPERLINK("https://vk.com/'+link+'";"'+link+'")')
        sheet.getRange("D"+num).setValue(city)
        sheet.getRange("E"+num).setValue(searchGenre(description))
        sheet.getRange("F"+num).setValue(description)
      }
    }
  }
  sort()
  return true
}

// Втянуть группы из закладок ВК
function getVkFave(isBot = false) {
  var idTag
  var getTags = UrlFetchApp.fetch('https://api.vk.com/method/fave.getTags.json?access_token='+access_token+'&v=5.107')
  var res = JSON.parse(getTags).response
  var iTag = res.items
  for (var i = 0; i < iTag.length; i++) {
    if (iTag[i].name == faveTag) idTag = iTag[i].id
  }
  
  // Предупреждение
  if (!isBot){
    var ui = SpreadsheetApp.getUi()
    var resp = ui.alert('После втягивания закладок с тегом "'+faveTag+'" они будут удалены из ВК. Продолжаем?', ui.ButtonSet.YES_NO)
  }
  var inside = function (){
    var getPages = UrlFetchApp.fetch('https://api.vk.com/method/fave.getPages.json?tag_id='+idTag+'&fields=city,description&access_token='+access_token+'&v=5.107')
    var iPage = JSON.parse(getPages).response.items
    // Цикл по каждой закладке. Добавление в таблицу, удаление с ВК
    for (var j = 0; j < iPage.length; j++) {
      var gr = iPage[j].group
      addInTableFromArray(gr)
      numRows++
        UrlFetchApp.fetch('https://api.vk.com/method/fave.removePage?group_id='+gr.id+'&access_token='+access_token+'&v=5.107')
        Utilities.sleep(1000) // костыль
    }
    sort()
  }
  if (isBot) { // Костыль, потому что бот стопарится на ui
    inside()
  }else if(resp == ui.Button.YES) {
    inside()
  }
}

// Добавление строки в таблицу
function addInTableFromArray(arr, linkIn) {
  if (linkIn){
    var link = linkIn
  }else{
    var link = arr.screen_name
    if (searchExists(link) != false) return false
  }
  var name = arr.name // Название
  var description = (arr.description)?arr.description:'' //Описание
  var city = (arr.city)?arr.city.title:'' // Город
  // Если в тексте нет русских букв, значит пишем название в колонку "English"
  var rus = (isRus(name) != -1)?"A":"B"
  // Добавление строки в таблицу
  sheet.getRange(rus+numRows).setValue(name.replace('=',''))
  sheet.getRange("C"+numRows).setValue('=HYPERLINK("https://vk.com/'+link+'";"'+link+'")')
  sheet.getRange("D"+numRows).setValue(city)
  sheet.getRange("E"+numRows).setValue(searchGenre(description))
  sheet.getRange("F"+numRows).setValue(description)
}
// Актуализировать ссылки. Единоразово.
function checkActualLink(){
  for (var i = 1; i < data.length; i++) {
    try {
      var num = i+1
      UrlFetchApp.fetch("https://vk.com/"+data[i][2])
      sheet.getRange('C'+num).setBackgroundColor('')
    }catch(err) {
      var num = i+1
      sheet.getRange('C'+num).setBackgroundColor('red')
    }
  }
}
// Статус URL в строке. Запуск добавлением кода в ячейку
function getStatusUrl(url){
   var options = {
     'muteHttpExceptions': true,
     'followRedirects': false
   }
   var response = UrlFetchApp.fetch(url.trim(), options)
   return response.getResponseCode()
}
//Поиск номера листа по имени
function getNumSheet(nameList){
  var s = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(nameList)
  if (s != null) {
    return s.getIndex()-1
  }else{
    return false
  }
}
//Удалить спецсимволы
function escapeHtml(text) {
  return text
      .replace(/&/g, "")
      .replace(/</g, "")
      .replace(/>/g, "");
}
// Меню
function onOpen() {
  SpreadsheetApp.getUi()
  .createMenu('Меню специальное')
  .addItem('Сортировка', 'sort')
  .addItem('Втянуть закладки из ВК', 'getVkFave')
  .addItem('Дозаполнить пустые', 'addInfo')
  .addToUi();
}

// Краткий вызов логирования
function ll(t){
  return Logger.log(t)
}


Бот.gs
var botApi = 'https://api.telegram.org/bot1123123:AAA/' // Токен Telegram бота 
/* Команды:
 /start - Начало работы бота
 /sort - Сортировка
 /getvkfave - Втянуть закладки из ВК 
 /addinfo - Дозаполнить пустые
 /getall - Получить все группы
*/
function doPost(e){
  var inp = JSON.parse(e.postData.contents)
  // Логи
  var sheet2 = ss.getSheets()[getNumSheet('Логи')]
  sheet2.getRange('A1').setValue(inp)
  
  var inpTxt = inp.message.text
  var chatId = inp.message.chat.id
  
  var link = inpTxt.replace('https://vk.com/','').replace('vk.com/','').replace('^club','')
  // Команды бота
  // start
  if (inpTxt == '/start' || inpTxt == 'В начало'){
    sendText(chatId, 'Бот заносит группы из ВК в Google таблицу.\n'+
                    '/start - Начало работы бота\n'+
                    '/sort - Сортировка\n'+
                    '/getvkfave - Втянуть закладки из ВК\n'+
                    '/addinfo - Дозаполнить пустые\n'+
                    '/getall - Получить все группы')
    return true
  }
  // sort
  if (inpTxt == '/sort' || inpTxt == 'Сортировка'){
    sort()
    sendText(chatId, 'Отсортировано')
    return true
  }
  // getvkfave
  if (inpTxt == '/getvkfave' || inpTxt == 'Втянуть закладки из ВК'){
    getVkFave(true)
    sendText(chatId, 'Втянуто')
    return true
  }
  // addinfo
  if (inpTxt == '/addinfo' || inpTxt == 'Дозаполнить пустые'){
    if (addInfo(true)) sendText(chatId, 'Дозаполнено')
    else sendText(chatId, 'Нет групп, которые нужно дополнить')
    return true
  }
  // getall
  if (inpTxt == '/getall' || inpTxt == 'Получить все группы'){
    var arr = [[],[],[],[],[],[],[],[],[],[],['В начало']] // TODO доделать
    var repeat = ''
    var n = 0
    var j = 0
    for (var i = 1; i < data.length; i++) {
      if (j == 4){ // Количество на одной строке
        n++
        j = 0
      }
      if (repeat != data[i][3]){
        arr[n][j] = data[i][3]
        if (data[i][3] == '') arr[n][j] = '---'
        j++
      }
      repeat = data[i][3]
    }
    var key = JSON.stringify({keyboard: 
                              arr,
                              resize_keyboard:true,
                              one_time_keyboard:true
                            });
    sendText(chatId, 'Выберите город', key)
    return true
  }
  
  // Город
  if (isRus(link) != -1) {
    var name = (inpTxt == '---')?'Не заполнен':inpTxt
    for (var j = 1; j < data.length; j++) {
      if (data[j][3] == inpTxt || inpTxt == '---') {
        var str = 'Город '+name+'\n\n'
        for (var i = 1; i < data.length; i++) {
          if ((data[j][3] == data[i][3]) || (data[i][3] == '' && inpTxt == '---')) {
            if (str.length >= 4000) { // Ограничение на кол-во символов в сообщении Telegram бота
              sendText(chatId, str)
              str = ''
            }
            str = str + '<a href="https://vk.com/' + data[i][2] + '">' + escapeHtml(data[i][0] + data[i][1]) + '</a> \n'
          }
        }
        if (str != '') sendText(chatId, str)
        return true
      }
    }
    sendText(chatId, inpTxt + ' - это не муз.группа и не команда бота')
    return true
  }
  // Проверка на валидность
  if (getStatusUrl('https://vk.com/'+link) != 200 && getStatusUrl('https://vk.com/club'+link) != 200){
    sendText(chatId, inpTxt+' - битая ссылка')
    return true
  }
  
  var name = searchExists(link)
  if (name != false){
    sendText(chatId, 'Группа '+name+' уже есть')
    return true
  }else{
    var html = UrlFetchApp.fetch('https://api.vk.com/method/groups.getById.json?group_id='+link+'&fields=city,description&access_token='+access_token+'&v=5.103')
    var json = JSON.parse(html).response[0]
    addInTableFromArray(json, link)
    sort()
    sendText(chatId, 'Группа '+json.name+' добавлена')
    return true
  }
}
//Отправить сообшение боту
function sendText(chatId, text, key = ''){
  var payload = {
    'method': 'sendMessage',
    'chat_id': String(chatId),
    'text': text,
    'parse_mode': 'HTML',
    'reply_markup': key,
    'disable_web_page_preview': true,
    'one_time_keyboard':true
  }     
  var data = {
    "method": "post",
    "payload": payload
  }
  // для дебага комментируем UrlFetchApp.fetch и вставляем ll(text)
  UrlFetchApp.fetch(botApi, data)
}


Подробнее разберём ниже.

Жмём "Опубликовать — Развернуть, как веб приложение".



Теперь у нас есть ссылка вида script.google.com/macros/s/AAA/exec, подставив к которой параметр «link», мы сможем занести группу ВК в Google Таблицу.

Юзерскрипты в браузере


Первый и основной способ занесения данных!

Открываем браузер Google Chrome или Mozilla Firefox и ставим плагин юзерскриптов Tampermonkey.

Ссылки для Хрома, для Мозиллы.

Изначально пользовался Greasemonkey, но пришлось перейти на Tampermonkey из-за кроссбраузерности.

У кого возникнут вопросы, почему параметры юзерскрипта начинаются с префикса GM_, а не TM_ — идея не моя, идём в документацию.


Вставляем скрипт vkGroupToGS, нажав на первую вкладку с плюсиком.

vkGroupToGS
// ==UserScript==
// @name         vkGroupToGS
// @namespace    https://vk.com/*
// @version      0.1
// @author       You
// @match        https://vk.com/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==
    var url_first = 'https://script.google.com/macros/s/AAA/exec'
    var url_short = document.location.href.replace("https://vk.com/", "")
    var d = document.createElement('div')
    var head = document.querySelector('.left_menu_nav_wrap')
    d.setAttribute('id', 'send_group')
    d.style.display = 'inline-block'
    d.style.position = 'relative'
    d.style.fontSize = '50pt'
    d.style.cursor = 'pointer'
    d.innerHTML = '+'
    head.parentNode.appendChild(d)
    d.onclick = function() {
    setTimeout(function() {
        GM_xmlhttpRequest({
            method: 'GET',
            url: url_first + '?link=' + url_short,
            headers: {
                'Accept': 'application/atom+xml,application/xml,text/xml'
            },
            onload: function(x) {
                console.log(x.responseText)
                if (/Ошибка/.test(x.responseText)) document.querySelector('#send_group').style.color = 'red'
                else document.querySelector('#send_group').style.color = 'green'
            }
        })
    }, 0)
};


«Файл — Сохранить».


Как видим, наш скрипт рисует плюсик (на что хватило фантазии) в левом блоке left_menu_nav_wrap.


По нажатию на плюсик выполняется GM_xmlhttpRequest из vkGroupToGS в наш скрипт Код.gs по ссылке Google Таблицы, приложенной выше (url_first).

Tampermonkey запросит у нас связать эти 2 скрипта правами, жмём либо "Всегда разрешать" либо "Всегда разрешать домену" для "script.google.com". Повторить при надобности для "script.googleusercontent.com".


Как только запрос успешно улетит, придёт колбэк из GM_xmlhttpRequest и окрасит нашу кнопку в зелёный цвет ? при удачном добавлении либо в красный ? если вернулась ошибка.

Плюс Tampermonkey — облачное хранение скриптов в Google (раньше не было) при наличии их плодовитости и своего воображения.

Вернёмся к Google Таблице.

Перед тем, как начать разбирать наш Код.gs я хочу поделиться документацией для разработки в ВК — vk.com/dev

Токен ВК


Получение ключа доступа
Выполнение запросов к API ВКонтакте

Пока научился только одним способом добывать токен — через самописные приложения.

Заходим сюда.
Платформа — "Standalone-приложение".
После создания всё, что нам надо — это ID приложения (ныне семизначный), пусть будет 1234567.
Но нам нужен токен.
Очень хорошо про токен написано в Знакомстве с API ВКонтакте.

Если не хотим читать, просто открываем ссылку, вставляя в client_id свой ID приложения.

Нас редиректит на страничку с надписью:
Пожалуйста, не копируйте данные из адресной строки для сторонних сайтов. Таким образом Вы можете потерять доступ к Вашему аккаунту.

И это верно, будьте очень аккуратны с токеном!

Токен копируем из адресной строки после access_token.

Сразу поделюсь, что с этим токеном можно запросы вида vk.com/dev/groups.search?params%5Bq%5D=%D0%BC%D1%83%D0%B7%D1%8B%D0%BA%D0%B0&params%5Btype%5D=page&params%5Bcountry_id%5D=1&params%5Bcity_id%5D=1&params%5Bfuture%5D=0&params%5Bmarket%5D=0&params%5Boffset%5D=0&params%5Bcount%5D=10&params%5Bv%5D=5.107

можно спокойно преобразовывать в ссылки api.vk.com/method/groups.search?q=%D0%BC%D1%83%D0%B7%D1%8B%D0%BA%D0%B0&type=page&country_id=1&city_id=1&future=0&market=0&offset=0&count=10&access_token=1111111111111&v=5.107 и получать информацию в любом приложении.



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

Конечно с определёнными ограничениями, допустим не больше 999 сущностей в ответе или 20 запросов в секунду и т.п.

Также кому-нибудь будет интересно поиграться с хранимками — в нашем приложении вкладка "Хранимые процедуры".

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

Следим за версиями!

С их изменениями может меняться функциональность методов. Ныне прикладываю примеры для версии 5.107.

Итак, токен у нас есть, переходим к…

Код.gs

doGet(e) — Принимаем входящие get запросы от бота, ВК и просто с браузера.
Входные параметры парсятся, как e.parameters['ВХОДНОЙ_ПАРАМЕТР']
Прилетает ссылка, проверяем, есть ли в таблице. Если нет, запрашиваем подробную информацию через groups.getById, парсим, добавляем.

! Внимание! Скрипт простой, так что будет проглатывать все прилетающие ссылки, так что будьте аккуратны даже введя 123 при проверке скрипта.

searchGenre — умный поиск стиля/жанра группы, из-за которого я и поставил метку «регулярные выражения» в статье.

Возможны улучшения.

Пока работает только на поиск последующей цепочки слов через слеш или дефис после слов "стиль или жанр или genre".

isRus — простая регулярка, исключающая русские буквы.
sort — сортировка. Поставил по умолчанию — 1. город, 2. название.
searchExists — простой поиск существующих групп через цикл.
addInfo — сделал скрипт для себя, оставил здесь, как бонус. Нужен если у вас есть список ссылок, но нет по ним данных.

Вставляем список в колонку «Ссылка», жмём "Дозаполнить пустые" в главном меню и остальные колонки дозаполняются из ВК.

Из-за ограничений на количество символов в UrlFetchApp.fetch и в количестве ссылок, помещаемый в адресную строку пришлось сделать цикл по 1000 символов.

getVkFave — ещё один бонус, возникший в голове во время написания статьи и которым я сам стал пользоваться.

Суть — заходим с телефона в приложение ВК, натыкаемся на группу, которую хотим сохранить, сохраняем её в закладках с заранее добавленной меткой "муз.группы" (глобальная переменная faveTag).

Как они накопятся, заходим в нашу Google Таблицу, вызываем меню, жмём "Втянуть закладки из ВК".

Скрипт с токеном лезет в api.vk.com/method/fave.getTags, парсит, добавляет в таблицу, предварительно предупредив об удалении из ВК (тут аккуратней с тестированием, всё удаляется подчистую с закладок с этим тегом).

getStatusUrl и checkActualLink — эксперимент, вылившийся в 2 скрипта.
Суть — проверять валидность и доступ до ссылок.

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

Кто будет пользоваться моей удобняшкой, советую некое TODO, которое будет не просто удалять невалидные ссылки, но и проверять через groups.search по названию наличие в ВК.

checkActualLink — единоразово проверяет валидность, окрашивая в красный невалидные.

  • Минус — низкая скорость.
  • Плюс — выполнять можно, когда захочется.

getStatusUrl — вызывается в ячейке через скрипт

=getStatusUrl(CONCAT("https://vk.com/";C2))

Основу взял отсюда.

  • Минус — запускается для всех ячеек при каждом открытии страницы.
  • Плюс — отрабатывает параллельно для всех ячеек, то есть скорость в разы выше, чем у checkActualLink.

Далее идут удобняшки для гуглоскриптов:

addInTableFromArray — Добавление строки в таблицу. Либо с цикла либо единоразово.
getNumSheet — Поиск номера листа по имени. Честно, не нашёл в интернете способа проще, чем этот. Нужен из-за смещения номеров листков при добавлении новых. Основу взял отсюда.
escapeHtml — Удалить спецсимволы
ll — для себя сделал Logger.log кратким, сокращает время, советую.
onOpen — Всем знакомая функция для отрисовки меню в Google Таблице.

Итак, наш Код.gs ловит ссылки с ВК через Tampermonkey и добавляет группы в Google Таблицу.

Включаем лень


  • Мы сидим за компьютером.
  • Мы сидим в телефоне.

С компьютера мы научились добавлять группы через "плюсик" в ВК и через закладки.
Через телефон только через закладки в приложении ВК.

Разберём ещё два варианта внесения групп.

USI


Юзерскрипты в мобильном браузере.

Кому-то он покажется старомодным и ненужным, но найдутся те люди, которые вынесут плюсы из него.

Поискав в интернете нашёл пока только такой плагин.
Работает только в Firefox (тестировал только в android).

Огромный плюс — можно использовать скрипт Tampermonkey.

Из-за отличий вёрстки между vk.com и m.vk.com, а также из-за открывания окна по умолчанию в m.vk.com я не стал рисовать плюсик, а сделал простой скрипт VK_event_to_list_mvk, срабатывающий при открытии окна (гореть мне в аду за setTimeout, знаю).

Код.gs
// ==UserScript==
// @name         VK_event_to_list_mvk
// @namespace    https://m.vk.com/*
// @match        https://m.vk.com/*
// @grant    GM_xmlhttpRequest
// ==/UserScript==
setTimeout(function() {
    var url_first = 'https://script.google.com/macros/s/111/exec';
    var url_short = document.location.href.replace("https://m.vk.com/", "");
    GM_xmlhttpRequest({
        method: 'GET',
        url: url_first + '?link=' + url_short,
        headers: {
            'Accept': 'application/atom+xml,application/xml,text/xml'
        },
        onload: function(x) {
            alert(x.responseText);
        }
    });
}, 2000);



Telegram Бот


Создаём бота по инструкции.
Нам нужен токен, пусть он будет 123123.

Создаём скрипт Бот.gs, вставляем в переменную botApi наш 123123, обрамляя кавычками и ссылкой api.telegram.org.

Далее надо сцепить нашего бота с Google Таблицей. Посмотреть, как это сделать через WebHook можно в предыдущей статье.

Что будет делать наш бот?

Разложим по командам:

  • /start — Начало работы бота
  • /sort — Сортировка
  • /getvkfave — Втянуть закладки из ВК
  • /addinfo — Дозаполнить пустые
  • /getall — Получить все группы

Видим, что у нас есть начало работы бота, туда мы вставим просто tutorial с описательной частью на ваше усмотрение.

Три команды из главного меню на всякий случай.

И очередной бонус — /getall, о нём ниже.

Команды для удобного вызова (в строке ввода сообщения написать слеш) можно добавить в том же @BotFather через /setcommands.

Формат — строки через тире:

start - Вернуться в начало
sort - Сортировка
getvkfave - Втянуть закладки из ВК
addinfo - Дозаполнить пустые
getall - Получить все группы


Бот.gs

doPost — принимает входящие сообщения от бота.

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

Поэтому после того, как мы распарсили входящее сообщение через JSON.parse(e.postData.contents), сразу пишем в логи.

Далее определяем, какая из команд нам пришла.

Если не команда, проверяем на валидность, то есть существует ли группа в ВК.

sendText — отправляет сообщения боту (по умолчанию parse_mode = HTML), документация.

/getall — разберём отдельно.

Пусть вас не пугает моё решение из

[[],[],[],[],[],[],[],[],[],[],['В начало']]

Сделано для заполнения строчек городами, пока ограничимся 8-10 строчками по 4 города (переменная j = 4).

Если музыкальная группа без города, вставляем ---.

Просто выбираем город и ловим русские буквы в Бот.gs.

Выводим список ссылок, ничего более.


Помним, что доступ к документации и тестированию бота через адресную строку через Hotspot Shield Free VPN Proxy — Unlimited VPN (для Google Chrome).

Вот мы и изучили три способа внесения в нашу таблицу:

  • Юзерскрипты в браузере
  • Юзерскрипты в мобильном браузере (USI)
  • Telegram Бот

Что нужно знать и помнить?


Префикс club в ссылках ВК

В группах ВК есть 2 типа хранения идентификатора.

  1. Название на английском с допустимыми подчёркиваниями и дефисами.
  2. ID

Причём выкладываться могут и так и так.

Проверить просто.

Если есть группа, допустим vk.com/4soulsband, подставляем 4soulsband в group_ids в vk.com/dev/groups.getById, выводится перечень параметров, один из которых ID. И если мы перед ним поставим club, вуаля, попадаем на ту же страницу vk.com/club68130764.

Ограничение на хранение групп в ВК до 5000

Кажется смешным, но мне не хватило.

Моя боль с обновлением страницы ВК

Каюсь, пока не смог победить отлов перехода между страницами в ВК для сброса состояния плюсика в Tampermonkey. TODO

Использование Google Таблиц, как БД

  • Плюс — доступность
  • Минус — когда копится больше 5000 строк, работа с "БД" замедляется. Так что для серьёзных проектов лучше использовать нормальные СУБД.

Определение города группы

Адрес группы в ВК может быть тут vk.com/dev/groups.getAddresses, соответственно кто захочет улучшить поиск города для группы, может использовать его.

Итог


У нас есть:

  1. Google таблица для сбора данных, с которой мы научились работать.
  2. Знания по VK API. То есть вы уже в курсе, что мы можем не ограничиваться музыкальными группами.
  3. Telegram Бот с БД, которую не нужно разворачивать и настраивать.

Моя цель не кичиться очередной созданной игрушкой, моя цель показать новичкам (и не только) огромный пласт открытых API`шек, которые помогут облегчить жизнь в той или иной степени, перенестись с железной земли в «облака», так сказать.

Данный функционал готов, как и открыт для улучшений.

Удачи в разработке и постижении новых знаний.

Документация


Google Apps Script — Spreadsheet Service
Telegram Bot API
VK API
Учимся писать userscript'ы
Userscripts. Углубляемся
Мониторинг сайта с помощью Google Docs
Баловство. Пишем Telegram бота на Google script
[Примеры, Google Apps Script] Разработка дополнений/скриптов для Google Таблиц (spreadsheets)
How to use google spreadsheets to check for broken links