Last active
August 30, 2019 15:37
-
-
Save sash13/c8eb64a1ffd8e6c07c8e270996436219 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1. Install Python 3 | |
2. pip install -r requirements.txt | |
3. Dwnload kyivcityapi.py from https://gist.github.com/sash13/7b5a7aef7e99ec2029b818715a7df766 | |
4. Check Api by run with login pass from id.kyivcity | |
$python kyivcityapi.py login pass | |
5. Create bot in telegramm channel @BotFather and get token for bot | |
6. Fill T_TOKEN from your bot and LOGIN and PASS from id.kyivcity.gov.ua | |
7. Run script | |
$python t_kyiv_bot.py | |
8. Send /start and you see in console chad_id. Copy this number in field USER_ID. | |
For preventing other users working with your bot. | |
9. Cntrl+C and start bot again | |
10. Subscribe for updates /set 60 for updating every 60 secounds. another command: | |
/hist,/last,/cards,/stats,/unset |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
python-telegram-bot==11.1.0 | |
requests | |
lxml | |
cssselect | |
pytz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
from telegram.ext import Updater, CommandHandler | |
from telegram import ParseMode | |
import logging | |
from functools import partial | |
from kyivcityapi import KyivCityAPI | |
import pytz | |
import datetime | |
import sys | |
#Fill this | |
T_TOKEN = '' #telegram token | |
LOGIN = '' # +380111111111 | |
PASS ='' | |
USER_ID = 1111111 # user ID of chat | |
# | |
last_time = 'None' | |
card_id = '' | |
trip_count = 0 | |
# Enable logging | |
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
def check_chat(user_id): | |
return user_id == USER_ID | |
def parse_date(d): | |
timeUTC = datetime.datetime.strptime(d, "%Y-%m-%dT%H:%M:%S.%fZ") | |
timezoneLocal = pytz.timezone('Europe/Kiev') | |
utc = pytz.utc | |
return utc.localize(timeUTC).astimezone(timezoneLocal) | |
def print_date(d): | |
return parse_date(d).strftime("%Y-%m-%d %H:%M") | |
def parse_trip(d, time = False): | |
format_data = ' *' + print_date(d['date']).split(' ')[-1] + '* ' | |
tta = ['Тролейбус', 'Автобус', 'Трамвай'] | |
if 'Платформа' in d['travelType']: | |
return format_data + ' _Метро_ ' + d['stopName'] +'\n' | |
if d['travelType'] in tta: | |
return format_data + ' _' + d['travelType'] + ' №' + str(d['travelLine']) + '_ на ' + d['stopName'] +'\n' | |
return format_data + ' _Что-то_' + str(d['stopName']) +'\n' | |
def parse_trips(ds): | |
format_line = '' | |
last_time ='' | |
for d in ds: | |
date = '*' + print_date(d['date']).split(' ')[0] + '*' | |
if date not in last_time: | |
format_line += date + '\n' | |
last_time = date | |
format_line += parse_trip(d) | |
return format_line | |
def parse_pays(ds): | |
format_line = '' | |
for d in ds: | |
format_line += '*' + print_date(d['date']) + '* ' | |
format_line += ( "_Придбання 'Одноразові поїздки'" if d['productType'] in "O" else "_Поповнення гаманця")+ '_ ' | |
format_line += ' ' + str(int(round(d['price']/100.0))) + ' ₴' + '\n' | |
return format_line | |
def trips_counter(card): | |
ticket_count = 0 | |
for asset in card['assets']['purchases']: | |
ticket_count += asset['residualUnits'] | |
return ticket_count | |
def start(bot, update): | |
print(update.message.chat_id, type(update.message.chat_id)) | |
if not check_chat(update.message.chat_id): | |
update.message.reply_text('Пока недоступно') | |
return | |
update.message.reply_text('Привет! Тут что-то будет.') | |
def stats(bot, update, api): | |
if not check_chat(update.message.chat_id): | |
update.message.reply_text('Пока недоступно') | |
return | |
STEP = 20 | |
d = api.get_history(card_id, size = STEP) | |
page = 1 | |
while page*STEP < api.history_total: | |
page+=1 | |
d += api.get_history(card_id, size = STEP , page = page) | |
cost = 0 | |
for p in d: | |
cost += 800-p['price'] | |
cost = cost/100.0 | |
travels = [a['travelType'] for a in d] | |
occurence = {t:travels.count(t) for t in set(travels)} | |
print(occurence) | |
occurence['Платформа'] += occurence['Платформа / Вестибюль'] | |
occurence.pop('Платформа / Вестибюль') | |
out = sorted(list(occurence.items()), key = | |
lambda kv:(kv[1], kv[0]), reverse=True) | |
format_line = '*Всего поездок:* ' + str(api.history_total) + '\n' | |
#out['Платформа'] += out['Платформа / Вестибюль'] | |
#out.pop('Платформа / Вестибюль') | |
for o in out: | |
name = o[0] if 'Платформа' not in o[0] else 'Метро' | |
format_line += '*' + name + '*: _'+str(o[1])+ 'раз_ ' + str(int(round(float(o[1])/api.history_total*100))) + '%\n' | |
format_line += '*Сэкономлено:* ' + str(cost) + 'грн' | |
bot.send_message(chat_id=update.message.chat_id, | |
text=format_line, | |
parse_mode=ParseMode.MARKDOWN) | |
def history(bot, update, api): | |
if not check_chat(update.message.chat_id): | |
update.message.reply_text('Пока недоступно') | |
return | |
d = api.get_pays(card_id, size = 10) | |
bot.send_message(chat_id=update.message.chat_id, | |
text=parse_pays(d), | |
parse_mode=ParseMode.MARKDOWN) | |
def last(bot, update, api): | |
if not check_chat(update.message.chat_id): | |
update.message.reply_text('Пока недоступно') | |
return | |
d = api.get_history(card_id, size = 10) | |
bot.send_message(chat_id=update.message.chat_id, | |
text=parse_trips(d), | |
parse_mode=ParseMode.MARKDOWN) | |
def cards(bot, update, api): | |
if not check_chat(update.message.chat_id): | |
update.message.reply_text('Пока недоступно') | |
return | |
format_line = '' | |
cards_data = api.get_cards() | |
for card in cards_data: | |
if card['type'] in 'ridango': | |
format_line += '*Проездной: ' + card['name'] +'*\n' | |
format_line += '*Код*: ' + card['code'] +'\n' | |
format_line += '*Активен*: ' + ('Нет' if card['discounted'] else 'Да') +'\n' | |
if card['type'] in 'qr': | |
format_line += '*Проездной: QR билеты*\n' | |
#format_line += '*Создан*: ' + parse_date(card['createdAt']).strftime("%Y-%m-%d %H:%M") +'\n' | |
ticket_count = trips_counter(card) | |
format_line += '*Поездок*: ' + str(ticket_count) +'\n\n' | |
bot.send_message(chat_id=update.message.chat_id, | |
text=format_line, | |
parse_mode=ParseMode.MARKDOWN) | |
def alarm(bot, job): | |
if not check_chat(job.context["update"].message.chat_id): | |
update.message.reply_text('Пока недоступно') | |
return | |
global last_time | |
"""Send the alarm message.""" | |
last = job.context["api"].get_history(job.context["card_id"])[0] | |
card_data = job.context["api"].get_cards() | |
global trip_count | |
trip_count_ = trips_counter(card_data[0]) | |
if trip_count != trip_count_: | |
print(trip_count_, ' trips') | |
trip_count =trip_count_ | |
bot.send_message(chat_id=job.context['update'].message.chat_id, | |
text='Поездка списана. Осталось '+str(trip_count)) | |
if last_time not in last['date'] and last['travelType']: | |
print(last_time, last) | |
last_time = last['date'] | |
#bot.send_message(job.context["chat_id"], text=parse_trip(last)) | |
bot.send_message(chat_id=job.context["update"].message.chat_id, | |
text=parse_trip(last), | |
parse_mode=ParseMode.MARKDOWN) | |
def set_timer(bot, update, api, args, job_queue, chat_data): | |
if not check_chat(update.message.chat_id): | |
update.message.reply_text('Пока недоступно') | |
return | |
"""Add a job to the queue.""" | |
chat_id = update.message.chat_id | |
try: | |
# args[0] should contain the time for the timer in seconds | |
due = int(args[0]) | |
if due < 0: | |
update.message.reply_text('Sorry we can not go back to future!') | |
return | |
global card_id | |
print(card_id) | |
# Add job to queue | |
#job = job_queue.run_once(alarm, due, context=chat_id) | |
job = job_queue.run_repeating(alarm, due, context={ | |
"chat_id": chat_id, | |
"api":api, | |
"card_id": card_id, | |
"update": update | |
} | |
, first=0) | |
chat_data['job'] = job | |
update.message.reply_text('Обновление начато!') | |
except (IndexError, ValueError): | |
update.message.reply_text('Usage: /set <seconds>') | |
def unset(bot, update, chat_data): | |
if not check_chat(update.message.chat_id): | |
update.message.reply_text('Пока недоступно') | |
return | |
"""Remove the job if the user changed their mind.""" | |
if 'job' not in chat_data: | |
update.message.reply_text('You have no active timer') | |
return | |
job = chat_data['job'] | |
job.schedule_removal() | |
del chat_data['job'] | |
update.message.reply_text('Timer successfully unset!') | |
def error(bot, update, error): | |
"""Log Errors caused by Updates.""" | |
logger.warning('Update "%s" caused error "%s"', update, error) | |
def main(): | |
# Run api | |
api = KyivCityAPI(LOGIN, PASS) | |
api.login() | |
global card_id | |
card_id = api.get_cards()[0]['id'] | |
"""Run bot.""" | |
updater = Updater(T_TOKEN) | |
# Get the dispatcher to register handlers | |
dp = updater.dispatcher | |
# on different commands - answer in Telegram | |
dp.add_handler(CommandHandler("start", start)) | |
dp.add_handler(CommandHandler("help", start)) | |
dp.add_handler(CommandHandler("hist", partial(history, api=api))) | |
dp.add_handler(CommandHandler("last", partial(last, api=api))) | |
dp.add_handler(CommandHandler("cards", partial(cards, api=api))) | |
dp.add_handler(CommandHandler("stats", partial(stats, api=api))) | |
dp.add_handler(CommandHandler("set", partial(set_timer, api=api), | |
pass_args=True, | |
pass_job_queue=True, | |
pass_chat_data=True)) | |
dp.add_handler(CommandHandler("unset", unset, pass_chat_data=True)) | |
# log all errors | |
dp.add_error_handler(error) | |
# Start the Bot | |
updater.start_polling() | |
# Block until you press Ctrl-C or the process receives SIGINT, SIGTERM or | |
# SIGABRT. This should be used most of the time, since start_polling() is | |
# non-blocking and will stop the bot gracefully. | |
updater.idle() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment