Чат чата для публикации последних ответов
Я являюсь половинной частью робота syb0rg , в котором будут опубликованы последние ответы Code Review на чат общения с клиентами . Вот список рекомендуемых мной рекомендаций по обзору в порядке предпочтения:
- Эффективность (с запросами API, скоростью входа и ответами и т. д.).
- Проблемы безопасности
- Лучшие практики
Для получения дополнительных запросов о бот-чате см. этот мета-пост .
В любом случае приемлемы любые отзывы. Не будьте слишком суровыми, это один из моих первых случаев использования Ruby.
ACCESS_TOKEN = '<insert key>'
# get your access token here:
# https://stackexchange.com/oauth/dialog?client_id=2666&redirect_uri=http://keyboardfire.com/chatdump.html&scope=no_expiry
$root = 'http://stackexchange.com'
$chatroot = 'http://chat.stackexchange.com'
$room_number = 12723
site = 'codereview'
email = '<insert email>'
password = '<insert password>'
require 'rubygems'
require 'mechanize'
require 'json'
require 'net/http'
loop
{
begin
$agent = Mechanize.new
$agent.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
login_form = $agent.get('https://openid.stackexchange.com/account/login').forms.first
login_form.email = email
login_form.password = password
$agent.submit login_form, login_form.buttons.first
puts 'logged in with SE openid'
meta_login_form = $agent.get($root + '/users/login').forms.last
meta_login_form.openid_identifier = 'https://openid.stackexchange.com/'
$agent.submit meta_login_form, meta_login_form.buttons.last
puts 'logged in to root'
chat_login_form = $agent.get('http://stackexchange.com/users/chat-login').forms.last
$agent.submit chat_login_form, chat_login_form.buttons.last
puts 'logged in to chat'
$fkey = $agent.get($chatroot + '/chats/join/favorite').forms.last.fkey
puts 'found fkey'
def send_message text
loop
{
begin
resp = $agent.post("#{$chatroot}/chats/#{$room_number}/messages/new", [['text', text], ['fkey', $fkey]]).body
success = JSON.parse(resp)['id'] != nil
return if success
rescue Mechanize::ResponseCodeError => e
puts "Error: #{e.inspect}"
end
puts 'sleeping'
sleep 3
}
end
puts $ERR ? "An unknown error occurred. Bot restarted." : "Bot initialized."
last_date = 0
loop
{
uri = URI.parse "https://api.stackexchange.com/2.2/events?pagesize=100&since=#{last_date}&site=#{site}&filter=!9WgJfejF6&key=thqRkHjZhayoReI9ARAODA((&access_token=#{ACCESS_TOKEN}"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
data = JSON.parse http.get(uri.request_uri).body
events = data['items']
data['items'].each do |event|
last_date = [last_date, event['creation_date'].to_i + 1].max
if ['answer_posted'].include? event['event_type']
send_message "[tag:rob0t] New answer detected:"
send_message event['link']
puts "Answer posted."
end
end
puts "#{data['quota_remaining']}/#{data['quota_max']} quota remaining"
sleep(40 + (data['backoff'] || 0).to_i) # add backoff time if any, just in case
}
rescue => e
$ERR = e
p e
end
}
( Атрибуция оригинального автора, код выше - это модифицированная версия. ) суб>
4 ответа
Во-первых, отступы вашего кода последовательно - стандарт в Ruby - это два пробела. Это включает отступы содержимого ваших блоков begin-rescue-end
.
Обычно мне не нравится делать такую огромную суету по поводу отступов, но в этом случае я считаю, что это очень важно, потому что:
- Ваша программа имеет очень необычный контур (бесконечные циклы и определение функции (!) внутри бесконечного цикла)
- Ставки высоки: если вы плохо себя чувствуете, вы можете сделать много людей расстроенными. Поэтому следует использовать хорошие методы разработки программного обеспечения.
Контур, подобный этому, будет более идиоматичным для Ruby:
class AnswerBot
ROOT = 'http://stackexchange.com'
CHAT_ROOT = 'http://chat.stackexchange.com'
def initialize(options)
@agent = Mechanize.new
@options = options
end
def login
# Do stuff with @agent
login_form = $agent.get('https://openid.stackexchange.com/account/login').forms.first
login_form.email = @options[:email]
# ...
@fkey = @agent.get(CHAT_ROOT + '/chats/join/favorite').forms.last.fkey
end
def fetch_answers
# Make request to api.stackexchange.com
# ...
data['items'].each { |event| yield event }
return (data['backoff'] || 0).to_i
end
def send_message(text, retries=5, backoff=40)
# ...
end
end
bot = AnswerBot.new(:access_token => ...,
:room_number = 12723,
:site => 'codereview',
:email => ...,
:password => ...)
loop {
begin
bot.login
do
backoff = bot.fetch_answers do |event|
if ['answer_posted'].include?(event['event_type']) # <-- Is that right?
bot.send_message(...)
end
end
while sleep(40 + backoff)
rescue => e
puts "An error occurred."
p e
end
puts "Bot restarted."
}
Синтаксис блока
Это:
loop
{
...
}
Вызывает синтаксическую ошибку в MRI 2.1. Это устранит синтаксическую ошибку:
loop {
...
}
Однако использование {...}
обычно зарезервировано для однострочных блоков. Предпочитают:
loop do
..
end
Методы
Используйте еще много методов. Должно быть возможно выяснить, что делает сценарий в широких мазках, глядя только на его основной метод. Найдите строки кода, которые делают одну вещь и помещают их в свой собственный метод. Например:
def login_to_se
login_form = $agent.get('https://openid.stackexchange.com/account/login').forms.first
login_form.email = email
login_form.password = password
$agent.submit login_form, login_form.buttons.first
puts 'logged in with SE openid'
end
...
login_to_se
и т. д. Ваши методы должны, по возможности, иметь следующие свойства:
- Метод делает одно:
- Название говорит, что оно делает
- Весь код в методе находится на уровне того же уровня абстракции
Вы хотите, чтобы код на более высоких уровнях, таких как основной цикл, выглядел следующим образом:
loop do
continue_on_error do
login_to_se
login_to_meta
login_to_chat
loop do
copy_new_post_to_chat
wait
end
end
end
Метод должен читать как рассказ. Абстрактные - в методах, классах и т. Д. - детали, из-за которых история трудно справиться.
Искажение спасения тоже
Вы можете заметить вызов continue_on_error
выше. Может быть очень полезно абстрагироваться от ваших блоков спасения. В этом случае он дает нам имя метода, в котором почему мы делаем :
def continue_on_error
yield
rescue => e
$ERR = e
p e
end
$ ERR
Мы можем избавиться от $ ERR, указав #continue_on_error, что мы перезапускаем:
def continue_on_error
yield
rescue => e
puts e
puts "Restarting"
end
и в основном цикле, а не:
puts $ERR ? "An unknown error occurred. Bot restarted." : "Bot initialized."
просто
puts "Initialized"
Вывод журнала скрипта будет таким же четким.
Некоторые проблемы с низким уровнем стиля:
- Хотя скобки вокруг списков параметров не являются обязательными, существует консенсус о том, что их не следует опускать .
- Я не вижу шаблона соответствия для использования сигила
$
для переменных. Я предлагаю не использовать их вообще. - Вы используете как
Mechanize
, так и необработанныеNet::HTTP
запросы. Я предлагаю использоватьMechanize
для всех.
Добавление к существующим ответам:
- Вам не нужно требовать
rubygems
, поскольку вы его вообще не используете. Это обычно не нужно. См. Здесь https://stackoverflow.com/questions/2711779/require-rubygems . -
Если у вас много требований, вы можете сделать этот трюк, чтобы сгруппировать их в одну строку:
require 'rubygems' require 'mechanize' require 'json' require 'net/http'
В
%w{rubygems mechanize json net/http}.each{|gem| require gem}