mirror of
https://github.com/cupcakearmy/mercatus.git
synced 2024-12-22 16:16:32 +00:00
new menu for more granular control over the single stocks
This commit is contained in:
parent
6f0930a0cc
commit
e1c93963d9
@ -1,37 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import threading
|
|
||||||
|
|
||||||
|
|
||||||
def interval(every: float or int, autorun=False, iterations=-1, isolated=False, *args_root, **kwargs_root):
|
|
||||||
def wrapper(fn):
|
|
||||||
|
|
||||||
async def decorator(*args, **kwargs):
|
|
||||||
it = 0
|
|
||||||
first = True
|
|
||||||
while iterations == -1 or it < iterations:
|
|
||||||
it += 1
|
|
||||||
|
|
||||||
if first:
|
|
||||||
first = False
|
|
||||||
else:
|
|
||||||
await asyncio.sleep(every)
|
|
||||||
|
|
||||||
await fn(*args, **kwargs)
|
|
||||||
|
|
||||||
def capsule(*args, **kwargs):
|
|
||||||
def loop_in_thread(l):
|
|
||||||
asyncio.set_event_loop(l)
|
|
||||||
l.run_until_complete(decorator(*args, **kwargs))
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
threading.Thread(target=loop_in_thread, args=(loop,)).start()
|
|
||||||
|
|
||||||
if autorun:
|
|
||||||
if isolated:
|
|
||||||
capsule(*args_root, **kwargs_root)
|
|
||||||
else:
|
|
||||||
asyncio.run(decorator(*args_root, **kwargs_root))
|
|
||||||
else:
|
|
||||||
return capsule if isolated else decorator
|
|
||||||
|
|
||||||
return wrapper
|
|
@ -1,23 +1,35 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardRemove, ParseMode
|
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardRemove, ParseMode
|
||||||
from telegram.ext import CommandHandler, MessageHandler, Filters, ConversationHandler, CallbackQueryHandler, CallbackContext
|
from telegram.ext import CommandHandler, MessageHandler, Filters, ConversationHandler, CallbackQueryHandler, CallbackContext
|
||||||
|
|
||||||
from utils import Section
|
MENU, API_KEY, ENABLED = range(3)
|
||||||
|
|
||||||
MENU, API_KEY, FREQUENCY, ENABLED = range(4)
|
|
||||||
|
class Section(Enum):
|
||||||
|
Watchlist = 'watchlist' # The list of Stocks/ETF to watch
|
||||||
|
Code = 'code' # Market code for a given stock, etf, etc.
|
||||||
|
API_Key = 'api_key' # Alpha Vantage API Key
|
||||||
|
Running = 'running' # Currently sending updates. Avoid overloading the API
|
||||||
|
Enabled = 'enabled' # Whether the bot should send automatic updates
|
||||||
|
Interval = 'interval' # Time axis of the graph
|
||||||
|
Frequency = 'frequency' # How ofter updates should be sent
|
||||||
|
LastRun = 'last_run' # Last time an update was sent to the user
|
||||||
|
CurrentToEdit = 'current_to_edit' # Current element to edit in the conversation handler
|
||||||
|
|
||||||
|
|
||||||
def show_menu(update: Update, context: CallbackContext):
|
def show_menu(update: Update, context: CallbackContext):
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[InlineKeyboardButton('API Key', callback_data=API_KEY)],
|
[InlineKeyboardButton('API Key', callback_data=API_KEY)],
|
||||||
[InlineKeyboardButton('Auto Updates', callback_data=ENABLED)],
|
[InlineKeyboardButton(
|
||||||
[InlineKeyboardButton('Frequency', callback_data=FREQUENCY)],
|
f'Turn {"off" if context.user_data.setdefault(Section.Enabled.value, True) else "on"} global auto updates',
|
||||||
|
callback_data=ENABLED)],
|
||||||
[InlineKeyboardButton('Done', callback_data=ConversationHandler.END)],
|
[InlineKeyboardButton('Done', callback_data=ConversationHandler.END)],
|
||||||
]
|
]
|
||||||
update.effective_user.send_message(
|
update.effective_user.send_message(
|
||||||
'_Current settings:_\n'
|
'_Current settings:_\n'
|
||||||
f'API Key: *{context.user_data[Section.API_Key.value]}*\n'
|
f'API Key: *{context.user_data.get(Section.API_Key.value, "No Api key set")}*\n'
|
||||||
f'Auto Updates: *{context.user_data[Section.Enabled.value]}*\n'
|
f'Global auto updates: *{context.user_data[Section.Enabled.value]}*\n'
|
||||||
f'Frequency: *{context.user_data[Section.Frequency.value]}*\n'
|
|
||||||
'\nWhat settings do you want to configure?',
|
'\nWhat settings do you want to configure?',
|
||||||
parse_mode=ParseMode.MARKDOWN,
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
reply_markup=InlineKeyboardMarkup(keyboard, one_time_keyboard=True)
|
reply_markup=InlineKeyboardMarkup(keyboard, one_time_keyboard=True)
|
||||||
@ -35,27 +47,7 @@ def show_menu_api_key(update: Update, context: CallbackContext):
|
|||||||
return API_KEY
|
return API_KEY
|
||||||
|
|
||||||
|
|
||||||
def show_menu_frequency(update: Update, context: CallbackContext):
|
def init(update: Update, context: CallbackContext):
|
||||||
keyboard = [
|
|
||||||
[InlineKeyboardButton('2 minutes', callback_data='2m'), InlineKeyboardButton(
|
|
||||||
'30 minutes', callback_data='30m')],
|
|
||||||
[InlineKeyboardButton('hour', callback_data='1h'), InlineKeyboardButton(
|
|
||||||
'4 hours', callback_data='4h')],
|
|
||||||
[InlineKeyboardButton('12 hours', callback_data='12h'), InlineKeyboardButton(
|
|
||||||
'day', callback_data='1d')],
|
|
||||||
[InlineKeyboardButton('3 days', callback_data='3d'), InlineKeyboardButton(
|
|
||||||
'week', callback_data='1w')],
|
|
||||||
[InlineKeyboardButton('Cancel', callback_data='cancel')],
|
|
||||||
]
|
|
||||||
update.effective_user.send_message(
|
|
||||||
'Send me updates every: ⬇',
|
|
||||||
reply_markup=InlineKeyboardMarkup(keyboard)
|
|
||||||
)
|
|
||||||
|
|
||||||
return FREQUENCY
|
|
||||||
|
|
||||||
|
|
||||||
def config(update: Update, context: CallbackContext):
|
|
||||||
context.bot.delete_message(
|
context.bot.delete_message(
|
||||||
chat_id=update.message.chat_id,
|
chat_id=update.message.chat_id,
|
||||||
message_id=update.message.message_id,
|
message_id=update.message.message_id,
|
||||||
@ -73,8 +65,6 @@ def menu(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
if selected == API_KEY:
|
if selected == API_KEY:
|
||||||
return show_menu_api_key(update, context)
|
return show_menu_api_key(update, context)
|
||||||
elif selected == FREQUENCY:
|
|
||||||
return show_menu_frequency(update, context)
|
|
||||||
elif selected == ENABLED:
|
elif selected == ENABLED:
|
||||||
toggle_enabled(update, context)
|
toggle_enabled(update, context)
|
||||||
else:
|
else:
|
||||||
@ -89,25 +79,8 @@ def set_api_key(update, context):
|
|||||||
return show_menu(update, context)
|
return show_menu(update, context)
|
||||||
|
|
||||||
|
|
||||||
def set_frequency(update: Update, context: CallbackContext):
|
|
||||||
selected = update.callback_query.data
|
|
||||||
|
|
||||||
if selected != 'cancel':
|
|
||||||
update.callback_query.edit_message_text(f'Saved {selected} 💪')
|
|
||||||
context.user_data[Section.Frequency.value] = selected
|
|
||||||
else:
|
|
||||||
context.bot.delete_message(
|
|
||||||
chat_id=update.callback_query.message.chat_id,
|
|
||||||
message_id=update.callback_query.message.message_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
return show_menu(update, context)
|
|
||||||
|
|
||||||
|
|
||||||
def toggle_enabled(update: Update, context: CallbackContext):
|
def toggle_enabled(update: Update, context: CallbackContext):
|
||||||
new = not context.user_data.setdefault(Section.Enabled.value, True)
|
context.user_data[Section.Enabled.value] = not context.user_data[Section.Enabled.value]
|
||||||
context.user_data[Section.Enabled.value] = new
|
|
||||||
update.effective_user.send_message('Auto updates enabled' if new else 'Auto updates disabled')
|
|
||||||
|
|
||||||
return show_menu(update, context)
|
return show_menu(update, context)
|
||||||
|
|
||||||
@ -118,16 +91,13 @@ def cancel(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
config_handler = ConversationHandler(
|
config_handler = ConversationHandler(
|
||||||
entry_points=[CommandHandler('config', config)],
|
entry_points=[CommandHandler('settings', init)],
|
||||||
|
|
||||||
states={
|
states={
|
||||||
MENU: [CallbackQueryHandler(menu)],
|
MENU: [CallbackQueryHandler(menu)],
|
||||||
API_KEY: [
|
API_KEY: [
|
||||||
CommandHandler('cancel', cancel),
|
CommandHandler('cancel', cancel),
|
||||||
MessageHandler(Filters.all, set_api_key),
|
MessageHandler(Filters.all, set_api_key),
|
||||||
],
|
],
|
||||||
FREQUENCY: [CallbackQueryHandler(set_frequency)],
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fallbacks=[CommandHandler('cancel', cancel)]
|
fallbacks=[CommandHandler('cancel', cancel)]
|
||||||
)
|
)
|
||||||
|
@ -1,57 +1,77 @@
|
|||||||
from asyncio import sleep, run
|
from asyncio import sleep, run
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from threading import Timer
|
|
||||||
|
|
||||||
from pytimeparse import parse
|
from pytimeparse import parse
|
||||||
from telegram import Update, ParseMode
|
from telegram import Update, ParseMode, ReplyKeyboardRemove
|
||||||
from telegram.ext import CallbackContext
|
from telegram.ext import CallbackContext, ConversationHandler
|
||||||
from telegram.ext.dispatcher import run_async
|
from telegram.ext.dispatcher import run_async
|
||||||
|
|
||||||
|
from commands.config import Section
|
||||||
from market import Market
|
from market import Market
|
||||||
from text import INTRO_TEXT
|
from text import INTRO_TEXT
|
||||||
from utils import Section, persistence, updater, current_timestamp, delta_timestamp
|
from utils import persistence, updater, current_timestamp, delta_timestamp
|
||||||
|
|
||||||
SENDING = False
|
SENDING = False
|
||||||
|
|
||||||
|
|
||||||
def error(update: Update, context: CallbackContext):
|
def error_handler(update: Update, context: CallbackContext):
|
||||||
print(context.error)
|
print('Error: ', context.error)
|
||||||
|
|
||||||
|
|
||||||
def start(update: Update, context: CallbackContext):
|
def start_handler(update: Update, context: CallbackContext):
|
||||||
update.message.reply_markdown(INTRO_TEXT)
|
update.message.reply_markdown(INTRO_TEXT)
|
||||||
|
|
||||||
|
|
||||||
|
def help_handler(update: Update, context: CallbackContext):
|
||||||
|
update.message.reply_markdown(INTRO_TEXT)
|
||||||
|
|
||||||
|
|
||||||
|
def stop_handler(update: Update, context: CallbackContext):
|
||||||
|
context.user_data.clear()
|
||||||
|
update.message.reply_text('You and your data were deleted 🗑')
|
||||||
|
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def data(update: Update, context: CallbackContext):
|
def data(update: Update, context: CallbackContext):
|
||||||
delta = current_timestamp() - context.user_data.setdefault(Section.Interval.value, delta_timestamp(days=365))
|
send_update_to_user(update.effective_user['id'], False)
|
||||||
send_update_to_user(user=update.effective_user['id'], delta=delta)
|
|
||||||
|
|
||||||
|
|
||||||
def send_update_to_user(user: str, delta: int):
|
def send_update_to_user(user: str, auto, codes=None):
|
||||||
user_data = None
|
user_data = None
|
||||||
try:
|
try:
|
||||||
user_data = persistence.user_data[user]
|
user_data = persistence.user_data[user]
|
||||||
running = user_data.setdefault(Section.Running.value, False)
|
running = user_data.setdefault(Section.Running.value, False)
|
||||||
print(f'Running {user} - {user_data}')
|
|
||||||
|
|
||||||
if Section.API_Key.value not in user_data:
|
if Section.API_Key.value not in user_data:
|
||||||
updater.bot.send_message(user, text='API Key not set ⛔️')
|
updater.bot.send_message(user, text='API Key not set ⛔️\nSet in /settings')
|
||||||
return
|
return
|
||||||
|
|
||||||
if running:
|
if running:
|
||||||
updater.bot.send_message(user, text='Already running 🏃')
|
updater.bot.send_message(user, text='Already running 🏃')
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f'Sending updates to {user}')
|
|
||||||
user_data[Section.Running.value] = True
|
user_data[Section.Running.value] = True
|
||||||
user_data[Section.LastRun.value] = current_timestamp()
|
|
||||||
|
|
||||||
market = Market(user_data[Section.API_Key.value])
|
market = Market(user_data[Section.API_Key.value])
|
||||||
|
now = current_timestamp()
|
||||||
|
if auto:
|
||||||
updater.bot.send_message(user, text='Getting updates 🌎')
|
updater.bot.send_message(user, text='Getting updates 🌎')
|
||||||
|
|
||||||
first = True
|
first = True
|
||||||
for item in user_data.get(Section.Watchlist.value, []):
|
for code in codes if codes else user_data.get(Section.Watchlist.value, {}).keys():
|
||||||
|
try:
|
||||||
|
code_data = persistence.user_data[user][Section.Watchlist.value][code]
|
||||||
|
code_data = code_data['value']
|
||||||
|
print(code, code_data)
|
||||||
|
last_run = code_data[Section.LastRun.value]
|
||||||
|
frequency = parse(code_data[Section.Frequency.value])
|
||||||
|
interval = parse(code_data[Section.Interval.value])
|
||||||
|
print(code, last_run + frequency, now, last_run + frequency - now)
|
||||||
|
|
||||||
|
if auto and last_run + frequency > now:
|
||||||
|
continue
|
||||||
|
persistence.user_data[user][Section.Watchlist.value][code][Section.LastRun.value] = current_timestamp()
|
||||||
|
persistence.flush()
|
||||||
|
|
||||||
if first:
|
if first:
|
||||||
first = False
|
first = False
|
||||||
else:
|
else:
|
||||||
@ -60,15 +80,18 @@ def send_update_to_user(user: str, delta: int):
|
|||||||
run(sleep(60))
|
run(sleep(60))
|
||||||
msg.delete()
|
msg.delete()
|
||||||
|
|
||||||
msg = updater.bot.send_message(user, text=f'Calculating {item}... ⏳')
|
msg = updater.bot.send_message(user, text=f'Calculating {code}... ⏳')
|
||||||
chart = market.get_wma(item, datetime.fromtimestamp(delta))
|
delta = datetime.fromtimestamp(now - interval)
|
||||||
|
chart = market.get_wma(code, delta)
|
||||||
msg.delete()
|
msg.delete()
|
||||||
updater.bot.send_photo(user, photo=chart, caption=f'*{item}*',
|
updater.bot.send_photo(user, photo=chart, disable_notification=True,
|
||||||
parse_mode=ParseMode.MARKDOWN, disable_notification=True)
|
caption=f'{code} - {code_data[Section.Interval.value]}')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'❌ {user} - {e}')
|
print(f'❌ {user} - {e}')
|
||||||
updater.bot.send_message(user, text=f'There was an error ⚠️\n {e}')
|
updater.bot.send_message(user, text=f'There was an error ⚠️\n {e}')
|
||||||
|
|
||||||
|
if auto:
|
||||||
|
updater.bot.send_message(user, text=f'Done ✅')
|
||||||
finally:
|
finally:
|
||||||
if user_data:
|
if user_data:
|
||||||
user_data[Section.Running.value] = False
|
user_data[Section.Running.value] = False
|
||||||
@ -81,16 +104,9 @@ def send_updates(context: CallbackContext):
|
|||||||
return
|
return
|
||||||
|
|
||||||
SENDING = True
|
SENDING = True
|
||||||
now = current_timestamp()
|
|
||||||
|
|
||||||
for user, user_data in persistence.user_data.items():
|
for user, user_data in persistence.user_data.items():
|
||||||
enabled = user_data.setdefault(Section.Enabled.value, True)
|
if user_data.setdefault(Section.Enabled.value, False):
|
||||||
last_run = user_data.setdefault(Section.LastRun.value, 0)
|
send_update_to_user(user=user, auto=True)
|
||||||
frequency = parse(user_data.setdefault(Section.Frequency.value, '1d'))
|
|
||||||
|
|
||||||
if enabled and last_run + frequency < now:
|
|
||||||
delta = now - user_data.setdefault(Section.Interval.value, delta_timestamp(days=365))
|
|
||||||
send_update_to_user(user=user, delta=delta)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
finally:
|
finally:
|
||||||
|
@ -1,45 +1,232 @@
|
|||||||
from telegram import Update
|
from telegram import Update, InlineKeyboardButton, ParseMode, InlineKeyboardMarkup, ReplyKeyboardRemove
|
||||||
from telegram.ext import CallbackContext
|
from telegram.ext import CallbackContext, ConversationHandler, CommandHandler, CallbackQueryHandler, MessageHandler, Filters
|
||||||
|
|
||||||
from limited_list import LimitedList
|
from commands.config import Section
|
||||||
from utils import parse_command, config, Section
|
from commands.other import send_update_to_user
|
||||||
|
from limited_dict import LimitedDict
|
||||||
|
from utils import parse_command, config
|
||||||
|
|
||||||
|
ALL, SINGLE, EDIT, ADD, DELETE, BACK, ENABLED, FREQUENCY, INTERVAL, DATA = map(chr, range(10))
|
||||||
|
END = str(ConversationHandler.END)
|
||||||
|
|
||||||
|
|
||||||
def get_watchlist(context: CallbackContext) -> LimitedList:
|
def get_watchlist(context: CallbackContext) -> LimitedDict:
|
||||||
return LimitedList(
|
return LimitedDict(
|
||||||
config[Section.Watchlist.value]['max_items'],
|
config[Section.Watchlist.value]['max_items'],
|
||||||
context.user_data.setdefault(Section.Watchlist.value, []),
|
context.user_data.setdefault(Section.Watchlist.value, {}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def save_watchlist(context: CallbackContext, l: LimitedList):
|
def save_watchlist(context: CallbackContext, limited_dict: LimitedDict):
|
||||||
context.user_data[Section.Watchlist.value] = l.all()
|
context.user_data[Section.Watchlist.value] = limited_dict.dict
|
||||||
|
|
||||||
|
|
||||||
def watchlist_add(update: Update, context: CallbackContext):
|
def init(update: Update, context: CallbackContext):
|
||||||
value, *rest = parse_command(update)
|
context.bot.delete_message(
|
||||||
|
chat_id=update.message.chat_id,
|
||||||
|
message_id=update.message.message_id,
|
||||||
|
)
|
||||||
|
return show_menu(update, context)
|
||||||
|
|
||||||
|
|
||||||
|
def show_menu(update: Update, context: CallbackContext):
|
||||||
wl = get_watchlist(context)
|
wl = get_watchlist(context)
|
||||||
wl.add(str(value).upper())
|
saved = [
|
||||||
save_watchlist(context, wl)
|
[InlineKeyboardButton(item, callback_data=item)]
|
||||||
update.message.reply_text('Saved 💾')
|
for item in wl.all()
|
||||||
|
]
|
||||||
|
options = [[
|
||||||
|
InlineKeyboardButton('Add', callback_data=ADD),
|
||||||
|
InlineKeyboardButton('Done', callback_data=END)
|
||||||
|
]]
|
||||||
|
update.effective_user.send_message(
|
||||||
|
'_Your Watchlist:_\n'
|
||||||
|
f'*{len(wl)}/{wl.limit}* slots filled\n'
|
||||||
|
'\nYou can add, modify or delete items',
|
||||||
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
|
reply_markup=InlineKeyboardMarkup(saved + options, one_time_keyboard=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ALL
|
||||||
|
|
||||||
|
|
||||||
def watchlist_delete(update: Update, context: CallbackContext):
|
def menu(update: Update, context: CallbackContext):
|
||||||
value, *rest = parse_command(update)
|
context.bot.delete_message(
|
||||||
|
chat_id=update.callback_query.message.chat_id,
|
||||||
|
message_id=update.callback_query.message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected: str = update.callback_query.data
|
||||||
|
if selected == ADD:
|
||||||
|
return show_add(update, context)
|
||||||
|
elif selected == END:
|
||||||
|
return ConversationHandler.END
|
||||||
|
else:
|
||||||
|
context.user_data[Section.CurrentToEdit.value] = selected
|
||||||
|
return show_single(update, context)
|
||||||
|
|
||||||
|
|
||||||
|
def show_add(update: Update, context: CallbackContext):
|
||||||
|
update.effective_user.send_message(
|
||||||
|
'Send me the code (e.g. AAPL or URTH)\n'
|
||||||
|
'or send /cancel',
|
||||||
|
reply_markup=ReplyKeyboardRemove()
|
||||||
|
)
|
||||||
|
return ADD
|
||||||
|
|
||||||
|
|
||||||
|
def add(update, context):
|
||||||
|
reply: str = update.message.text
|
||||||
wl = get_watchlist(context)
|
wl = get_watchlist(context)
|
||||||
found = wl.delete(value)
|
wl[reply.upper()] = {
|
||||||
|
Section.Enabled.value: True,
|
||||||
|
Section.Frequency.value: '1d',
|
||||||
|
Section.Interval.value: '52w',
|
||||||
|
Section.LastRun.value: 0,
|
||||||
|
}
|
||||||
save_watchlist(context, wl)
|
save_watchlist(context, wl)
|
||||||
update.message.reply_text('Deleted 🗑' if found else 'Not found ❓')
|
update.message.reply_text(f'Saved {reply} 💾', reply_markup=ReplyKeyboardRemove())
|
||||||
|
|
||||||
|
return show_menu(update, context)
|
||||||
|
|
||||||
|
|
||||||
def watchlist_all(update: Update, context: CallbackContext):
|
def show_single(update, context):
|
||||||
items = get_watchlist(context).all()
|
current = context.user_data[Section.CurrentToEdit.value]
|
||||||
update.message.reply_text('\n'.join(items) if len(items) > 0 else 'Your list is empty 📭')
|
current_data = get_watchlist(context)[current]
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton(f'Turn {"off" if current_data[Section.Enabled.value] else "on"} auto updates', callback_data=ENABLED)],
|
||||||
|
[InlineKeyboardButton(f'Frequency', callback_data=FREQUENCY)],
|
||||||
|
[InlineKeyboardButton(f'Time interval', callback_data=INTERVAL)],
|
||||||
|
[InlineKeyboardButton(f'Show data', callback_data=DATA)],
|
||||||
|
[InlineKeyboardButton('Delete', callback_data=DELETE)],
|
||||||
|
[InlineKeyboardButton('Back', callback_data=BACK)],
|
||||||
|
]
|
||||||
|
update.effective_user.send_message(
|
||||||
|
'_Current settings:_\n'
|
||||||
|
f'Auto Updates: *{current_data[Section.Enabled.value]}*\n'
|
||||||
|
f'Frequency: *{current_data[Section.Frequency.value]}*\n'
|
||||||
|
f'Interval: *{current_data[Section.Interval.value]}*\n'
|
||||||
|
f'\nEdit {current}: ⬇',
|
||||||
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
|
reply_markup=InlineKeyboardMarkup(keyboard)
|
||||||
|
)
|
||||||
|
|
||||||
|
return SINGLE
|
||||||
|
|
||||||
|
|
||||||
def watchlist_clear(update: Update, context: CallbackContext):
|
def single(update, context):
|
||||||
|
current = context.user_data[Section.CurrentToEdit.value]
|
||||||
|
selected = update.callback_query.data
|
||||||
wl = get_watchlist(context)
|
wl = get_watchlist(context)
|
||||||
wl.clear()
|
|
||||||
|
context.bot.delete_message(
|
||||||
|
chat_id=update.callback_query.message.chat_id,
|
||||||
|
message_id=update.callback_query.message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if selected == DELETE:
|
||||||
|
update.effective_user.send_message(f'Deleted {current} 💪')
|
||||||
|
del wl[current]
|
||||||
save_watchlist(context, wl)
|
save_watchlist(context, wl)
|
||||||
update.message.reply_text('Cleared 🧼')
|
return show_menu(update, context)
|
||||||
|
elif selected == BACK:
|
||||||
|
return show_menu(update, context)
|
||||||
|
elif selected == ENABLED:
|
||||||
|
print('Changing', wl[Section.Enabled.value])
|
||||||
|
wl[current][Section.Enabled.value] = not wl[current][Section.Enabled.value]
|
||||||
|
save_watchlist(context, wl)
|
||||||
|
return show_single(update, context)
|
||||||
|
elif selected == FREQUENCY:
|
||||||
|
return show_single_frequency(update, context)
|
||||||
|
elif selected == INTERVAL:
|
||||||
|
return show_single_interval(update, context)
|
||||||
|
elif selected == DATA:
|
||||||
|
send_update_to_user(update.effective_user['id'], False, [current])
|
||||||
|
return show_single(update, context)
|
||||||
|
else:
|
||||||
|
return cancel(update, context)
|
||||||
|
|
||||||
|
|
||||||
|
def show_single_frequency(update: Update, context: CallbackContext):
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton('2 minutes', callback_data='2m'), InlineKeyboardButton(
|
||||||
|
'30 minutes', callback_data='30m')],
|
||||||
|
[InlineKeyboardButton('hour', callback_data='1h'), InlineKeyboardButton(
|
||||||
|
'4 hours', callback_data='4h')],
|
||||||
|
[InlineKeyboardButton('12 hours', callback_data='12h'), InlineKeyboardButton(
|
||||||
|
'day', callback_data='1d')],
|
||||||
|
[InlineKeyboardButton('3 days', callback_data='3d'), InlineKeyboardButton(
|
||||||
|
'week', callback_data='1w')],
|
||||||
|
[InlineKeyboardButton('Cancel', callback_data='cancel')],
|
||||||
|
]
|
||||||
|
update.effective_user.send_message(
|
||||||
|
'Send me updates every: ⬇',
|
||||||
|
reply_markup=InlineKeyboardMarkup(keyboard)
|
||||||
|
)
|
||||||
|
|
||||||
|
return FREQUENCY
|
||||||
|
|
||||||
|
|
||||||
|
def single_frequency(update: Update, context: CallbackContext):
|
||||||
|
return save_single_attribute(update, context, Section.Frequency.value)
|
||||||
|
|
||||||
|
|
||||||
|
def show_single_interval(update: Update, context: CallbackContext):
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton('1 day', callback_data='1d'), InlineKeyboardButton(
|
||||||
|
'2 days', callback_data='2d')],
|
||||||
|
[InlineKeyboardButton('1 week', callback_data='7d'), InlineKeyboardButton(
|
||||||
|
'1 weeks', callback_data='14d')],
|
||||||
|
[InlineKeyboardButton('1 month', callback_data='30d'), InlineKeyboardButton(
|
||||||
|
'3 months', callback_data='90d')],
|
||||||
|
[InlineKeyboardButton('1 year', callback_data='52w'), InlineKeyboardButton(
|
||||||
|
'2 years', callback_data='104w')],
|
||||||
|
[InlineKeyboardButton('Cancel', callback_data='cancel')],
|
||||||
|
]
|
||||||
|
update.effective_user.send_message(
|
||||||
|
'Select the graph time span 📈:',
|
||||||
|
reply_markup=InlineKeyboardMarkup(keyboard)
|
||||||
|
)
|
||||||
|
|
||||||
|
return INTERVAL
|
||||||
|
|
||||||
|
|
||||||
|
def single_interval(update: Update, context: CallbackContext):
|
||||||
|
return save_single_attribute(update, context, Section.Interval.value)
|
||||||
|
|
||||||
|
|
||||||
|
def save_single_attribute(update: Update, context: CallbackContext, key):
|
||||||
|
current = context.user_data[Section.CurrentToEdit.value]
|
||||||
|
selected = update.callback_query.data
|
||||||
|
wl = get_watchlist(context)
|
||||||
|
|
||||||
|
context.bot.delete_message(
|
||||||
|
chat_id=update.callback_query.message.chat_id,
|
||||||
|
message_id=update.callback_query.message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if selected != 'cancel':
|
||||||
|
update.effective_user.send_message(f'Saved {selected} 💾')
|
||||||
|
wl[current][key] = selected
|
||||||
|
|
||||||
|
return show_single(update, context)
|
||||||
|
|
||||||
|
|
||||||
|
def cancel(update: Update, context: CallbackContext):
|
||||||
|
update.effective_user.send_message('Canceled', reply_markup=ReplyKeyboardRemove())
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
|
||||||
|
cancel_command_handler = CommandHandler('cancel', cancel)
|
||||||
|
|
||||||
|
watchlist_handler = ConversationHandler(
|
||||||
|
entry_points=[CommandHandler('list', init)],
|
||||||
|
states={
|
||||||
|
ALL: [CallbackQueryHandler(menu)],
|
||||||
|
ADD: [cancel_command_handler, MessageHandler(Filters.text, add)],
|
||||||
|
SINGLE: [cancel_command_handler, CallbackQueryHandler(single)],
|
||||||
|
FREQUENCY: [CallbackQueryHandler(single_frequency)],
|
||||||
|
INTERVAL: [CallbackQueryHandler(single_interval)],
|
||||||
|
},
|
||||||
|
fallbacks=[cancel_command_handler],
|
||||||
|
)
|
||||||
|
16
src/generator_dict.py
Normal file
16
src/generator_dict.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
class GeneratorDict(dict):
|
||||||
|
def __init__(self, *args, generator=None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if not generator:
|
||||||
|
def generator(x): return x
|
||||||
|
|
||||||
|
self.generator = generator
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
return super().__getitem__(key)
|
||||||
|
except:
|
||||||
|
value = self.generator(key)
|
||||||
|
super().__setitem__(key, value)
|
||||||
|
return value
|
@ -1,38 +1,44 @@
|
|||||||
import time
|
import time
|
||||||
import math
|
from math import inf
|
||||||
|
|
||||||
|
|
||||||
class LimitedDict:
|
class LimitedDict:
|
||||||
|
|
||||||
def __init__(self, init: dict, limit: int):
|
def __init__(self, limit: int, init: dict):
|
||||||
self.dict = init
|
self.dict = init
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
|
|
||||||
def set(self, key, value):
|
def __len__(self):
|
||||||
# Delete oldest element if there are too many
|
return len(self.dict)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# Delete the oldest if there are too many entries
|
||||||
if len(self.dict) + 1 > self.limit:
|
if len(self.dict) + 1 > self.limit:
|
||||||
timestamp = math.inf
|
timestamp = inf
|
||||||
who = None
|
oldest = None
|
||||||
for cur, item in self.dict.items():
|
for cur, item in self.dict.items():
|
||||||
if item['when'] < timestamp:
|
if item['when'] < timestamp:
|
||||||
timestamp = item['when']
|
timestamp = item['when']
|
||||||
who = cur
|
oldest = cur
|
||||||
del self.dict[who]
|
del self.dict[oldest]
|
||||||
|
|
||||||
self.dict[key] = {
|
self.dict[key] = {
|
||||||
'value': value,
|
'value': value,
|
||||||
'when': int(time.time())
|
'when': int(time.time())
|
||||||
}
|
}
|
||||||
|
|
||||||
def get(self, key):
|
def __getitem__(self, key):
|
||||||
value = self.dict.get(key, None)
|
value = self.dict.get(key, None)
|
||||||
return value['value'] if value else None
|
return value['value'] if value else None
|
||||||
|
|
||||||
def delete(self, key):
|
def __delitem__(self, key):
|
||||||
self.dict.pop(key, None)
|
self.dict.pop(key, None)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.dict.clear()
|
self.dict.clear()
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
return [x['value'] for x in self.dict.values()]
|
return {
|
||||||
|
key: value['value']
|
||||||
|
for key, value in self.dict.items()
|
||||||
|
}
|
@ -11,10 +11,13 @@ class LimitedList:
|
|||||||
self.data = init if init else []
|
self.data = init if init else []
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.data)
|
||||||
|
|
||||||
def _is_index(self, i: int) -> bool:
|
def _is_index(self, i: int) -> bool:
|
||||||
return False if i < 0 or i > len(self.data) - 1 else True
|
return False if i < 0 or i > len(self.data) - 1 else True
|
||||||
|
|
||||||
def add(self, value: str):
|
def add(self, value: any):
|
||||||
print(f'Before {self.data}')
|
print(f'Before {self.data}')
|
||||||
# Delete oldest element if there are too many
|
# Delete oldest element if there are too many
|
||||||
if len(self.data) + 1 > self.limit:
|
if len(self.data) + 1 > self.limit:
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import matplotlib as mpl
|
import matplotlib as mpl
|
||||||
from telegram.ext import CommandHandler
|
from telegram.ext import CommandHandler
|
||||||
|
|
||||||
from utils import updater, persistence
|
from utils import updater
|
||||||
from commands.config import config_handler
|
from commands.config import config_handler
|
||||||
from commands.watchlist import watchlist_add, watchlist_delete, watchlist_all, watchlist_clear
|
from commands.watchlist import watchlist_handler
|
||||||
from commands.other import start, data, send_updates
|
from commands.other import data, send_updates, start_handler, help_handler, stop_handler, error_handler
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -14,16 +14,16 @@ def main():
|
|||||||
jq = updater.job_queue
|
jq = updater.job_queue
|
||||||
|
|
||||||
# Handlers
|
# Handlers
|
||||||
dp.add_handler(CommandHandler('add', watchlist_add))
|
dp.add_error_handler(error_handler)
|
||||||
dp.add_handler(CommandHandler('delete', watchlist_delete))
|
dp.add_handler(CommandHandler('start', start_handler))
|
||||||
dp.add_handler(CommandHandler('list', watchlist_all))
|
dp.add_handler(CommandHandler('stop', stop_handler))
|
||||||
dp.add_handler(CommandHandler('clear', watchlist_clear))
|
dp.add_handler(CommandHandler('help', help_handler))
|
||||||
dp.add_handler(config_handler)
|
|
||||||
dp.add_handler(CommandHandler('start', start))
|
|
||||||
dp.add_handler(CommandHandler('data', data))
|
dp.add_handler(CommandHandler('data', data))
|
||||||
|
dp.add_handler(config_handler)
|
||||||
|
dp.add_handler(watchlist_handler)
|
||||||
|
|
||||||
# Cron jobs
|
# Cron jobs
|
||||||
jq.run_repeating(send_updates, interval=30, first=0)
|
jq.run_repeating(send_updates, interval=30, first=5)
|
||||||
|
|
||||||
# Start
|
# Start
|
||||||
print('Started 🚀')
|
print('Started 🚀')
|
||||||
|
19
src/utils.py
19
src/utils.py
@ -10,19 +10,9 @@ CONFIG_FILE = './config.yml'
|
|||||||
|
|
||||||
config = load(open(CONFIG_FILE, 'r'), Loader=Loader)
|
config = load(open(CONFIG_FILE, 'r'), Loader=Loader)
|
||||||
persistence = PicklePersistence(DB_FILE)
|
persistence = PicklePersistence(DB_FILE)
|
||||||
# persistence.load_singlefile()
|
|
||||||
updater: Updater = Updater(config['token'], use_context=True, persistence=persistence)
|
updater: Updater = Updater(config['token'], use_context=True, persistence=persistence)
|
||||||
|
|
||||||
|
|
||||||
class Section(Enum):
|
|
||||||
Watchlist = 'watchlist'
|
|
||||||
API_Key = 'api_key'
|
|
||||||
Running = 'running'
|
|
||||||
Interval = 'interval' # Time axis of the graph
|
|
||||||
Frequency = 'frequency' # How ofter updates should be sent
|
|
||||||
LastRun = 'last_run'
|
|
||||||
|
|
||||||
|
|
||||||
def current_timestamp():
|
def current_timestamp():
|
||||||
return int(datetime.now().timestamp())
|
return int(datetime.now().timestamp())
|
||||||
|
|
||||||
@ -32,11 +22,8 @@ def delta_timestamp(**kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def parse_command(update: Update) -> (str, str):
|
def parse_command(update: Update) -> (str, str):
|
||||||
|
"""
|
||||||
|
Splits the command from the rest of the message and returns the tuple
|
||||||
|
"""
|
||||||
key, value = (update.message.text.split(' ', 1)[1].split(' ', 1) + [None])[:2]
|
key, value = (update.message.text.split(' ', 1)[1].split(' ', 1) + [None])[:2]
|
||||||
return key, value
|
return key, value
|
||||||
|
|
||||||
|
|
||||||
def parse_callback(update: Update) -> str:
|
|
||||||
selected = update.callback_query.data
|
|
||||||
cleaned = ''.join(selected.split(':')[1:]) # Remove the pattern from the start
|
|
||||||
return cleaned
|
|
||||||
|
Loading…
Reference in New Issue
Block a user