Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Konstantin_Krasilov/3/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

source 'https://rubygems.org'

gem 'aasm'
gem 'activerecord'
gem 'dotenv-rails'
gem 'factory_bot'
gem 'json'
gem 'pg'
gem 'progress_bar'
gem 'rake'
gem 'rspec'
gem 'sinatra'
gem 'sinatra-activerecord'
gem 'telegramAPI'
gem 'pry'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundler/OrderedGems: Gems should be sorted in an alphabetical order within their section of the Gemfile. Gem pry should appear before telegramAPI.

gem 'whenever', require: false
12 changes: 12 additions & 0 deletions Konstantin_Krasilov/3/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

require 'sinatra/activerecord/rake'
require './app'

namespace :db do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не очень понятно, если честно, что ты хотел тут сказать. Выглядит как ненужный код.

Copy link
Contributor Author

@kkrasilov kkrasilov May 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я его добавил чтобы была возможность работать с таксками гема sinatra/activerecord/rake - rake db:migrate, rake db:seed.
https://github.com/sinatra-activerecord/sinatra-activerecord#setup

task :load_config do
require './app'
end
end

Dir.glob('lib/tasks/*.rake').each { |r| load r }
63 changes: 63 additions & 0 deletions Konstantin_Krasilov/3/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Telegram-Bot "English lessons"

## Описание

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

## Пример взаимодействия

> /start
>
> Привет! Я бот, который помогает учить новые английские слова каждый день. Давай сперва определимся,' \
'сколько слов в день (от 1 до 6 ) ты хочешь узнавать?
>
> 7
>
> Сорри, только 6 слов. Давай еще раз?
>
> 4
>
> Принято!

Через какое-то время от бота должно прийти сообщение похожего смысла:

> **Opinion** - 1 unproven belief. 2 view held as probable. 3 what one thinks about something. 4 piece of professional advice (a second opinion). 5 estimation (low opinion of). [latin: related to *opine]

Если человек не отреагировал на слово, то бот напомнит ему об этом:

> Кажется, ты был слишком занят и пропустил слово выше? Дай мне знать, что у тебя все хорошо!
>
> 😘
>
> Вижу, что ты заметил слово! Продолжаем учиться дальше!

## Установка

Для корректной работы программы на вашем компьютере должен быть установлены [Ruby](https://www.ruby-lang.org/en/),
база данных [Postgres](https://www.postgresql.org/) и [Ngrok](https://ngrok.com/).
Запустите сервер `ngrok`:
```
$ ./ngrok http 4567
```
Создайте своего [telegram бота](https://core.telegram.org/bots).
Пропишите переменные окружения в .env файле:
```
TELEGRAM_TOKEN=Ваш токен telegram
DATABASE_USER=Ваши_данные
DATABASE_PASSWORD=Ваши_данные
DATABASE_NAME=Ваши_данные
URL=https url который выдал ngrok
```
Запустите комманды:
```
$ bundle
$ bunlde exec rake db:migrate db:seed
$ whenever --update-crontab
```
## Запуск
Запуск осуществляется командой:
```
$ bundle exec ruby app.rb
```
41 changes: 41 additions & 0 deletions Konstantin_Krasilov/3/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require 'telegramAPI'
require 'sinatra'
require 'dotenv'
require 'sinatra/activerecord'
require_relative 'config/connection'
require_relative 'app/models/user'
require_relative 'app/services/telegram/conversation'

Dotenv.load
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лайк за использование ENV переменных 👍


WELCOME = 'Привет! Я бот, который помогает учить новые английские слова каждый день. Давай сперва определимся,' \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/RedundantFreeze: Do not freeze immutable objects, as freezing them has no effect.

'сколько слов в день (от 1 до 6) ты хочешь узнавать?'.freeze

api = TelegramAPI.new ENV['TELEGRAM_TOKEN']

post '/telegram' do
status 200

request.body.rewind
data = JSON.parse(request.body.read)

chat_id = data['message']['chat']['id']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Чисто в теории у тебя тут очень легко может случиться ошибка, которую ты не обработаешь. Например chat ключа не будет в сообщении, оно вернет nil попробует взять ['id'] и получится беда.

Чтобы от такого застраховаться - пойди узнать про метод dig у хеша. И не забывай про обработку ошибок.

Copy link
Contributor Author

@kkrasilov kkrasilov May 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как лучше обрабатывать ошибки?

Просто добавить

chat_id = data.dig('message', 'chat', 'id')
raise StandardError, 'Sorry, chat id cannot be blank' if chat_id.nil?

или обернуть все в begin -

begin
  chat_id = data.dig('message', 'chat', 'id')
  raise StandardError if chat_id.nil?
rescue
  puts 'Error - Sorry, chat id cannot be blank'
end

чтобы программа продолжала работать.

И с ошибками ActiveRecord, сделать -

def set_user
  user = User.find_or_create_by!(telegram_id: chat_id)
rescue ActiveRecord::NotNullViolation => e
  puts "Error - #{e}"
end

или просто добавить метод find_or_create_by! без rescue?

message = data['message']['text']

user = User.find_or_create_by(telegram_id: chat_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_or_create_by если не создаст пользователя, вернет тебе false и будет беда. Лучше всего если не обрабатываешь ошибки в ActiveRecord использовать методы с восклицательным знаком find_or_create_by!


case message
when '/start'
api.sendMessage(chat_id, WELCOME) if message.include?('/start')
else
api.sendMessage(chat_id, Telegram::SendMaxWord.new(user, message).call) if user.waiting_max_word?
api.sendMessage(chat_id, Telegram::SendSmiley.new(user, message).call) if user.waiting_smiley?
end

# Return an empty json, to say "ok" to Telegram
'{}'
end

api.setWebhook(ENV['URL'])
30 changes: 30 additions & 0 deletions Konstantin_Krasilov/3/app/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'aasm'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/EmptyLineAfterMagicComment: Add an empty line after magic comments.


class User < ActiveRecord::Base
include AASM

has_many :user_words, dependent: :destroy
has_many :words, through: :user_words

aasm do
state :waiting_max_word, initial: true
state :sleeping, :waiting_smiley, :learning

event :answer_max_word do
transitions from: :sleeping, to: :waiting_max_word
end

event :answer_smiley do
transitions from: :learning, to: :waiting_smiley
end

event :learn do
transitions from: [:waiting_max_word, :waiting_smiley, :sleeping], to: :learning
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/SymbolArray: Use %i or %I for an array of symbols.

end

event :sleep do
transitions from: :waiting_smiley, to: :sleeping
end
end
end
6 changes: 6 additions & 0 deletions Konstantin_Krasilov/3/app/models/user_word.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

class UserWord < ActiveRecord::Base
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rails/ApplicationRecord: Models should subclass ApplicationRecord.

belongs_to :user
belongs_to :word
end
10 changes: 10 additions & 0 deletions Konstantin_Krasilov/3/app/models/word.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class Word < ActiveRecord::Base
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rails/ApplicationRecord: Models should subclass ApplicationRecord.

has_many :user_words, dependent: :destroy
has_many :users, through: :user_words

def to_s
"#{value} - #{meaning}"
end
end
36 changes: 36 additions & 0 deletions Konstantin_Krasilov/3/app/services/telegram/lesson.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require 'telegramAPI'
require 'dotenv'
require_relative '../../models/user'
require_relative '../../models/word'
require_relative '../../models/user_word'

Dotenv.load

module Telegram
# A class that teaches the user new words. Searches for a suitable word, sends to the user and waits for a reaction.
class Lesson
def initialize(user)
@user = user
@api = TelegramAPI.new ENV['TELEGRAM_TOKEN']
end

def call
find_and_added_word_to_user
send_word
@user.answer_smiley!
end

private

def find_and_added_word_to_user
@word = Word.where.not(id: @user.words).order('RANDOM()').first
@user.words << @word
end

def send_word
@api.sendMessage(@user.telegram_id, @word.to_s)
end
end
end
23 changes: 23 additions & 0 deletions Konstantin_Krasilov/3/app/services/telegram/reminder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

require 'telegramAPI'
require 'dotenv'
require_relative '../../models/user'

Dotenv.load

module Telegram
# A class that reminds the user to send an emoticon.
class Reminder
MESSAGE = 'Кажется, ты был слишком занят и пропустил слово выше? Дай мне знать, что у тебя все хорошо!'

def initialize(user)
@user = user
@api = TelegramAPI.new ENV['TELEGRAM_TOKEN']
end

def call
@api.sendMessage(@user.telegram_id, MESSAGE)
end
end
end
31 changes: 31 additions & 0 deletions Konstantin_Krasilov/3/app/services/telegram/send_max_word.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require_relative '../../models/user'

module Telegram
# A class that expects from the user information about the maximum word.
class SendMaxWord
RESPONSE = {
max_word_success: 'Принято!',
max_word_error: 'Сорри, только 6 слов. Давай еще раз?'
}.freeze

def initialize(user, message)
@user = user
@message = message
end

def call
return RESPONSE[:max_word_error] unless (1..6).cover?(@message.to_i)

update_user!
RESPONSE[:max_word_success]
end

private

def update_user!
@user.update(max_words: @message, aasm_state: 'learning')
end
end
end
32 changes: 32 additions & 0 deletions Konstantin_Krasilov/3/app/services/telegram/send_smiley.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require_relative '../../models/user'
require_relative '../../models/user_word'

module Telegram
# A class that expects a message from a user with an emoticon from the user.
class SendSmiley
RESPONSE = {
smiley_success: 'Вижу, что ты заметил слово! Продолжаем учиться дальше!',
smiley_error: 'Отправь смайл 😉'
}.freeze

def initialize(user, message)
@user = user
@message = message
end

def call
return RESPONSE[:smiley_error] unless @message.unpack('U*').any? { |e| e.between?(0x1F600, 0x1F6FF) }

update_user!
RESPONSE[:smiley_success]
end

private

def update_user!
@user.user_words.where(created_at: Date.today.all_day).count >= @user.max_words ? @user.sleep! : @user.lesson!
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rails/Date: Do not use Date.today without zone. Use Time.zone.today instead.

end
end
end
15 changes: 15 additions & 0 deletions Konstantin_Krasilov/3/config/connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'sinatra/activerecord'
require 'dotenv'

Dotenv.load

set :database, {
adapter: 'postgresql',
encoding: 'unicode',
pool: 5,
username: ENV['DATABASE_USER'],
password: ENV['DATABASE_PASSWORD'],
database: ENV['DATABASE_NAME']
}
15 changes: 15 additions & 0 deletions Konstantin_Krasilov/3/config/schedule.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

set :output, 'log/cron.log'

every '00 00 * * *' do
rake 'telegram:start_new_training_day'
end

every '00 11-22 * * *' do
rake 'telegram:start_lesson'
end

every '*/5 * * * *' do
rake 'telegram:reminder_for_answer'
end
15 changes: 15 additions & 0 deletions Konstantin_Krasilov/3/db/migrate/20210430082533_create_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class CreateUsers < ActiveRecord::Migration[6.1]
def up
create_table :users do |t|
t.integer :telegram_id, null: false, index: { unique: true }
t.integer :max_words, null: false, default: 1
t.integer :conversation_status, null: false, default: 0

t.timestamps
end
end

def down
drop_table :users
end
end
14 changes: 14 additions & 0 deletions Konstantin_Krasilov/3/db/migrate/20210430084025_create_words.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CreateWords < ActiveRecord::Migration[6.1]
def up
create_table :words do |t|
t.string :value
t.text :meaning

t.timestamps
end
end

def down
drop_table :words
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CreateUserWords < ActiveRecord::Migration[6.1]
def up
create_table :user_words do |t|
t.references :user, null: false, foreign_key: true
t.references :word, null: false, foreign_key: true

t.timestamps
end
end

def down
drop_table :user_words
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddAasmStateForUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :aasm_state, :string
end
end
Loading