From d51e5cf469f66ad8f82f5bcb29baccd6d1e8385f Mon Sep 17 00:00:00 2001 From: Aleksey Kurepin Date: Mon, 27 Apr 2020 04:08:02 +0300 Subject: [PATCH] initial commit --- .env | 2 + .gitignore | 2 + .ruby-version | 1 + Gemfile | 11 ++++ Gemfile.lock | 59 +++++++++++++++++++++ bin/console | 10 ++++ bin/portfolio | 3 ++ config/dotenv.rb | 7 +++ config/oj.rb | 6 +++ lib/tinky_client.rb | 124 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 225 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100755 bin/console create mode 100755 bin/portfolio create mode 100644 config/dotenv.rb create mode 100644 config/oj.rb create mode 100644 lib/tinky_client.rb diff --git a/.env b/.env new file mode 100644 index 0000000..de1008f --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +TINKOFF_OPENAPI_URL=https://api-invest.tinkoff.ru/openapi +TINKOFF_OPENAPI_TOKEN= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9797a6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env.local +.env.*.local diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..860487c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.7.1 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..a5485e0 --- /dev/null +++ b/Gemfile @@ -0,0 +1,11 @@ +source 'https://rubygems.org' + +gem 'dotenv' + +gem 'faraday' +gem 'faraday_middleware' +gem 'faraday_middleware-parse_oj' + +gem 'awesome_pry' + +gem 'tty-table' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..85bc2d0 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,59 @@ +GEM + remote: https://rubygems.org/ + specs: + awesome_print (1.8.0) + awesome_pry (0.0.1) + awesome_print + pry-rails + coderay (1.1.2) + dotenv (2.7.5) + equatable (0.6.1) + faraday (0.17.3) + multipart-post (>= 1.2, < 3) + faraday_middleware (0.14.0) + faraday (>= 0.7.4, < 1.0) + faraday_middleware-parse_oj (0.3.2) + faraday (~> 0.9) + faraday_middleware (>= 0.9.1, < 1.0) + oj (>= 2.0, < 4.0) + method_source (1.0.0) + multipart-post (2.1.1) + necromancer (0.6.0) + oj (3.10.6) + pastel (0.7.3) + equatable (~> 0.6) + tty-color (~> 0.5) + pry (0.13.1) + coderay (~> 1.1) + method_source (~> 1.0) + pry-rails (0.3.9) + pry (>= 0.10.4) + strings (0.1.8) + strings-ansi (~> 0.1) + unicode-display_width (~> 1.5) + unicode_utils (~> 1.4) + strings-ansi (0.2.0) + tty-color (0.5.1) + tty-screen (0.7.1) + tty-table (0.11.0) + equatable (~> 0.6) + necromancer (~> 0.5) + pastel (~> 0.7.2) + strings (~> 0.1.5) + tty-screen (~> 0.7) + unicode-display_width (1.7.0) + unicode_utils (1.4.0) + +PLATFORMS + ruby + +DEPENDENCIES + awesome_pry + dotenv + faraday + faraday_middleware + faraday_middleware-parse_oj + tty-table + +BUNDLED WITH + 2.1.4 diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..ea304df --- /dev/null +++ b/bin/console @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +require 'bundler/setup' +require 'pry' +require 'awesome_print' +AwesomePrint.pry! + +require_relative '../lib/tinky_client' + +Pry.start(TinkyClient) diff --git a/bin/portfolio b/bin/portfolio new file mode 100755 index 0000000..b3b6189 --- /dev/null +++ b/bin/portfolio @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require_relative '../lib/tinky_client' +TinkyClient.portfolio diff --git a/config/dotenv.rb b/config/dotenv.rb new file mode 100644 index 0000000..8bc88ee --- /dev/null +++ b/config/dotenv.rb @@ -0,0 +1,7 @@ +require 'dotenv' +Dotenv.load( + ".env.#{ENV['APP_ENV']}.local", + '.env.local', + ".env.#{ENV['APP_ENV']}", + '.env' +) diff --git a/config/oj.rb b/config/oj.rb new file mode 100644 index 0000000..99ad3f3 --- /dev/null +++ b/config/oj.rb @@ -0,0 +1,6 @@ +require 'oj' + +Oj.default_options = { + mode: :compat, + symbol_keys: true +} diff --git a/lib/tinky_client.rb b/lib/tinky_client.rb new file mode 100644 index 0000000..1c14326 --- /dev/null +++ b/lib/tinky_client.rb @@ -0,0 +1,124 @@ +require './config/dotenv' +require './config/oj' + +require 'faraday' +require 'faraday_middleware' +require 'faraday_middleware/parse_oj' + +require 'tty/table' + +require 'pry' +require 'awesome_print' + +module TinkyClient + CURRENCIES = { RUB: '₽', USD: '$', EUR: '€' }.freeze + + class Client + attr_reader :connection + + def initialize + @connection = Client.make_connection(ENV['TINKOFF_OPENAPI_URL']) + end + + def get_portfolio + get_data('portfolio') + end + + private + def get_data(url) + request(:get, url) + end + + def request(method, url, params = {}) + response = connection.public_send(method, url, params) + + if response.success? + response.body + else + handle_error(response) + end + end + + def handle_error(response) + raise ClientError, "Tinkoff responded with HTTP #{response.status}: #{response.body.ai}" + end + + class << self + def make_connection(url) + Faraday.new(url: url) do |builder| + builder.request :json + builder.authorization :Bearer, ENV['TINKOFF_OPENAPI_TOKEN'] + builder.response :oj, content_type: 'application/json' + builder.adapter Faraday.default_adapter + end + end + end + end + + class ClientError < StandardError; end + + class << self + def portfolio + puts + + summary = client.get_portfolio + + table = TTY::Table.new(header: ['Type', 'Name', 'Amount', 'Yield']) + + summary.dig(:payload, :positions).each do |p| + currency = CURRENCIES[p[:expectedYield][:currency].to_sym] + decorated_yield = decorate_value(p[:expectedYield][:value], currency) + + table << [ + p[:instrumentType].upcase, + decorate_name(p[:name]), + { value: decorate_amount(p[:balance]), alignment: :right }, + { value: decorated_yield, alignment: :right } + ] + end + + puts table.render(:ascii, padding: [0,1,0,1]) + puts + end + + private + def client + @client ||= Client.new + end + + def pastel + @pastel ||= Pastel.new + end + + def decorate_value(value, currency) + color = if value.positive? + :green + elsif value.negative? + :red + else + :clear + end + + formatted_value = sprintf("%+d #{currency}", value) + pastel.decorate(formatted_value, color) + end + + def decorate_amount(amount) + if amount == amount.to_i + amount.round + else + amount + end + end + + def decorate_name(name) + stripped_name = if name.length > 29 + name[0..28] + '…' + else + name + end + + pastel.bold(stripped_name) + end + end +end