├── .gitignore ├── README.md ├── images ├── screenshot.png └── screenshot_grafana.png ├── requirements.txt ├── setup.py └── uber_cli ├── __init__.py └── writers.py /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | *.pyc 3 | env.sh 4 | *.db 5 | .idea 6 | build/ 7 | dist/ 8 | *.egg-info/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Всё плохо, API сломали когда объединили Uber и Яндекс 2 | 3 | [![Code Health](https://landscape.io/github/strizhechenko/uber-cli/master/landscape.svg?style=flat)](https://landscape.io/github/strizhechenko/uber-cli/master) 4 | 5 | # Как выглядит 6 | 7 | ## Утилита 8 | 9 | ``` shell 10 | $ uber-cli --help 11 | Usage: uber-cli [options] 12 | 13 | Options: 14 | -h, --help show this help message and exit 15 | -i SECONDS delay between queries 16 | -w, --watch run query loop 17 | -o, --one-line writer format 18 | -d, --dict writer format 19 | --influxdb-format writer format 20 | --influxdb-url=URL Example: http://127.0.0.1:8086/write?db=my_db 21 | -f FAIR_PRICE, --fair-price=FAIR_PRICE 22 | defines fair price that ok to order taxi 23 | $ uber-cli -w "New bar" "Jawsspot" 24 | Time : Product : Min : Max 25 | 2017-05-09 16:10:49.298236 : uberSTART : 66.0 : 82.0 26 | 2017-05-09 16:11:21.143715 : uberSTART : 66.0 : 82.0 27 | 2017-05-09 16:11:52.638423 : uberSTART : 66.0 : 82.0 28 | ``` 29 | 30 | ## Графики 31 | 32 | ![Скриншот](/images/screenshot_grafana.png) 33 | 34 | # Установка 35 | 36 | ``` shell 37 | pip install uber-cli 38 | ``` 39 | 40 | # Конфигурация 41 | 42 | В `$HOME/.uberrc` можно определить несколько переменных 43 | 44 | ``` 45 | DEFAULT_CITY: "Екатеринбург" 46 | DEFAULT_TYPE: "uberSTART" 47 | SERVER_TOKEN: "PUT-YOUR-SERVER-TOKEN-HERE" 48 | PHRASE: "Yo-ho-ho, uber is cheap" 49 | ``` 50 | 51 | - DEFAULT_CITY - чтобы лучше определялась геопозиция лучше указать город в котором закзаываем. Он будет автоматом добавляться при запросе геопозиции. 52 | - DEFAULT_TYPE - тариф убера, который используется там, где показывается только один тариф. 53 | - SERVER_TOKEN - токен, полученный при регистрации приложения в Uber. 54 | - PHRASE - сообщение которое произносится/выводится в случае, если цена на такси подходит вам. 55 | 56 | # Регистрация на Uber API 57 | 58 | На сайте Uber: https://developer.uber.com/products/ride-requests 59 | -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strizhechenko/uber-cli/b89e1cb1ddc09c0510f0f0ffc9f2ea59c202de6e/images/screenshot.png -------------------------------------------------------------------------------- /images/screenshot_grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strizhechenko/uber-cli/b89e1cb1ddc09c0510f0f0ffc9f2ea59c202de6e/images/screenshot_grafana.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | pygeocoder 3 | uber_rides 4 | requests 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup and build script for the netutils-linux.""" 4 | 5 | import os 6 | import setuptools 7 | 8 | 9 | def read(*paths): 10 | """Build a file path from *paths* and return the contents.""" 11 | with open(os.path.join(*paths), 'r') as f: 12 | return f.read() 13 | 14 | 15 | setuptools.setup( 16 | name='uber-cli', 17 | version='1.0.0', 18 | author='Oleg Strizhechenko', 19 | author_email='oleg.strizhechenko@gmail.com', 20 | license='MIT', 21 | url='https://github.com/strizhechenko/uber-cli', 22 | keywords='uber cli rides taxi util', 23 | description='Unofficial read-only Uber CLI on top of uber-rides.', 24 | packages=['uber_cli'], 25 | entry_points={ 26 | 'console_scripts': [ 27 | 'uber-cli=uber_cli.__init__:main', 28 | ], 29 | }, 30 | install_requires=['pyyaml', 'uber-rides', 'pygeocoder'], 31 | classifiers=[ 32 | 'Development Status :: 3 - Alpha', 33 | 'Intended Audience :: Developers', 34 | 'Operating System :: MacOS', 35 | 'Operating System :: POSIX', 36 | 'Programming Language :: Python', 37 | 'Programming Language :: Python :: 3', 38 | 'Topic :: Software Development', 39 | 'Topic :: Utilities', 40 | ], 41 | ) 42 | -------------------------------------------------------------------------------- /uber_cli/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import os 5 | import time 6 | import optparse 7 | import yaml 8 | from pygeocoder import Geocoder 9 | from uber_rides.session import Session 10 | from uber_rides.client import UberRidesClient 11 | from uber_cli import writers 12 | 13 | 14 | class Place(object): 15 | """ Описание места """ 16 | def __init__(self, addr, conf): 17 | self.addr = addr 18 | self.city = conf.get('DEFAULT_CITY') 19 | 20 | def with_city(self): 21 | return "{0}, {1}".format(self.city, self.addr) 22 | 23 | 24 | class UberCLI(object): 25 | """ Основная логика """ 26 | def __init__(self): 27 | self.conf = self.read_config() 28 | self.options, self.args = self.read_options() 29 | if self.conf.get('POINTS'): 30 | for n, arg in enumerate(self.args): 31 | if arg in self.conf.get('ALIASES'): 32 | self.args[n] = self.conf.get('ALIASES')[arg] 33 | self.writer = self.choose_writer()(self.options) 34 | assert len(self.args) == 2, "Usage: uber-cli " 35 | self.places = { 36 | "src": Place(self.args[0], self.conf), 37 | "dst": Place(self.args[1], self.conf), 38 | } 39 | 40 | def choose_writer(self): 41 | """ Выбор вывода данных""" 42 | if self.options.dict_writer: 43 | return writers.DictWriter 44 | if self.options.influxdb_writer: 45 | return writers.InfluxWriter 46 | return writers.PlainWriter 47 | 48 | @staticmethod 49 | def alert(message): 50 | """ Сообщение о том, что убер подешевел """ 51 | print(message) 52 | os.system("say {0}".format(message)) 53 | 54 | @staticmethod 55 | def read_options(): 56 | """ Разбор аргументов """ 57 | parser = optparse.OptionParser() 58 | parser.add_option('-i', dest='interval', type="int", metavar='SECONDS', default=30, 59 | help='delay between queries') 60 | parser.add_option('-w', '--watch', dest='watch', action='store_true', default=False, help='run query loop') 61 | parser.add_option('-o', '--one-line', dest='plain_writer', action='store_true', default=False, 62 | help='writer format') 63 | parser.add_option('-d', '--dict', dest='dict_writer', action='store_true', default=False, help='writer format') 64 | parser.add_option('--influxdb-format', dest='influxdb_writer', action='store_true', default=False, 65 | help='writer format') 66 | parser.add_option('--influxdb-url', dest='influxdb', metavar='URL', 67 | help='Example: http://127.0.0.1:8086/write?db=my_db') 68 | parser.add_option('-f', '--fair-price', dest='fair_price', type="int", 69 | help='defines fair price that ok to order taxi') 70 | return parser.parse_args() 71 | 72 | @staticmethod 73 | def read_config(): 74 | conf_file = os.path.join(os.getenv('HOME'), '.uberrc') 75 | if not os.path.exists(conf_file): 76 | return {} 77 | with open(conf_file) as conf_fd: 78 | return yaml.load(conf_fd) 79 | 80 | def geocode(self): 81 | geocoder = Geocoder() 82 | src = geocoder.geocode(self.places['src'].with_city()).coordinates 83 | dst = geocoder.geocode(self.places['dst'].with_city()).coordinates 84 | print(self.places['src'].with_city()) 85 | print(self.places['dst'].with_city()) 86 | return src, dst 87 | 88 | def price(self, src, dst): 89 | client = UberRidesClient(Session(server_token=self.conf.get('SERVER_TOKEN'))) 90 | return client.get_price_estimates(src[0], src[1], dst[0], dst[1]).json.get('prices') 91 | 92 | def oneshot(self, src, dst): 93 | price = self.price(src, dst) 94 | self.writer.write(price, self.places) 95 | if self.options.fair_price and price[0].get('low_estimate') < self.options.fair_price: 96 | self.alert(self.conf.get('PHRASE')) 97 | exit(0) 98 | 99 | def watch(self, src, dst): 100 | try: 101 | self.writer.header() 102 | while True: 103 | self.oneshot(src, dst) 104 | time.sleep(self.options.interval) 105 | except KeyboardInterrupt: 106 | exit(0) 107 | 108 | 109 | def main(): 110 | uc = UberCLI() 111 | src, dst = uc.geocode() 112 | if uc.options.watch: 113 | uc.watch(src, dst) 114 | else: 115 | uc.oneshot(src, dst) 116 | 117 | 118 | if __name__ == '__main__': 119 | main() 120 | -------------------------------------------------------------------------------- /uber_cli/writers.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | import datetime 4 | import requests 5 | 6 | 7 | class BaseWriter(object): 8 | def __init__(self, options): 9 | self.options = options 10 | 11 | def write(self, price, places): 12 | pass 13 | 14 | def header(self): 15 | pass 16 | 17 | 18 | class PlainWriter(BaseWriter): 19 | def write(self, price, places): 20 | product = price[0] 21 | print("{0} : {1:12} : {2:5} : {3:5}".format( 22 | datetime.datetime.now(), 23 | product['display_name'], 24 | product['low_estimate'], 25 | product['high_estimate'])) 26 | 27 | def header(self): 28 | print("{0:26} : {1:12} : {2:5} : {3:5}".format("Time", "Product", "Min", "Max")) 29 | 30 | 31 | class DictWriter(BaseWriter): 32 | def write(self, price, places): 33 | print(json.dumps(price, indent=4)) 34 | 35 | 36 | class InfluxWriter(BaseWriter): 37 | def write(self, price, places): 38 | assert self.options.influxdb, "Please, supply --influxdb-url " 39 | product = price[0] 40 | for key in ('low_estimate', 'high_estimate'): 41 | data = "{0},src={1},dst={2} value={3}".format( 42 | key, places['src'].addr, places['dst'].addr, product.get(key)) 43 | print(self.options.influxdb, '-d', data) 44 | try: 45 | requests.post(self.options.influxdb, data) 46 | except requests.exceptions.ConnectionError as err: 47 | print(err) 48 | --------------------------------------------------------------------------------