Skip to content

Commit

Permalink
Create Bot ;-).
Browse files Browse the repository at this point in the history
  • Loading branch information
karreiro committed Sep 26, 2018
1 parent b21a657 commit 797df5c
Show file tree
Hide file tree
Showing 13 changed files with 461 additions and 4 deletions.
6 changes: 5 additions & 1 deletion Gemfile
@@ -1,2 +1,6 @@
ruby '2.4.4'

source 'https://rubygems.org'
gem 'sinatra'
gem 'sinatra', '2.0.2'
gem 'httparty'
gem 'google-api-client'
76 changes: 76 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,76 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
declarative (0.0.10)
declarative-option (0.1.0)
faraday (0.13.1)
multipart-post (>= 1.2, < 3)
google-api-client (0.19.6)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
googleauth (0.6.2)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
logging (~> 2.0)
memoist (~> 0.12)
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
httparty (0.15.7)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
jwt (2.1.0)
little-plugger (1.1.4)
logging (2.2.2)
little-plugger (~> 1.1)
multi_json (~> 1.10)
memoist (0.16.0)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
mustermann (1.0.3)
os (0.9.6)
public_suffix (2.0.5)
rack (2.0.5)
rack-protection (2.0.2)
rack
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.1)
signet (0.8.1)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
sinatra (2.0.2)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.2)
tilt (~> 2.0)
tilt (2.0.8)
uber (0.1.0)

PLATFORMS
ruby

DEPENDENCIES
google-api-client
httparty
sinatra (= 2.0.2)

RUBY VERSION
ruby 2.4.4p296

BUNDLED WITH
1.16.5
20 changes: 20 additions & 0 deletions LICENSE.txt
@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors, https://js.foundation/

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46 changes: 46 additions & 0 deletions README.md
@@ -0,0 +1,46 @@
# Arnaldo Bot

A simple bot for helping you with your financial life, read more [here](http://karreiro.com/2018/09/25/financial-bot.html).


## Demo
[![Demo](http://karreiro.com/assets/demo-bot.gif "Demo")](http://karreiro.com/assets/demo-bot.gif)


## Configuring

Configure an instance of the bot by following the next 3 simple steps:

### 1) Telegram environment variables

First of all, configure the **TELEGRAM_TOKEN** and the **TELEGRAM_ID** environment variables.

You'll need to create a bot on Telegram, by adding the *BotFather* and following the instructions there. He will help you with everything and, in the end, you will get the bot token.

Considering that the token is something like `"111111111:AAAAaa11aaaaAA1aAAaAA1AAaAaaaAaaaAA"`, the **TELEGRAM_TOKEN** value must be `"bot111111111:AAAAaa11aaaaAA1aAAaAA1AAaAaaaAaaaAA"` (noticed that it's necessary to add the word _bot_ in the beginning).

For the **TELEGRAM_ID**, variable, which your user id, you can ask for it to the @jsondumpbot ;-)


### 2) Google API environment variables

The variables configured in this steps are: **GOOGLE_ACCOUNT_TYPE**, **GOOGLE_CLIENT_EMAIL** and **GOOGLE_PRIVATE_KEY**.

First, go to [Google Developers Console](https://console.developers.google.com/project), click the Library section, enable Google Sheets API, and finally, in the Service account section, create a service account without roles, with the "G Suite Domain-wide Delegation" enabled.

Finally, click on the "Create key" button and Download the JSON file with the three variables that we need.


### 3) The spreadsheet

Create a spreadsheet on Google Drive. The URL is something like this `https://docs.google.com/spreadsheets/d/AAAAAA/edit`, and the `AAAAAA` is the **SPREADSHEET_ID** that we're looking for.

Finally, share the spreadsheet with the e-mail from the **GOOGLE_CLIENT_EMAIL** variable, and boom! It's done :-)


## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
60 changes: 60 additions & 0 deletions adalberto.rb
@@ -0,0 +1,60 @@
class Adalberto

attr_reader :message

def initialize(message)
@message = message
end

def execute!
return unless message.valid?
return unless message.valid_sender?

if message.expense?
say append_expense(message)
else
say excuse(message)
end
end

private

def append_expense(message)
finance_spreadsheet.append_expense finance_entry(message)
success
rescue
'Oops... Something went wrong! ☹️'
end

def success
[
'Ok... done! 😉',
'Done! 😁',
'Ok, ok... done 😒',
].sample
end

def finance_entry(message)
FinanceEntry.new(message)
end

def excuse(message)
[
'I\'m not sure if I can help you right now.. 🧐',
'IDK.. 🤔',
'Hmmmmmmmmmm.. 😶',
].sample
end

def say(message)
telegram.send(message)
end

def finance_spreadsheet
@finance_spreadsheet ||= FinanceSpreadsheet.new
end

def telegram
@telegram = Telegram.new
end
end
21 changes: 19 additions & 2 deletions app.rb
@@ -1,5 +1,22 @@
require 'sinatra'
require 'google/apis/sheets_v4'
require 'httparty'
require 'cgi'
require './adalberto'
require './category_engine'
require './finance_entry'
require './finance_spreadsheet'
require './google_sheets_api'
require './message'
require './telegram'

get '/' do
"Hello World!"
post '/' do

json = JSON.parse(request.body.read)
message = Message.new(json)
adalberto = Adalberto.new(message)

adalberto.execute!

status 200
end
45 changes: 45 additions & 0 deletions category_engine.rb
@@ -0,0 +1,45 @@
class CategoryEngine

attr_reader :message

def initialize(message)
@message = message
end

def category
return 'entertainment' if entertainment?
return 'market' if market?
return 'dinner' if dinner?
'other'
end

private

def entertainment?
keywords = %w(beer party)

keywords.any? do |keyword|
raw_message.include?(keyword)
end
end

def market?
keywords = %w(market shopping)

keywords.any? do |keyword|
raw_message.include?(keyword)
end
end

def dinner?
keywords = %w(food dinner)

keywords.any? do |keyword|
raw_message.include?(keyword)
end
end

def raw_message
message.text.downcase
end
end
2 changes: 1 addition & 1 deletion config.ru
@@ -1,2 +1,2 @@
require './hello'
require './app'
run Sinatra::Application
38 changes: 38 additions & 0 deletions finance_entry.rb
@@ -0,0 +1,38 @@
class FinanceEntry

attr_reader :message

def initialize(message)
@message = message
end

def raw
[[id, date, value, category, raw_message]]
end

private

def id
message.id
end

def date
message.date
end

def value
message.expense_value
end

def category
category_engine.category
end

def category_engine
CategoryEngine.new(message)
end

def raw_message
message.text
end
end
25 changes: 25 additions & 0 deletions finance_spreadsheet.rb
@@ -0,0 +1,25 @@
class FinanceSpreadsheet

attr_reader :google_sheets_api

def initialize
@google_sheets_api = GoogleSheetsApi.new(ENV['SPREADSHEET_ID'])
end

def append_expense(finance_entry)
append_data(finance_entry)
rescue
create_sheet(finance_entry)
end

private

def append_data(finance_entry)
google_sheets_api.append_data(finance_entry.raw)
end

def create_sheet(finance_entry)
google_sheets_api.create_sheet
google_sheets_api.append_data(finance_entry.raw)
end
end

0 comments on commit 797df5c

Please sign in to comment.