├── .gitignore ├── MANIFEST.in ├── setup.py ├── UNLICENSE ├── README.md └── aboki /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | dist 3 | build 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md setup.py UNLICENSE 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath, dirname, join, normpath 2 | from setuptools import setup 3 | 4 | 5 | setup( 6 | 7 | # Basic package information: 8 | name = 'aboki', 9 | version = '0.3', 10 | scripts = ('aboki', ), 11 | 12 | # Packaging options: 13 | zip_safe = False, 14 | include_package_data = True, 15 | 16 | # Package dependencies: 17 | install_requires = ['docopt>=0.6.2', 'requests>=2.13.0', 'beautifulsoup4>=4.5.3', 'html5lib>=0.999999999'], 18 | 19 | # Metadata for PyPI: 20 | author = 'Akinjide Bankole', 21 | author_email = 'r@akinjide.me', 22 | license = 'UNLICENSE', 23 | url = 'https://github.com/akinjide/aboki', 24 | keywords = 'forex cli utility fx currency abokifx aboki market rate', 25 | description = 'Black market currency rate instantly in your terminal!', 26 | long_description = open(normpath(join(dirname(abspath(__file__)), 'README.md'))).read() 27 | ) -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aboki 2 | 3 | Black market currency rate instantly in your terminal! (Powered by 4 | [AbokiFx](https://abokifx.com/).) 5 | 6 | NOTE: I am in no way affiliated with AbokiFx. I don't know anyone that works there, have no relationship with them -- nothing. 7 | 8 | 9 | ## Prerequisites 10 | 11 | Before using `aboki`, you should be familiar with foreign exchange and black market... *Duh!* 12 | 13 | 14 | ## Installation 15 | 16 | You can install `aboki` via [pip](http://pip.readthedocs.org/en/latest/): 17 | 18 | ```bash 19 | $ pip install aboki 20 | ``` 21 | 22 | Once `aboki` has been installed, you'll can start by running: 23 | 24 | ```bash 25 | $ aboki test 26 | ``` 27 | 28 | ## Usage 29 | 30 | If you simply run `aboki` on the command line, you'll get a list of help. 31 | 32 | ```bash 33 | $ aboki recent # show recent exchange rates 34 | $ aboki rates # show current exchange rates 35 | $ aboki rate # show exchange rate for currency 36 | $ aboki convert # see how much money you'll get if you sell 37 | $ aboki test # test aboki 38 | $ aboki (-h | --help) # display help information 39 | ``` 40 | 41 | All commands that have side effects will prompt you for action before doing anything for added security. 42 | 43 | ## Changelog 44 | 45 | v0.3: 05-09-2017 46 | 47 | - Python 3 Support and Error handling. 48 | 49 | v0.2: 04-16-2017 50 | 51 | - Fixing some small documentation issues. 52 | 53 | v0.1: 04-12-2017 54 | 55 | - First release! 56 | 57 | 58 | ## Like This? 59 | 60 | If you've enjoyed using `aboki`, feel free to star this project, send me some bitcoin! My address is: 61 | 62 | **17AcFdn8kTpGw1R34MC5U5SyZHrMbZK4Sq** 63 | 64 | Or... You could tip me on [paypal](https://www.paypal.me/akinjide) :) 65 | 66 | <3 67 | 68 | -Akinjide 69 | -------------------------------------------------------------------------------- /aboki: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | NAME: 4 | aboki - 5 | 6 | DESCRIPTION: 7 | Black market currency rate instantly in your terminal! (Powered by AbokiFx: 8 | https://abokifx.com/). 9 | 10 | USAGE: 11 | aboki recent 12 | aboki rates 13 | aboki rate 14 | aboki convert 15 | aboki test 16 | aboki (-h | --help) 17 | aboki --version 18 | 19 | OPTIONS: 20 | (string) Specify rate type for this operation: cbn, movement, 21 | lagos_previous, moneygram, westernunion, otherparallel. This 22 | defaults to 'cbn'. 23 | (string) Specify what currency rate to show. 24 | (float) The amount you'd like to convert. 25 | (string) Specify the currency you are converting from. 26 | (string) Specify the currency you are converting to. 27 | -h --help Show this screen. 28 | --version Show version. 29 | 30 | EXAMPLES: 31 | o Convert usd to ngn 32 | 33 | The following convert command converts a currency to a specified currency: 34 | 35 | aboki convert 500 usd ngn 36 | 37 | Output: 38 | 39 | Conversion Successful 40 | SEE HOW MUCH YOU GET IF YOU SELL 41 | ================================ 42 | { 43 | "ngn": 202500.0, 44 | "rate": 405.0, 45 | "usd": 500.0 46 | } 47 | ================================ 48 | 49 | o Retrieve usd rate 50 | 51 | The following rate command retrieves currency current rate: 52 | 53 | aboki rate usd 54 | 55 | Output: 56 | 57 | USD Exchange Rate 58 | ================= 59 | 387.0 60 | ================= 61 | 62 | Written by Akinjide Bankole . Like the software? Star 63 | on Github 64 | Or... Send a tip to Akinjide: 17AcFdn8kTpGw1R34MC5U5SyZHrMbZK4Sq 65 | """ 66 | 67 | 68 | from __future__ import absolute_import, print_function 69 | from json import dumps 70 | from sys import exit 71 | 72 | from bs4 import BeautifulSoup 73 | from docopt import docopt 74 | from requests import get, post, exceptions 75 | from pkg_resources import get_distribution 76 | 77 | 78 | # Globals 79 | API_URI = 'https://www.abokifx.com' 80 | VERSION = 'aboki %s' % get_distribution("aboki").version 81 | 82 | 83 | class Aboki: 84 | 85 | def __init__(self): 86 | self.currencies = ('usd', 'gbp', 'eur') 87 | self.types = ['cbn', 'movement', 'lagos_previous', 'moneygram', 'westernunion', 'otherparallel'] 88 | self.quotes = 'Quotes:\t*morning\t**midday\t***evening' 89 | self.note = '**NOTE**: Buy / Sell => 90 / 100\n' 90 | 91 | def make_request(self, path, params={}, data={}, method='GET'): 92 | """Make the specified API request, and return the HTML data, or quit 93 | with an error. 94 | """ 95 | 96 | try: 97 | if method.lower() == 'post': 98 | resp = post('%s/%s' % (API_URI, path), params=params, data=dumps(data), headers={'Content-Type': 'application/json'}) 99 | else: 100 | resp = get('%s/%s' % (API_URI, path), params=params) 101 | 102 | if resp.status_code != 200: 103 | print('Error connecting. Please try again.') 104 | print('If the problem persists, please email r@akinjide.me.') 105 | exit(1) 106 | return resp.text 107 | 108 | except (exceptions.RequestException, e): # When exceptions are more than one, they need to be wrapped in parenthesis in py3 109 | print('Error connecting. Please check network and try again.') 110 | print('If the problem persists, please email r@akinjide.me.') 111 | exit(1) 112 | 113 | def parse(self, content): 114 | soup = BeautifulSoup(content, "html5lib") 115 | records = soup.select("body .lagos-market-rates table tbody > tr") 116 | data = [[str(j) for j in record.stripped_strings 117 | if str(j) not in ('NGN', 'Buy / Sell')] for record in records] 118 | return {'title': str(soup.title.string), 'data': data} 119 | 120 | def get_current_rate(self): 121 | """Return the current exchange rate for USD, GBP, EUR.""" 122 | 123 | resp = self.make_request('') 124 | rjson = self.parse(resp) 125 | rates = ' / '.join(rjson['data'][0][1:]).split(' / ') 126 | return dict(zip(self.currencies, [float(rate) for rate in rates if '*' not in rate])) 127 | 128 | def recent(self): 129 | """List recent exchange rates for USD, GBP, EUR.""" 130 | 131 | resp = self.make_request('') 132 | rjson = self.parse(resp) 133 | underline = '==' * len(rjson['title']) 134 | 135 | print(self.quotes) 136 | print(self.note) 137 | print(rjson['title']) 138 | print(underline) 139 | print('\t\t\t\t%s\t\t\t%s\t\t%s' % ('USD', 'GBP', 'EUR')) 140 | 141 | for datum in rjson['data']: 142 | print('\t|\t'.join(datum)) 143 | 144 | print(underline) 145 | 146 | def rates(self, type): 147 | """List current exchange rates. 148 | 149 | Supported types: cbn, movement, lagos_previous, moneygram, westernunion and otherparallel. 150 | """ 151 | 152 | param = None 153 | if type in self.types: 154 | param = {'rates': self.types[self.types.index(type)]} 155 | else: 156 | print('\nNot sure which type?') 157 | print('You can specify any of this: ' 158 | 'cbn, movement, lagos_previous, moneygram, westernunion or otherparallel\n' 159 | '[default: cbn]\n') 160 | param = {'rates': 'cbn'} 161 | 162 | resp = self.make_request('ratetypes', param) 163 | rjson = self.parse(resp) 164 | underline = '==' * len(rjson['title']) 165 | 166 | if type in ('otherparallel', 'movement'): 167 | print(self.quotes) 168 | print(self.note) 169 | elif type == 'lagos_previous': 170 | print(self.note) 171 | 172 | print(rjson['title']) 173 | print(underline) 174 | 175 | for index, data in enumerate(rjson['data']): 176 | if index == 0: 177 | print('\t\t\t%s' % '\t\t\t'.join(data)) 178 | else: 179 | print('\t|\t'.join(data)) 180 | 181 | print(underline) 182 | 183 | def rate(self, currency): 184 | """List current exchange rate for currency.""" 185 | 186 | rates = self.get_current_rate() 187 | xc = rates.get(currency) 188 | 189 | if xc: 190 | print('%s Exchange Rate' % (currency.upper())) 191 | print('================') 192 | print(xc) 193 | print('================') 194 | else: 195 | print('\nNot sure which type?') 196 | print('You can specify any of this: usd, gbp, eur or ngn\n') 197 | 198 | def convert(self, amount, FROM, TO): 199 | """Convert currency with current rate.""" 200 | 201 | rates = self.get_current_rate() 202 | FROM = FROM.strip().lower() 203 | TO = TO.strip().lower() 204 | error = 'Oops! You are trying to do something useful. Please use any available currencies for conversion' 205 | 206 | try: 207 | assert FROM and TO and 'ngn' in (FROM, TO), error 208 | assert FROM in self.currencies or TO in self.currencies, error 209 | except (AssertionError): # Wrapped exceptions in parenthesis 210 | print(error) # AssertionError isn't handled during conversion because 'e' is undefined 211 | exit(1) 212 | 213 | ojson = {FROM: amount} 214 | if FROM == 'ngn': 215 | ojson[TO] = amount / rates[TO] 216 | ojson['rate'] = rates[TO] 217 | elif FROM in self.currencies: 218 | ojson['ngn'] = amount * rates[FROM] 219 | ojson['rate'] = rates[FROM] 220 | 221 | print('Conversion Successful') 222 | print('SEE HOW MUCH YOU GET IF YOU SELL') 223 | print('================================') 224 | print(dumps(ojson, sort_keys=True, indent=2, separators=(',', ': '))) 225 | print('================================') 226 | 227 | def test(self): 228 | """Test to make sure everything's working.""" 229 | 230 | resp = self.make_request('') 231 | if resp: 232 | print("Yippe! you've broken nothing!") 233 | else: 234 | print("Oops! Catastrophic Failure") 235 | print('If the problem persists, please email r@akinjide.me.') 236 | exit(1) 237 | 238 | 239 | def main(): 240 | """Handle programmer input, and do stuffs.""" 241 | 242 | arguments = docopt(__doc__, version=VERSION) 243 | aboki = Aboki() 244 | 245 | if arguments['recent']: 246 | aboki.recent() 247 | elif arguments['rates']: 248 | aboki.rates(arguments['']) 249 | elif arguments['rate']: 250 | aboki.rate(arguments['']) 251 | elif arguments['convert']: 252 | amount = float(arguments['']) 253 | aboki.convert(amount, arguments[''], arguments['']) 254 | elif arguments['test']: 255 | aboki.test() 256 | 257 | 258 | if __name__ == '__main__': 259 | main() 260 | --------------------------------------------------------------------------------