Чат чата для публикации последних ответов

Я являюсь половинной частью робота syb0rg , в котором будут опубликованы последние ответы Code Review на чат общения с клиентами . Вот список рекомендуемых мной рекомендаций по обзору в порядке предпочтения:

  1. Эффективность (с запросами API, скоростью входа и ответами и т. д.).
  2. Проблемы безопасности
  3. Лучшие практики

Для получения дополнительных запросов о бот-чате см. этот мета-пост .

В любом случае приемлемы любые отзывы. Не будьте слишком суровыми, это один из моих первых случаев использования 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
}

( Атрибуция оригинального автора, код выше - это модифицированная версия. ) суб>

55 голосов | спросил rob0t 16 MarpmSun, 16 Mar 2014 18:59:36 +04002014-03-16T18:59:36+04:0006 2014, 18:59:36

4 ответа


22

Во-первых, отступы вашего кода последовательно - стандарт в Ruby - это два пробела. Это включает отступы содержимого ваших блоков begin-rescue-end.

Обычно мне не нравится делать такую ​​огромную суету по поводу отступов, но в этом случае я считаю, что это очень важно, потому что:

  1. Ваша программа имеет очень необычный контур (бесконечные циклы и определение функции (!) внутри бесконечного цикла)
  2. Ставки высоки: если вы плохо себя чувствуете, вы можете сделать много людей расстроенными. Поэтому следует использовать хорошие методы разработки программного обеспечения.

Контур, подобный этому, будет более идиоматичным для 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."
}
ответил 200_success 16 MarpmSun, 16 Mar 2014 20:37:17 +04002014-03-16T20:37:17+04:0008 2014, 20:37:17
22

Синтаксис блока

Это:

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"

Вывод журнала скрипта будет таким же четким.

ответил Wayne Conrad 16 MarpmSun, 16 Mar 2014 22:22:12 +04002014-03-16T22:22:12+04:0010 2014, 22:22:12
8

Некоторые проблемы с низким уровнем стиля:

  • Хотя скобки вокруг списков параметров не являются обязательными, существует консенсус о том, что их не следует опускать .
  • Я не вижу шаблона соответствия для использования сигила $ для переменных. Я предлагаю не использовать их вообще.
  • Вы используете как Mechanize, так и необработанные Net::HTTP запросы. Я предлагаю использовать Mechanize для всех.
ответил 200_success 16 MarpmSun, 16 Mar 2014 20:56:18 +04002014-03-16T20:56:18+04:0008 2014, 20:56:18
3

Добавление к существующим ответам:

  1. Вам не нужно требовать rubygems, поскольку вы его вообще не используете. Это обычно не нужно. См. Здесь https://stackoverflow.com/questions/2711779/require-rubygems .
  2. Если у вас много требований, вы можете сделать этот трюк, чтобы сгруппировать их в одну строку:

    require 'rubygems'
    require 'mechanize'
    require 'json'
    require 'net/http'
    

    В

    %w{rubygems mechanize json net/http}.each{|gem| require gem}
    
ответил Mhmd 15 J0000006Europe/Moscow 2014, 20:30:43

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132