В этом уроке у нас появляется работающий бейби бот, который умеет повторять голоса @academy и/или других аккаунтов.
Предыдущие посты
Предисловие
- Обязательно задавайте вопросы в комментариях, если я что-то непонятно объясняю!
- Требуется базовый уровень понимания JavaScript, веб технологий и командной строки.
- У меня минимальный опыт работы с русскоязычной терминологией в программировании, поэтому названия я буду оставлять на английском языке.
- В этом уроке используется неоптимальный код, паттерны и структура, в приоритете находится простота и читаемость кода.
На чем мы остановились
В прошлый раз мы закончили разработку нашего бейби бота на том, что научились определять операции голосования за посты и обрабатывать их. Прежде, чем мы приступим к следующей части нам необходимо рассмотреть вопрос с ключами аккаунтов.
Некоторые важные факты о ключах
- любой аккаунт Голоса управляется криптографическими ключами
- есть 4 типа ключей: постинг, активный, собственника и мемо
- публичные ключи начинаются на
GLS
и видны всем в блокчейне - приватные ключи начинаются на
5
- свой приватный ключ можно найти по ссылке https://golos.id/@ТВОЙ_АККАУНТ/permissions
- постинг ключ авторизирует только следующие операции: голос, пост, комментарий, реблог
- активный ключ авторизирует финансовые операции с токенами, голосование за делегатов и смену аватарки :D
- как для бота, так и для сайта golos.io используйте только постинг ключ
- сохраните в нескольких места ваш ключ собственника или owner key и не используйте его вообще, он может понадобится только для сброса постинг или активного ключа
Настраиваем ключи и аккаунт
Сейчас мы будем учиться программировать бота делать действия за нас. Код бейби бота в предыдущих уроках не зависил от аккаунта пользователя бота. Поэтому мы детально разберем кода с настройкой пользовательсих данных.
const accountName = 'ontofractal' // аккаунт пользователя, который запускает бота
const postingKey = process.env.GOLOS_POSTING_KEY // предпочтительный вариант: используем environment variable для доступа к приватному постинг ключу
// const postingKey = '5K...' // альтернативный вариант: вводим приватный ключ прямо в код
const accountVotesToFollow = ['academy'] // array аккаунтов, голоса которых мы будем повторять
Environment variable -- переменная среды или переменная операционной системы, хранящая какую-либо информацию — например, данные о настройках системы в виде строки (текста).
У программ есть доступ к переменным среды, в node.js это происходит с помощью глобальной переменной process
: process.env.GOLOS_POSTING_KEY
В Linux и MacOS стандатный шелл в терминалах называется bash. Задание env(сокращенно от environment) variable выглядит так: export GOLOS_POSTING_KEY=5...
Для пользователей Windows больше информации можно найти тут.
Стандартной практикой обеспеченя безопасности секретов (всевозможных ключей и приватных данных) является отсутствие секретов в коде. Секреты останутся секретами, если сервер взломают, доступ к коду получит подрядчик или программист случайно откроет публичный доступ к репозиторию. На гитхабе присутствуют боты, которые сканируют все новые коммиты, находят забытые там ключи доступа, например, к Amazon AWS, программно создают новые сервера и майнят криптовалюты. Владельцы взломанных аккаунтов получают счета на тысячи долларов.
Что на самом деле значит "проголосовать"
Мы подготовили необходимую информацию: аккаунт, ключ, список аккаунтов, чьи голоса будет повторять бот. У нас уже есть код, который обрабатывает и выводит на экран всю необходимую нам информацию о новых голосах на блокчейне. Теперь мы изучим, как можно программным методом проголосовать за пост или комментарий.
С точки зрения блокчейна, "проголосовать" означает отправить на ноду (сервер) Голоса транзакцию с операцией vote
, подписанную одним из приватных ключей аккаунта, от имени которого совершается операция vote.
Примеры транзакции и операции
Транзакция, включающая в себя операцию голосования выглядит так:
[{"expiration": "2017-01-29T03:14:15", "extensions": [], "operations": [["vote", {"voter": "academy", "author": "qqc", "weight": 10000, "permlink": "javascript-urok-1"}]], "signatures": ["2071b95a146da86a7e33d976a3e1c7e428aef663a1f0d5b6d62f5bb564da4f7555733c688d87f118627b149a88bd8e0f03008385b262885105b3aa195320b6c2a0"], "ref_block_num": 2250, "ref_block_prefix": 2576683073}]
Сама операция имеет следующую форму/вид: ["vote", {"voter": "academy", "author": "qqc", "weight": 10000, "permlink": "javascript-urok-1"}]
Как видим, в самой операции есть только несколько параметров: голосующий аккаунт, автор, пермлинк и вес (не сила) голоса. А вот уже в транзакции есть криптографические подписи и метаданные необходимые для проверки и включения транзакции в блокчейн. Для автоматического создания подписей и отправки транзакции на ноду мы продолжим использовать библиотеку golos.js (мой порт steemjs )
Передаем данные операции голосования на ноду
Так выглядит функция передачи данных голоса на ноду:
golos.broadcast.vote(wif, voter, author, permlink, weight, function(err, result) {
console.log(err, result);
});
Разберем параметры, которая принимает метод vote
:
wif
: ключ голосующего пользователяvoter
: аккаунт голосующего пользователяauthor
: автор постаpermlink
: постоянная ссылкаweight
(вес данного голоса от 1 до 10000)
Пара author
и permlink
являются уникальными идентификаторами контента в блокчейне, как для постов, так и для комментариев. Если запросить данные о посте или комментарии с помощью метода golos.api.getContent(author, permlink)
то результатом будет JS объект, где в том числе будет содержаться property (свойство) id
. Так вот, id
(несмотря на свое название) не является уникальным, неизменным идентификатором постов. В одном из предыдущих хард форков Стима формат и значение id
изменилось для всех постов и комментариев.
Функция реагирования на новые голоса
const reactToIncomingVotes = (voteData) => {
const {voter, author, permlink, weight} = voteData
// проверяем входит ли проголосовавший аккаунт в список
const isMatchingVoter = accountVotesToFollow.includes(voter)
// проверяем не является ли это флагом, т.е. имеет вес ниже 0
// если сделать строго больше 0, то голоса не будут сниматься, даже если аккаунт убрал свой голос за пост
const isMatchingWeight = weight >= 0
if (isMatchingVoter && isMatchingWeight) {
golos.broadcast.vote(postingKey, accountName, author, permlink, weight, (err, result) => {
if (err) {
console.log('===========ПРОИЗОШЛА ОШИБКА, БОТ НЕ ГОЛОСОВАЛ===========')
console.log(err)
} else {
// используем ES2016 template strings, которые позволяют форматировать строки интерполируя expressions с помощью ${}
console.log(`@${accountName} проголосовал за пост ${permlink} написанный @${author} c весом ${weight} копируя голос ${voter}`)
}
})
}
}
Разберем главную функцию нашего урока. Она принимает данные о происходящих на блокчейне Голоса операциях vote
, проверяет их на соответствие правилам, повторяет голос и выводит результат на экран.
const {voter, author, permlink, weight}
использует удобную для повышения читаемости кода фичу ES2016 -- деструктурирование
function(err,result){...}
-- коллбек, обычная функция которая будет вызвана библиотекой golos
, а именно внутренней имплементацией метода golos.broadcast.vote
после получения ответа от ноды.
В зависимости от ответа ноды, результатом будет или выполненная операция голосования за пост или ошибка. Так или иначе результат будет выведен в консоль.
Важно: в операции vote
не содержится данных о том, пост это или комментарий. соответственно и бот будет голосовать как за посты, так и за комментарии. Аккаунт @academy голосует только за посты.
Весь код
const golos = require('golos') // импортируем модуль голоса
const util = require('util') // это встроенный в node.js модуль
const Promise = require("bluebird") // импортируем модуль Bluebird -- самую популярную имплементацию Promise
const _ = require('lodash') // как уже понятно, импортируем lodash ^_^
const accountName = 'ontofractal' // аккаунт пользователя, который запускает бота
const postingKey = process.env.GOLOS_POSTING_KEY // предпочтительный вариант: используем environment variable для доступа к приватному постинг ключу
// const postingKey = '5K...' // альтернативный вариант: вводим приватный ключ прямо в код
const accountVotesToFollow = ['academy'] // array аккаунтов, голоса которых мы будем повторять
// создаем новый Promise обворачивая golos.api.getDynamicGlobalProperties
const dynamicGlobalProperties = new Promise((resolve, reject) => {
golos.api.getDynamicGlobalProperties((err, result) => {
if (err) {
reject(err)
}
else {
resolve(result)
}
})
})
const pluckBlockHeight = x => x.head_block_number
// создадим функцию, которая достанет все операции из всех транзакций блока и поместит их в array
const unnestOps = (blockData) => {
// метод map создает новый array применяя функцию переданную в первый аргумент к каждому элементу
// используем метод flatten модуля lodash для извлечения элементов из вложенных списков и помещения в одноуровней список
return _.flatten(blockData.transactions.map(tx => tx.operations))
}
const reactToIncomingVotes = (voteData) => {
const {voter, author, permlink, weight} = voteData
// проверяем входит ли проголосовавший аккаунт в список
const isMatchingVoter = accountVotesToFollow.includes(voter)
// проверяем не является ли это флагом, т.е. имеет вес ниже 0
// если сделать строго больше 0, то голоса не будут сниматься, даже если аккаунт убрал свой голос за пост
const isMatchingWeight = weight >= 0
if (isMatchingVoter && isMatchingWeight) {
golos.broadcast.vote(postingKey, accountName, author, permlink, weight, (err, result) => {
if (err) {
console.log('===========ПРОИЗОШЛА ОШИБКА, БОТ НЕ ГОЛОСОВАЛ===========')
console.log(err)
} else {
// используем ES2016 template strings, которые позволяют форматировать строки интерполируя expressions с помощью ${}
console.log(`@${accountName} проголосовал за пост ${permlink} написанный @${author} c весом ${weight} копируя голос ${voter}`)
}
})
}
}
const selectOpHandler = (op) => {
// используем destructuring, очень удобную фичу EcmaScript2016
// это, конечно, не паттерн метчинг Elixir или Elm, но все равно сильно помогает улучшить читаемость кода
const [opType, opData] = op
if (opType === 'vote') {
reactToIncomingVotes(opData)
}
}
// поменяем имя функции с getBlockData на processBlockData т.к. ее назначение изменилось
const processBlockData = height => {
golos.api.getBlock(height, (err, result) => {
if (err) {
console.log(err)
}
else {
console.log('')
console.log('============ НОВЫЙ БЛОК ============')
// console.log(result) заменим на
unnestOps(result)
// в отличие от map, метод forEach не возвращает список с результатом применения функции
// также как и map, метод forEach применяет переданную в него функцию к каждому элементу array
// forEach используется для указания того, что результат применения функции является side effect
.forEach(selectOpHandler) // передаем функцию, которая определит, что делать
}
})
}
const startFetchingBlocks = startingHeight => {
let height = startingHeight
setInterval(() => {
processBlockData(height)
height = height + 1 // брррр, мутация
// у нас есть доступ к переменной height благодаря closure
}, 3000)
// Задаем интервал в 3000 мс т.к. блок Голоса генерируется каждые три секунды
}
// резолвим Promise
dynamicGlobalProperties
.then(pluckBlockHeight)
.then(startFetchingBlocks)
.catch(e => console.log(e))
Запускаем бота для своего аккаунта
Код бота этого урока готов к запуску. Бота-куратора можно запускать как на сервере, так и на пользовательском компьютере.
Правильным способом было бы использовать git и систему контроля версий, но пока будет достаточно сделать следующее:
- создать и скопировать вручную файлы
main.js
иpackage.json
из коммита этого урока в моем репозитории - внимание: для корректной работы необходимо использовать мой модуль
ontofractal/golosjs
(в package.json) - с помощью текстового редактора настроить список аккаунтов, чьи голоса будем копировать
- запустить
npm install
. - запустить бота
node main.js
В завершающем скринкасте я покажу, как настроить бот для аккаунта @ontofractal, который будет повторять голоса топ 19 делегатов с минимальным весом.
Ссылки на блокчейн эксплорер с голосами из скринкаста, которые аккаунт ontofractal передал в блокчейн следуя списку делегатов (за время создания скринкаста голосовал только @good-karma):
- http://golosd.com/tx/d04f63a62ce84b9b6de3f569c0175d75cadad719
- http://golosd.com/tx/0da94f558b352192208429629abf283c2d12e850
- http://golosd.com/tx/ce1c085321d7db5972cbd8e48755c96587a2329e
- http://golosd.com/tx/92f4686e9062718c00be52b0a046e4c8b2d179f7
Важно
Код выпущен под MIT лицензией. Всю ответственность за использование кода вы принимаете на себя.
В следующем уроке
- продолжим добавлять важный функционал
- научимся восстаналивать работу бота при критических ошибках
- изучим возможности для деплоймента на удаленных серверах
Спасибо! Очень познавательно и полезно
Спасибо за ценную инфу!
Пиши, если есть интерес к какой-то конкретной теме :)
Если несложно, прошу подсказать как выбрать посты по тегу.
Насколько я помню, у клиента Голоса (и Стима) нету JSONRPC метода API для выборки постов по тэгу. Т.е. напрямую из нод посты по тегу не вытянешь. Не знаю почему так, казалось бы, что это базовый функционал.
@stranger27:
по такой выборке можно, глянь доки методов библиотеки
golosjs
у меня в репоХорошо, а можно ли тогда смотреть посты по такой выборке: новые, актуальные, трендовые ?
Очень помогло, спасибо.
рад слышать/читать :)
Очень интересно, но пока не все получается :(
это нормально, у меня тоже сначала почти ничего не получалось :)
Зачем столько мучений? :) Превратить Голос к казино и играйте на здоровье?
Хотя... по сути вы и развиваете инструментарий казино :):):)
Сухой остаток (попытка) здесь: https://golos.id/ru--yevolyucziya/@jumper/kriptovalyuta-golos-proekt-solyaris
У тебя какое-то альтернативное определение слова "казино" ¯\(ツ)/¯
Чистое казино - имея ограниченное кол-во денег, планировать ставки, используя боты-шулеры. А отцы/матери-основатели обещали качественный контент за счет тщательных чтения и отбора его кураторами :) Оно и понятно - лохам надо обещать золотые горы :)
Или ваши боты умеют читать? :):):)
мне скучно что-то рассказывать человеку, который не потрудился изучить принципы работы Голоса
Ну да, зелен виноград :) Не напрягайтесь :)
Было бы здорово, если поэтапно опишешь процедуру размещения Бота на виртуальном сервере. Спасибо
Конечно, в следующем или через один урок сделаю про деплоймент, буду использовать docker, есть смысл немного с ним познакомиться до урока.
Привет. Ошибка аналогичная rusldv, пока не могу разобраться...
Попробуй удалить папку
node_modules/
, проверь, чтобыpackage.json
в dependencies значение пары было"golos": "ontofractal/golosjs"
и сделай зановоnpm install
, rusldv это помогло.А про папку можно немного подробнее, я такую даже не создавал... вроде)
И что должно отображаться, если корректно переустановился npm install?
если ты проходил предыдущие уроки и делал
npm install
, то она автоматически появилась, теперь ее нужно удалить и заново инсталлировать. ничего особого не отображается, просто сообщение об успехе инсталляции модулей. напиши мне в chat.golos.io, если не сработает.Спасибо, оказалось не тот package.json правил. Работает, как часы. Осталось в облаке разместить
когда-нибудь все это пойму
нужно время и усилия -- и поймешь! :)
о да)
А теперь он ругается когда голосует. (npm - это ник бота. Просто первое что в голову пришло :))
Ключ для постинга правильный я проверял. Тот который на 5..
проверь
package.json
, в dependencies"golos": "ontofractal/golosjs"
? и еще переустановил ли ты npm модули?Я туда как на видео зафигарил "golos": "git://github.com/ontofractal/golosjs.git", :))
Ща поменяю
"ontofractal/golosjs" -- это простая форма записи "git://github.com/ontofractal/golosjs.git" :)
маякни в chat.golos.io, если не получится после переустановки
npm install
@rusldv
эта ошибка, скорее всего, связана с неправильным модулем
steemauth
, который подписывает транзакции. напиши в чат, расскажу.хорошо тогда завтра если сам не справлюсь)
Спасибо за статью. А не подскажите где можно найти описание функций API. Вот этих https://github.com/ontofractal/golosjs/tree/master/doc Я просто не могу въехать в некоторые передаваемые аргументы им))
С документацией у Стима, а соответственно и у Голоса туговато. Вот тут частично задокументированы методы API https://steemit.github.io/steemit-docs/#introduction, но я часто иду смотреть прямо в исходники https://github.com/steemit/steem/blob/master/libraries/app/database_api.cpp
кстати, давай на ты :)
Ага спасибо. Да давай. Я тоже не люблю, когда так официально общаются :)
И у меня тут такая ошибка вылизает. Когда добавил функционал описанный в этом уроке. Вы не сталкивались?
Выглядит как опечатка в
accoun!VotesToFollow
, в коде урокаaccountVotesToFollow
Спасибо сейчас попробую исправить)
ладно. покопаю еще стимит) мало ли)...
Кстати когда уже ограничение по кол-ву комментариев переделают не в курсе случайно?))
Неограниченную глубину комментариев должны внедрить в следующем форке в Стиме или через один (вроде бы) в Голосе .
спасибо.
жаль что я не технарь)..
это можно исправить, было бы желание)
интересно сколько на это "Требуется базовый уровень понимания JavaScript, веб технологий и командной строки" времени уйдет..
Если активно прикладывать усилия, то для базового уровня необходимо от 1 до 3 месяцев.
боооольше, если ты всю жизнь гуманитарий
@mrgreen:
это смотря в каком смысле "прокатят" :) вряд ли так чему-то научишься..
https://steemit.com/steem/@xeroc/upvote-bot-in-less-than-10-lines-of-code
а вот такие штуки не прокатят? хотя не прокатят наверное. там без учета 30 мин.
надо бы стимит еще покопать может что и найду)