abot-info 0.1.1 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 127b62eb518c63d6b7ba0bbcccc85ef22964b44555d92a1dae386dd48dcae3ae
4
- data.tar.gz: b82f5cb6291ee3e612982fda79fb5430694f6fd0127f41ecaae9f9e1c57620af
3
+ metadata.gz: c356808904955eec165f429c9a0012bc095865983303853d48ff09239e988e19
4
+ data.tar.gz: 930de8a6a519242048f9e02603090cdd9560c8040866ec0d23f80b3affa2062e
5
5
  SHA512:
6
- metadata.gz: 03e4168d791452a1faccaa0ac91754101df5d9db3e7f1916dd116686cede9d4df51d9c89c71623c8c08804ab03c3726ce4b0579026e0ba9577c49162bdd165fe
7
- data.tar.gz: f74ff5924ec5bb3270e4503f5c30dacbda788837a48543deafc255a43e665c4b7211fed1a97d04e145480fb7364926697f084c6c804ef88edc10173dd321ef1c
6
+ metadata.gz: a5fbf25ee4fc34c87213f122de255590ae13e316741b0d049f6bde84dd3cacb52f10e8a002eafb6c4eb269b13f4396e8ea06d6d99acf6f2763d59f29dfbe448d
7
+ data.tar.gz: f833ee553da12be027dd6e72ebc2544527ec2e64e9080939b2bcbc15628675111e70c1caaf691cbfca2229540bb2778f64c0a0a7f46e3777ca129ebbe7ad35cb
data/Gemfile CHANGED
@@ -5,3 +5,4 @@ gem 'paint', '~> 2.2.1'
5
5
  gem 'sqlite3', '~> 1.4.2'
6
6
  gem 'terminal-table', '~> 3.0.1'
7
7
  gem 'binance-ruby', '~> 1.3.1'
8
+ gem 'telegram-bot-ruby', '~> 0.16.0'
data/abot-info.gemspec CHANGED
@@ -38,4 +38,5 @@ Gem::Specification.new do |spec|
38
38
  spec.add_dependency "sqlite3", '~> 1.4.2'
39
39
  spec.add_dependency "terminal-table", '~> 3.0.1'
40
40
  spec.add_dependency "binance-ruby", '~> 1.3.1'
41
+ spec.add_dependency 'telegram-bot-ruby', '~> 0.16.0'
41
42
  end
@@ -79,6 +79,33 @@ module Abot
79
79
  symbol_info(symbol).try(:[], :highPrice).to_f
80
80
  end
81
81
 
82
+ def coins_without_order(quote_assets)
83
+ exception_coins = quote_assets + ['BNB'] + Coin::FIAT
84
+ balances = account_info[:balances]
85
+ result = balances.select do |s|
86
+ s[:free].to_f != 0 &&
87
+ !exception_coins.include?(s[:asset]) &&
88
+ symbol_info(s[:asset] + 'USDT').try(:[], :askPrice).to_f * s[:free].to_f > 5 &&
89
+ (
90
+ begin
91
+ btcusdt = get_symbols.find { |f| f[:symbol] == 'BTCUSDT' }[:askPrice].to_f
92
+ if s[:asset] == 'BTC'
93
+ symbol_info("#{s[:asset]}USDT").try(:[], :askPrice).to_f * s[:free].to_f > 5
94
+ elsif get_symbols.find { |f| f[:symbol] == "#{s[:asset]}USDT" }.nil?
95
+ coin = get_symbols.find { |f| f[:symbol] == "#{name}BTC" }
96
+ coin[:askPrice].to_f * s[:free].to_f * btcusdt > 5
97
+ else
98
+ coin = get_symbols.find { |f| f[:symbol] == "#{s[:asset]}USDT" }
99
+ coin[:askPrice].to_f * s[:free].to_f > 5
100
+ end
101
+ rescue StandardError
102
+ false
103
+ end
104
+ )
105
+ end
106
+ result.pluck(:asset)
107
+ end
108
+
82
109
  private
83
110
 
84
111
  def calculation_current_balance_btc
@@ -8,9 +8,11 @@ module Abot
8
8
  include Abot::Info::Coin::CoinDecorator
9
9
 
10
10
  attr_reader :base_asset, :quote_asset, :average_price, :current_price, :sell_price, :volume, :num_averaging,
11
- :total_quote, :buy_price, :step_averaging, :tick_size, :account, :timer, :trade_params
11
+ :total_quote, :buy_price, :step_averaging, :tick_size, :account, :timer, :trade_params,
12
+ :pump_detector
13
+
14
+ FIAT = %w[USDT BUSD AUD BIDR BRL EUR GBR RUB TRY TUSD USDC DAI IDRT PAX UAH NGN VAI BVND]
12
15
 
13
- FIAT = ['USDT', 'BUSD', 'AUD', 'BIDR', 'BRL', 'EUR', 'GBR', 'RUB', 'TRY', 'TUSD', 'USDC', 'DAI', 'IDRT', 'PAX', 'UAH', 'NGN', 'VAI', 'BVND']
14
16
 
15
17
  def initialize(options = {})
16
18
  @trade_params = options[:trade_params]
@@ -27,6 +29,7 @@ module Abot
27
29
  @step_averaging = options[:step_averaging]
28
30
  @tick_size = options[:tick_size]
29
31
  @timer = options[:timer]
32
+ @pump_detector = options[:pump_detector]
30
33
  end
31
34
 
32
35
  def self.current_coins(account, trade_params)
@@ -46,6 +49,7 @@ module Abot
46
49
  step_averaging: coin['stepAveraging'].to_f,
47
50
  tick_size: coin['tickSize'],
48
51
  timer: coin['timer'].to_s[0..9],
52
+ pump_detector: coin['pumpDetector'],
49
53
  )
50
54
  end
51
55
  end
@@ -74,6 +78,10 @@ module Abot
74
78
  @percent_to_order ||= ((sell_price / current_price - 1) * 100)
75
79
  end
76
80
 
81
+ def percent_from_order
82
+ @percent_from_order ||= ((1 - current_price / sell_price) * 100)
83
+ end
84
+
77
85
  def percent_from_min_to_average
78
86
  @percent_to_min ||= ((min_price / next_average_price - 1) * 100)
79
87
  end
@@ -82,6 +90,10 @@ module Abot
82
90
  @percent_to_max ||= ((sell_price / max_price - 1) * 100)
83
91
  end
84
92
 
93
+ def percent_from_max
94
+ @percent_from_max ||= ((1 - max_price / sell_price) * 100)
95
+ end
96
+
85
97
  def min_price
86
98
  @min_price ||= account.symbol_min_price(symbol)
87
99
  end
@@ -14,6 +14,8 @@ module Abot
14
14
  API_KEY_TABLE = BASE_CONNECTION.execute('SELECT * FROM api_key').freeze
15
15
  API_KEY = API_KEY_TABLE.first['api'].freeze
16
16
  SECRET_KEY = API_KEY_TABLE.first['secret'].freeze
17
+ TG_TOKEN = API_KEY_TABLE.first['tg_token'].freeze
18
+ TG_NAME = API_KEY_TABLE.first['tg_name'].freeze
17
19
 
18
20
  class << self
19
21
  def data_coins
@@ -25,7 +27,7 @@ module Abot
25
27
  end
26
28
 
27
29
  def data_daily_profit
28
- BASE_CONNECTION.execute("SELECT quote, sum(profit) FROM daily_profit GROUP BY quote")
30
+ BASE_CONNECTION.execute("SELECT quote, sum(profit) as sum, count(*) as count FROM daily_profit GROUP BY quote")
29
31
  end
30
32
  end
31
33
  end
@@ -5,15 +5,9 @@ module Abot
5
5
  class Coin
6
6
  module CoinDecorator
7
7
  def decorated_sell_up
8
- rounded_sell_up = sell_up.round(2)
9
- case num_averaging
10
- when 1, 2
11
- rounded_sell_up > 2 ? Paint[rounded_sell_up, :red] : rounded_sell_up
12
- when 3, 4, 5
13
- rounded_sell_up > 2 ? rounded_sell_up : Paint[rounded_sell_up, :red]
14
- else
15
- rounded_sell_up
16
- end
8
+ return '-' if sell_up == Float::INFINITY
9
+
10
+ sell_up.round(2)
17
11
  end
18
12
 
19
13
  def decorated_current_profit
@@ -38,22 +32,19 @@ module Abot
38
32
  end
39
33
 
40
34
  def decorated_next_average_price
41
- "#{next_average_price} (#{decorated_next_average_price_percent})"
35
+ next_average_price.zero? ? '-' : "#{next_average_price} (#{decorated_next_average_price_percent})"
42
36
  end
43
37
 
44
38
  def decorated_name
45
- arrow = if account.symbol_info(symbol).try(:[], :lastPrice).to_f < account.symbol_info(symbol).try(:[], :askPrice).to_f
46
- Paint[' ↑', :green]
47
- elsif account.symbol_info(symbol).try(:[], :lastPrice).to_f > account.symbol_info(symbol).try(:[], :askPrice).to_f
48
- Paint[' ↓', :red]
49
- else
50
- ''
51
- end
52
- "#{name}(#{account.symbol_info(symbol).try(:[], :priceChangePercent)}%)#{arrow}"
39
+ "#{name} (#{account.symbol_info(symbol).try(:[], :priceChangePercent)}%)#{pump}"
40
+ end
41
+
42
+ def pump
43
+ pump_detector == 'True' ? Paint[' (P)', :blue] : ''
53
44
  end
54
45
 
55
46
  def decorated_name_mobile
56
- name
47
+ "#{name} #{pump}"
57
48
  end
58
49
 
59
50
  def decorated_sell_price
@@ -64,6 +55,10 @@ module Abot
64
55
  "#{current_price} (#{percent_to_order.round(2)}%)"
65
56
  end
66
57
 
58
+ def decorated_current_price_perc_of_order
59
+ "#{current_price} (#{percent_from_order.round(2)}%)"
60
+ end
61
+
67
62
  def decorated_min_price
68
63
  "#{min_price} (#{percent_from_min_to_average.round(2)}%)"
69
64
  end
@@ -72,6 +67,10 @@ module Abot
72
67
  "#{max_price} (#{percent_to_max.round(2)}%)"
73
68
  end
74
69
 
70
+ def decorated_max_price_perc_of_order
71
+ "#{max_price} (#{percent_from_max.round(2)}%)"
72
+ end
73
+
75
74
  def decorated_current_quote
76
75
  current_quote.round(2)
77
76
  end
@@ -97,7 +96,7 @@ module Abot
97
96
  sec = timer.to_s[0..9].to_f
98
97
  return '-' if sec < 1_577_836_800 # 2020-01-01 00:00:00 +0000
99
98
 
100
- medidas = ["г", "мес", "д", "ч", "м", "c"]
99
+ medidas = %w[г мес д ч м c]
101
100
  array = [1970, 1, 1, 0, 0, 0]
102
101
  text = ''
103
102
  Time.at(Time.now.to_i - sec).utc.to_a.take(6).reverse.each_with_index do |k, i|
@@ -12,13 +12,18 @@ module Abot
12
12
  magenta
13
13
  ].freeze
14
14
 
15
- def self.daily_profit_str
15
+ def self.today_info_str
16
+ profit_array = DatabaseTable.data_daily_profit
17
+ return "Сегодня сделок не было \n" if profit_array.blank?
18
+
16
19
  profit = 'Профит сегодня:'
17
- DatabaseTable.data_daily_profit.each do |c|
20
+ transactions_count = 'Сделок '
21
+ profit_array.each do |c|
18
22
  tick_size = (c['quote'] == 'BTC' ? 7 : 2)
19
- profit += Paint[" #{c['sum(profit)'].round(tick_size)} #{c['quote']};", :green]
23
+ profit += "#{Paint[" #{c['sum'].round(tick_size)} #{c['quote']}", :green]};"
24
+ transactions_count += "#{c['quote']}: #{Paint[c['count'], :green]}; "
20
25
  end
21
- profit
26
+ "#{profit} #{transactions_count}\n"
22
27
  end
23
28
 
24
29
  def self.balance_str(account, current_coins, trade_params)
@@ -55,8 +60,54 @@ module Abot
55
60
  "#{symbol_name}: " + Paint["#{symbol[:askPrice].to_f} (#{symbol[:priceChangePercent]}%)", percent_color]
56
61
  end
57
62
 
58
- def self.check_abot
59
- Paint['WARNING: ПРОВЕРЬТЕ БОТА !!!', :black, :red] + "\n" if (Time.now - File.mtime(DatabaseTable::DB_PATH)) > 10
63
+ def self.check_abot(current_coins, telegram)
64
+ if (Time.now - File.mtime(DatabaseTable::DB_PATH)) > 40
65
+ if TelegramSender::SENDED_ERROR.blank?
66
+ TelegramSender::SENDED_ERROR[:warn_send] = true
67
+ telegram.send_message('WARNING: ПРОВЕРЬТЕ БОТА !!!') if telegram.present?
68
+ end
69
+ return Paint['WARNING: ПРОВЕРЬТЕ БОТА !!!', :black, :red] + "\n"
70
+ elsif TelegramSender::SENDED_ERROR
71
+ TelegramSender::SENDED_ERROR.clear
72
+ end
73
+
74
+ error_coins = current_coins.map { |c| c.current_price > c.sell_price ? c.name : nil }.compact
75
+ TelegramSender::SENDED_SELL_ERROR.delete_if { |key, _| !error_coins.include?(key) }
76
+
77
+ error_coins.each do |c|
78
+ if TelegramSender::SENDED_SELL_ERROR[c].to_i < 4
79
+ TelegramSender::SENDED_SELL_ERROR[c] = TelegramSender::SENDED_SELL_ERROR[c].to_i + 1
80
+ end
81
+ end
82
+
83
+ error_coins.select! { |s| TelegramSender::SENDED_SELL_ERROR[s].to_i > 2 }
84
+
85
+ return nil if error_coins.blank?
86
+
87
+ if telegram.present? &&
88
+ error_coins.find { |c| TelegramSender::SENDED_SELL_ERROR[c] == 3 }.present? &&
89
+ error_coins.find { |c| TelegramSender::SENDED_SELL_ERROR[c] == 4 }.blank?
90
+ telegram.send_message('WARNING: ПРОВЕРЬТЕ БОТА, ВОЗМОЖНО НУЖЕН РЕБУТ')
91
+ end
92
+ Paint['WARNING: ПРОВЕРЬТЕ БОТА, ВОЗМОЖНО НУЖЕН РЕБУТ', :black, :red] + "\n"\
93
+ end
94
+
95
+ def self.check_coins(account, quote_assets, telegram)
96
+ coins_without_order = account.coins_without_order(quote_assets)
97
+ TelegramSender::SENDED_LOST_COINS.delete_if { |key, _| !coins_without_order.include?(key) }
98
+
99
+ coins_without_order.each do |c|
100
+ if TelegramSender::SENDED_LOST_COINS[c].to_i < 4
101
+ TelegramSender::SENDED_LOST_COINS[c] = TelegramSender::SENDED_LOST_COINS[c].to_i + 1
102
+ end
103
+ end
104
+
105
+ coins_without_order.select! { |s| TelegramSender::SENDED_LOST_COINS[s].to_i > 2 }
106
+
107
+ return nil if coins_without_order.blank?
108
+
109
+ telegram.send_lost_coins(coins_without_order) if telegram.present?
110
+ Paint["Монеты без ордера: #{coins_without_order.join(', ')}", :black, :red] + "\n"\
60
111
  end
61
112
  end
62
113
  end
@@ -6,7 +6,7 @@ module Abot
6
6
  class Table < Terminal::Table
7
7
  include Abot::Info::Helpers
8
8
 
9
- attr_reader :current_coins, :account, :rate_coins, :columns, :trade_params
9
+ attr_reader :current_coins, :account, :rate_coins, :columns, :trade_params, :telegram
10
10
 
11
11
  ALL_HEADINGS = {
12
12
  name: 'Coin',
@@ -22,6 +22,8 @@ module Abot
22
22
  potential_profit: "Пот.\nпрофит",
23
23
  buy_date: "Дата покупки",
24
24
  timer: 'Время',
25
+ current_price_perc_of_order: 'Тек.цена',
26
+ max_price_perc_of_order: 'Максимум 24h',
25
27
  }.freeze
26
28
  HEADINGS_COINS_TABLE_DEFAULT = %i[
27
29
  name
@@ -55,12 +57,13 @@ module Abot
55
57
  @rate_coins = options.fetch(:rate_coins, [])
56
58
  @trade_params ||= DatabaseTable.data_settings
57
59
  @current_coins = Coin.current_coins(account, trade_params)
58
-
60
+ @telegram = Info::TelegramSender.new if options[:telegram_enable] == 'true'
59
61
 
60
62
  options = {
61
63
  headings: columns.map { |h| { value: ALL_HEADINGS[h], alignment: :center } },
62
64
  style: { border_x: '=' }
63
65
  }
66
+
64
67
  super options
65
68
  end
66
69
 
@@ -95,11 +98,12 @@ module Abot
95
98
  end
96
99
 
97
100
  def info_str
98
- "#{Helpers.check_abot}" \
99
- "#{Helpers.daily_profit_str}\n" \
100
- "#{Helpers.balance_str(account, current_coins, trade_params)}\n" \
101
- "#{Helpers.potential_balance_str(account, current_coins, trade_params)}\n" \
102
- "#{rate_coins_str}"
101
+ "#{Helpers.check_coins(account, trade_params["quote_asset"].split(' '), telegram)}" \
102
+ "#{Helpers.check_abot(current_coins, telegram)}" \
103
+ "#{Helpers.today_info_str}" \
104
+ "#{Helpers.balance_str(account, current_coins, trade_params)}\n" \
105
+ "#{Helpers.potential_balance_str(account, current_coins, trade_params)}\n" \
106
+ "#{rate_coins_str}"
103
107
  end
104
108
 
105
109
  def rate_coins_str
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abot
4
+ module Info
5
+ class TelegramSender
6
+ attr_reader :chat_id, :token
7
+ SENDED_LOST_COINS = {}
8
+ SENDED_ERROR = {}
9
+ SENDED_SELL_ERROR = {}
10
+
11
+ def initialize
12
+ @chat_id = DatabaseTable::TG_NAME
13
+ @token = DatabaseTable::TG_TOKEN
14
+ end
15
+
16
+ def send_message(text)
17
+ Telegram::Bot::Client.run(token) do |bot|
18
+ message = bot.api.send_message(chat_id: chat_id, text: text)
19
+ if message['ok'] == true
20
+ bot.api.pin_chat_message(chat_id: chat_id, message_id: message.dig('result', 'message_id'))
21
+ end
22
+ end
23
+ end
24
+
25
+ def send_lost_coins(coins_without_order)
26
+ coins_without_order.each do |coin|
27
+ send_message("WARNING обнаружена монета без ордера: #{coin}") if SENDED_LOST_COINS[coin].to_i == 3
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Abot
4
4
  module Info
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.8'
6
6
  end
7
7
  end
data/lib/abot/info.rb CHANGED
@@ -5,12 +5,14 @@ require 'sqlite3'
5
5
  require 'terminal-table'
6
6
  require 'paint'
7
7
  require 'binance-ruby'
8
+ require 'telegram/bot'
8
9
 
9
10
  require_relative 'info/option_parser'
10
11
  require_relative 'info/coin'
11
12
  require_relative 'info/binance_account'
12
13
  require_relative 'info/table'
13
14
  require_relative 'info/version'
15
+ require_relative 'info/telegram_sender'
14
16
 
15
17
  module Abot
16
18
  module Info
@@ -43,6 +45,8 @@ module Abot
43
45
  " timer: Время с покупки\n" \
44
46
  " current_profit: Текущая прибыль\n" \
45
47
  " potential_profit: Потенциальная прибыль\n",
48
+ " current_price_perc_of_order: Текущая цена (подсчет процента от ордера за продажу)\n" \
49
+ " max_price_perc_of_order: Максимальная цена за 24ч (подсчет процента от ордера за продажу)\n" \
46
50
  )
47
51
  parser.add_option(
48
52
  :del, '--del=COLUMN1,COLUMN2,COLUMN3',
@@ -60,6 +64,8 @@ module Abot
60
64
  " timer: Время с покупки\n" \
61
65
  " current_profit: Текущая прибыль\n" \
62
66
  " potential_profit: Потенциальная прибыль\n",
67
+ " current_price_perc_of_order: Текущая цена (подсчет процента от ордера за продажу)\n" \
68
+ " max_price_perc_of_order: Максимальная цена за 24ч (подсчет процента от ордера за продажу)\n"
63
69
  )
64
70
  parser.add_option(
65
71
  :db_path, '--db_path=DB_PATH',
@@ -70,11 +76,19 @@ module Abot
70
76
  :symbols, '--symbols=SYMBOL1,SYMBOL2,SYMBOL3',
71
77
  'Пары для отображения курсов',
72
78
  )
79
+ parser.add_option(
80
+ :telegram_enable, '--telegram_enable=true',
81
+ 'Отправка в телеграм канал справочной информации',
82
+ )
83
+ parser.add_option(
84
+ :update_time, '--update_time=SECONDS',
85
+ 'Период обновления информации, секунд, мин. 10',
86
+ )
73
87
  end.final!
74
88
 
75
89
  require_relative 'info/database_table'
76
90
 
77
- start_stats({ rate_coins: rate_coins, columns: columns })
91
+ start_stats({ rate_coins: rate_coins, columns: columns, telegram_enable: telegram_enable, update_time: update_time })
78
92
  end
79
93
 
80
94
  def start_stats(opts)
@@ -88,7 +102,7 @@ module Abot
88
102
  puts "ERROR: #{e.message}"
89
103
  end
90
104
 
91
- sleep 10
105
+ sleep update_time
92
106
  end
93
107
  end
94
108
 
@@ -102,6 +116,10 @@ module Abot
102
116
  @template ||= Info::OptionParser.instance.options[:template]
103
117
  end
104
118
 
119
+ def telegram_enable
120
+ @telegram_enable ||= Info::OptionParser.instance.options[:telegram_enable]
121
+ end
122
+
105
123
  def columns
106
124
  add = Info::OptionParser.instance.options[:add]&.split(',')&.map { |m| m.to_sym } || []
107
125
  del = Info::OptionParser.instance.options[:del]&.split(',')&.map { |m| m.to_sym } || []
@@ -121,6 +139,11 @@ module Abot
121
139
  def binance_account
122
140
  BinanceAccount.new(DatabaseTable::API_KEY, DatabaseTable::SECRET_KEY)
123
141
  end
142
+
143
+ def update_time
144
+ sec = Info::OptionParser.instance.options[:update_time].to_i
145
+ sec > 10 ? sec : 10
146
+ end
124
147
  end
125
148
  end
126
149
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abot-info
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - w_dmitrii
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-21 00:00:00.000000000 Z
11
+ date: 2021-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: 1.3.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: telegram-bot-ruby
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.16.0
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.16.0
125
139
  description:
126
140
  email:
127
141
  - wiz.work2021@gmail.com
@@ -146,6 +160,7 @@ files:
146
160
  - lib/abot/info/helpers.rb
147
161
  - lib/abot/info/option_parser.rb
148
162
  - lib/abot/info/table.rb
163
+ - lib/abot/info/telegram_sender.rb
149
164
  - lib/abot/info/version.rb
150
165
  - spec/abot/statistics_spec.rb
151
166
  homepage: