omerXfaruq commited on
Commit
4717db6
1 Parent(s): 23a4209

FULL COMMIT

Browse files
app.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+
4
+ def start_app(telegram_token: str, ngrok_token: str):
5
+ cmnd = f"bash run_with_ngrok.sh {telegram_token} {ngrok_token}"
6
+ os.system(cmnd)
7
+ return 'Application started'
8
+
9
+ demo = gr.Interface(
10
+ start_app,
11
+ [gr.Textbox(label="Telegram Token"),gr.Textbox(label="NGROK Token")],
12
+ [gr.Textbox(label="Result")]
13
+ ).launch()
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ black
2
+ pytest
3
+ asyncio
4
+ sqlmodel
5
+ sqlalchemy
6
+ pyngrok
7
+ fastapi
8
+ uvicorn
9
+ pydantic
10
+ typing
11
+ httpx
12
+ datetime
13
+ curl
run_with_ngrok.sh ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ export TELEGRAM_TOKEN=$1
2
+ export NGROK_TOKEN=$2
3
+ python3 -m src.__init__ ngrok >> logs.txt
src/__init__.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+
4
+ from pyngrok import ngrok
5
+ import uvicorn
6
+ import asyncio
7
+
8
+ from .events import Events
9
+ from .constants import Constants
10
+
11
+ __all__ = []
12
+
13
+ if __name__ == "__main__":
14
+ if Events.TOKEN is None:
15
+ sys.exit("No TELEGRAM_TOKEN found in the environment, exiting now.")
16
+
17
+ PORT = Events.PORT
18
+ loop = asyncio.get_event_loop()
19
+
20
+ # Run with ngrok if the parameter is given
21
+ running_option = None
22
+ if len(sys.argv) == 2:
23
+ running_option = sys.argv[1]
24
+
25
+ if running_option == "ngrok":
26
+ ngrok_token = str(os.environ.get("NGROK_TOKEN"))
27
+ if ngrok_token == "None":
28
+ print(
29
+ "NGROK auth token is not found in the environment. Ngrok will timeout after a few hours."
30
+ )
31
+ else:
32
+ print(f"NGROK TOKEN: {ngrok_token}")
33
+ ngrok.set_auth_token(ngrok_token)
34
+ http_tunnel = ngrok.connect(PORT, bind_tls=True)
35
+ ssh_tunnel = ngrok.connect(22, "tcp")
36
+ public_url = http_tunnel.public_url
37
+ ssh_url = ssh_tunnel.public_url
38
+ Events.HOST_URL = public_url
39
+ _ = loop.run_until_complete(
40
+ Events.send_a_message_to_user(
41
+ Constants.BROADCAST_CHAT_ID, f"ssh: {ssh_url}, http:{public_url}"
42
+ )
43
+ )
44
+ else:
45
+ public_url = loop.run_until_complete(Events.get_public_ip())
46
+ Events.HOST_URL = f"https://{public_url}"
47
+ if running_option == "self_signed":
48
+ Events.SELF_SIGNED = True
49
+
50
+ print(Events.HOST_URL)
51
+ success = loop.run_until_complete(Events.set_telegram_webhook_url())
52
+
53
+ if success:
54
+ uvicorn.run(
55
+ "src.listener:app",
56
+ host="0.0.0.0",
57
+ port=PORT,
58
+ reload=True,
59
+ log_level="info",
60
+ )
61
+ else:
62
+ print("Fail, closing the app.")
src/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (1.45 kB). View file
src/__pycache__/constants.cpython-310.pyc ADDED
Binary file (25.1 kB). View file
src/__pycache__/db.cpython-310.pyc ADDED
Binary file (10.3 kB). View file
src/__pycache__/events.cpython-310.pyc ADDED
Binary file (5.83 kB). View file
src/__pycache__/listener.cpython-310.pyc ADDED
Binary file (2.43 kB). View file
src/__pycache__/message_validations.cpython-310.pyc ADDED
Binary file (3.11 kB). View file
src/__pycache__/response_logic.cpython-310.pyc ADDED
Binary file (5.98 kB). View file
src/constants.py ADDED
@@ -0,0 +1,561 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .db import default_schedule, get_user_status
2
+
3
+
4
+ class Constants:
5
+ smile = "😊"
6
+ hello = "👋🏻"
7
+ sad = "😔"
8
+ sun_glasses = "😎"
9
+
10
+ BROADCAST_CHAT_ID = -1001786782026
11
+ FEEDBACK_FORWARD_CHAT_ID = -683998033
12
+ BOT_ID = 5065052385
13
+
14
+ class Common:
15
+ @staticmethod
16
+ def inactive_user(name: str, language_code: str = "en") -> str:
17
+ if language_code == "tr":
18
+ return f"{name}, sistemime kayıtlı değilsin, you are not in the system, please join by typing; *join* or /join. {Constants.smile}"
19
+ else:
20
+ return f"{name}, you are not in the system, please join by typing; *join* or /join. {Constants.smile}"
21
+
22
+ @staticmethod
23
+ def no_memory_found(name: str, language_code: str = "en") -> str:
24
+ if language_code == "tr":
25
+ return f"{name}, ne yazık ki hatıra kasan boş. Lütfen bu komutla not ekle *add Cümle* {Constants.smile}"
26
+ else:
27
+ return f"{name}, I could not find any note in your Vault. Please add a note with, *add Sentence* {Constants.smile}"
28
+
29
+ @staticmethod
30
+ def unknown_command(name: str, language_code: str = "en") -> str:
31
+
32
+ if language_code == "tr":
33
+ return (
34
+ f"Sevgili {name}, ne yazık ki bu komutu bilmiyorum {Constants.sad}"
35
+ f"\n- /start veya *start* ile başlangıç mesajını görebilirsin"
36
+ f"\n- /leave veya *leave* ile günlük hatırlatmaları durdurabilirsin"
37
+ f"\n- /send veya *send* ile rastgele bir not yollarım"
38
+ f"\n- *send number* ile çok sayıda not yollarım"
39
+ f"\n- /status veya *status* ile status bilgini yollarım"
40
+ f"\n- /list veya *list* ile tüm notlarını gönderirim"
41
+ f"\nTemel komutlarım bunlardı, günlük takvimi ayarlama vb. diğer tüm detaylı komutları görmek için, *help veya /help"
42
+ )
43
+ else:
44
+ return (
45
+ f"Dear {name}, unfortunately I do not know that command {Constants.sad}"
46
+ f"\n- /start or *start* to see the start message"
47
+ f"\n- /leave or *leave* to deactivate daily reminders"
48
+ f"\n- /send or *send* to get a random note"
49
+ f"\n- *send number* to get multiple random notes"
50
+ f"\n- /status or *status* to get your status information"
51
+ f"\n- /list or *list* to list notes"
52
+ f"\nThese were my main commands, to see additional commands like editing daily schedule please use, *help* or /help"
53
+ )
54
+
55
+ class Start:
56
+ @staticmethod
57
+ def start_message(name: str, language_code: str = "eng") -> str:
58
+ if language_code == "tr":
59
+ return (
60
+ f"Merhabalar {name} {Constants.hello}"
61
+ f"\nHatıra Kasası notlarını kaydetmene imkan sağlar ve her gün sana rastgele notlar yollar."
62
+ f"\n\nHayatımızda karşılaştığımız güzel ve önemli cümleleri bir yere not almak ve sonrasında onları hatırlamak oldukça zor değil mi {Constants.sad}?"
63
+ f"\nİşte ben bu sorunu oldukça basit ve kullanması kolay bir yöntemle çözüyorum {Constants.sun_glasses}. Zira karışık çözümleri hayatımıza sokmak zor {Constants.sad}."
64
+ f"\nBu yöntemdeki güzellik şurada, bana verdiğin her notu elbet bir gün sana yollayacağım. Ne zaman yollayacağımı da düşünmene gerek yok."
65
+ f"\nBu güzel fikri verdiği için sevgili hanımım Seyyide'ye teşekkür ederim."
66
+ f"\n- Botu anlatan kısa rehbere geçmek için buyur tıkla, /tutorial1"
67
+ )
68
+ else:
69
+ return (
70
+ f"Hello {name} {Constants.hello}"
71
+ f"\nKeeping note of beautiful & important stuff that we come across throughout the life, and later remembering them is quite difficult isn't it 😔? Here is the Memory Vault for the rescue! Just take your notes, and I will occasionally remind them to you 😎"
72
+ f"\nUnfortunately in this era our days&agendas are very busy and it is very hard to follow something consistently. Memory Vault helps us on this front by continuously reminding our notes to us so that you won't ever forget them. You can use it for"
73
+ f"\n1. Habit Building"
74
+ f"\n2. Language Learning"
75
+ f"\n3. Learning the way of Entrepreneurship"
76
+ f"\n4. Remembering names"
77
+ f"\n5. Notetaking"
78
+ f"\n6. Or, Anything Custom, Memory Vault is very flexible and general solution! "
79
+ f"\n"
80
+ f"\n❤️ Sincerely thanks to my dear wife Seyyide for the beautiful idea."
81
+ f"\n"
82
+ f"\n- To start to a small tutorial please click, /tutorial1"
83
+ )
84
+
85
+ @staticmethod
86
+ def group_warning(name: str, language_code: str = "eng") -> str:
87
+ if language_code == "tr":
88
+ return (
89
+ f"\nBu arada Hatıra Kasasını gruplarda kullanmak için onu ya"
90
+ f"\n- Grup admini yapmalı -- Bu durumda Hatıra Kasası gruptaki tüm mesajları dinleyip cevap verecektir"
91
+ f"\n- Ya da Hatıra Kasası'nın herhangi bir mesajını yanıtlamalısın."
92
+ )
93
+ else:
94
+ return (
95
+ f"Btw, to use Memory Vault in a group you should either"
96
+ f"\n- Make Memory-Vault group admin -- In this case Memory Vault will listen and reply to every message."
97
+ f"\n- Or reply to any message from Memory Vault to interact."
98
+ )
99
+
100
+ class Help:
101
+ @staticmethod
102
+ def help_message(name: str, language_code: str = "eng") -> str:
103
+ if language_code == "tr":
104
+ return (
105
+ f"\n\nHafıza Kasası sana her gün, takvimindeki saatlerde kasandan rastgele notlar gönderir."
106
+ f"\n- /help veya *help* yardım mesajını alabilirsin"
107
+ f"\n- /join veya *join* ile günlük gönderimi aktifleştirebilirsin"
108
+ f"\n- /leave veya *leave* ile günlük hatırlatmayı durdurabilirsin"
109
+ f"\n- /send veya *send* ile rastgele bir not yollarım"
110
+ f"\n- *send number* ile çok sayıda not yollarım"
111
+ f"\n- /status veya *status* ile status bilgini yollarım"
112
+ f"\n- /list veya *list* ile tüm notlarını gönderirim"
113
+ f"\n\n- *add Note* ile kasana bir not ekleyebilirsin"
114
+ f"\nÖrnek:"
115
+ f"\n*add Vakit hiç bir zaman geri gelmez*"
116
+ f"\n\n- *delete id* ile bir notu silebilirsin. Not id'lerini bu komutlarla öğrenebilirsin, *list* veya /list"
117
+ f"\nÖrnek:"
118
+ f"\n*delete 2*"
119
+ f"\n\n- *gmt zaman-dilimi* ile zaman dilimi belirleyebilirsin, varsayılan zaman dilimi *GMT0*'dır"
120
+ f"\nÖrnek:"
121
+ f"\nGMT+3: *gmt 3*"
122
+ f"\nGMT0: *gmt 0*"
123
+ f"\nGMT-5: *gmt -5*"
124
+ f"\n\n- /support veya *support* ile beni nasıl destekleyebileceğini öğrenebilirsin"
125
+ f"\n- *feedback Cümle* ile bot hakkındaki düşüncelerini veya ƒeedback'lerini yollayabilirsin"
126
+ f"\n\n*Schedule(takvim) hakkındaki komutlar:*"
127
+ f"\nHer gün takvimindeki saat başlarında sana notlar yollarım. Varsayılan takvim saatleri *{default_schedule}*'dır. Yani her gün 8:00 ve 20:00'de sana bir adet not yollayacağım."
128
+ f"\nSchedule komutlarıyla kendi günlük takvimini oluşturabilirsin. Ayrıca bir saati birden fazla kez ekleyerek o saatte birden çok not alabilirsin."
129
+ f"\n- /schedule veya *schedule* ile şuanki takvimini yollarım"
130
+ f"\n- *schedule reset* ile takvimini varsayılan takvime({default_schedule}) çekerim"
131
+ f"\n- *schedule add saat1 saat2 saat3* ile saatleri takvimine eklerim"
132
+ f"\nÖrnek:"
133
+ f"\n*schedule add 1 3 9 11*"
134
+ f"\n- *schedule remove saat* ile bir saati takviminden tamamen kaldırabilirsin"
135
+ f"\nÖrnek:"
136
+ f"\n*schedule remove 8*"
137
+ f"\n\n*Grup Kullanımı*"
138
+ f"\n - Beni *gruplarda da kullanabilirsin*, gruba ekleyip yönetici yapman yeterli. Yönetici yapmak istemiyorsan da grupta benim mesajlarıma yanıtla yaparak da komutları kullanabilirsin."
139
+ f"\n- *Birden fazla Hatıra Kasasına* sahip olmak için beni farklı gruplarda kullanabilirsin. Mesela bir kelime öğrenme grubu kurabilirsin."
140
+ f"\n- Örnek grup: Kuran'ı Kerim'den Dualar(@PrayersFromQuran)"
141
+ )
142
+ else:
143
+ return (
144
+ f"\n\nMemory Vault will send you random notes from your memory vault, at the hours in your schedule every day."
145
+ f"\n- /help or *help* to get help message"
146
+ f"\n- /join or *join* to activate daily note sending"
147
+ f"\n- /leave or *leave* to deactivate daily reminders"
148
+ f"\n- /send or *send* to get a random note"
149
+ f"\n- *send number* to get multiple random notes"
150
+ f"\n- /status or *status* to get your status information"
151
+ f"\n- /list or *list* to list notes"
152
+ f"\n\n- *add Note* to add a note to your memory vault"
153
+ f"\nExample:"
154
+ f"\n*add Time never does come back*"
155
+ f"\n\n- *delete id* to delete a note. You can learn the note ids with the command, *list* or /list"
156
+ f"\nExample:"
157
+ f"\n*delete 2*"
158
+ f"\n\n- *gmt timezone* to set your timezone, the default timezone is *GMT0*"
159
+ f"\nExamples:"
160
+ f"\nGMT+3: *gmt 3*"
161
+ f"\nGMT0: *gmt 0*"
162
+ f"\nGMT-5: *gmt -5*"
163
+ f"\n\n- /support or *support* to learn how to support me"
164
+ f"\n- *feedback Sentence* to send your thoughts and feedbacks about the bot"
165
+ f"\n\n*Schedule related commands:*"
166
+ f"\nI send notes according to the hours in your schedule. Default schedule hours are *{default_schedule}*. I will send you a note at 8:00 and 20:00 everyday."
167
+ f"\nYou can create your own daily schedule. Furthermore you can add an hour multiple times to receive multiple notes at that hour."
168
+ f"\n- /schedule or *schedule* to display your current schedule"
169
+ f"\n- *schedule reset* to reset your schedule to the default schedule"
170
+ f"\n- *schedule add hour1 hour2 hour3* to add hours to your schedule"
171
+ f"\nExample:"
172
+ f"\n*schedule add 1 3 9 11*"
173
+ f"\n- *schedule remove hour* to remove an hour from your schedule"
174
+ f"\nExample:"
175
+ f"\n*schedule remove 8*"
176
+ f"\n\n*Group Usage*"
177
+ f"\n - You can *use me in groups* as well, just add me to a group and promote me to admin there. If you don't want to make me an admin, you can reply to my messages in the group to use my commands."
178
+ f"\n- Furthermore *you can have multiple memory vaults* by using different groups. For example I would serve you well in a *language learning group*, where you add words you want to remember to your memory vault."
179
+ f"\n- Example group: @PrayersFromQuran"
180
+ )
181
+
182
+ class Join:
183
+ @staticmethod
184
+ def successful_join(name: str, language_code: str = "en") -> str:
185
+ if language_code == "tr":
186
+ return (
187
+ f"Hoşgeldin, sefa geldin {name}! Günlük not yollamayı açtın. Takvimindeki saatlere göre sana hatıra kasandan her gün notlar yollayacağım."
188
+ f"Varsayılan takvimindeki saatleri {default_schedule}'dır. (8 -> 8:00, 20 -> 20:00). Daha detaylı bilgi için, *help* veya /help."
189
+ f"\nYeni bir kullanıcı isen lütfen bu komuta tıklayarak rehbere başla, /tutorial1 {Constants.smile}"
190
+ )
191
+ else:
192
+ return (
193
+ f"Welcome onboard {name}! "
194
+ f"\nYou activated daily note sending. I will send you random notes from your memory vault according to your schedule."
195
+ f"The default hours in the schedule are {default_schedule}(8 -> 8:00, 20 -> 20:00). You can get more detailed information by writing, *help* or /help."
196
+ f"\nIf you are a new user, please start the tutorial by clicking, /tutorial1 {Constants.smile}"
197
+ )
198
+
199
+ @staticmethod
200
+ def already_joined(name: str, language_code: str = "en") -> str:
201
+ if language_code == "tr":
202
+ return f"{name}, hesabın zaten aktif. Hesabının mevcut durumunu görmek için bu komutu kullanabilirsin, /status."
203
+ else:
204
+ return f"{name}, Your account is already active. You can see your status via, /status."
205
+
206
+ class Leave:
207
+ @staticmethod
208
+ def successful_leave(name: str, language_code: str = "en") -> str:
209
+ if language_code == "tr":
210
+ return (
211
+ f"Allah'a emanet ol {name}. Günlük not yollamamı kapattın, ama merak etme hatıra kasan benimle."
212
+ f"İstediğin zaman bu komutlarla geri gelebilirsin, *join veya /join."
213
+ )
214
+ else:
215
+ return (
216
+ f"Good bye {name}, you deactivated daily note sending. It was nice to have you here. "
217
+ f"Your memory vault remains with me, you can return whenever you wish with command, *join* or /join."
218
+ )
219
+
220
+ @staticmethod
221
+ def already_left(name: str, language_code: str = "en") -> str:
222
+ if language_code == "tr":
223
+ return f"{name}, hesabın zaten atıl durumda."
224
+ else:
225
+ return f"{name}, Your account is already inactive."
226
+
227
+ class Send:
228
+ @staticmethod
229
+ def send_count_out_of_bound(name: str, language_code: str = "en") -> str:
230
+ if language_code == "tr":
231
+ return f"{name}, lütfen 1<n<50 arasında bir sayı ver, örn: *send 3*."
232
+ else:
233
+ return f"{name}, please give a number which is 1<n<50, ie: *send 3*."
234
+
235
+ class List:
236
+ @staticmethod
237
+ def list_messages(name: str, note_count: int, language_code: str = "en") -> str:
238
+ if language_code == "tr":
239
+ return (
240
+ f"{name} Destur! {note_count} notun birer birer akacak."
241
+ f"\n\nHatıra kasasının kapılarını açın!"
242
+ f"\n*id | not*"
243
+ )
244
+ else:
245
+ return (
246
+ f"Brace yourself {name}, you will receive {note_count} notes one by one."
247
+ f"\n\nOpen the gates of the memory vault!"
248
+ f"\n*id | note*"
249
+ )
250
+
251
+ class Add:
252
+ @staticmethod
253
+ def no_sentence(name: str, language_code: str = "en") -> str:
254
+ if language_code == "tr":
255
+ return f"{name} add kelimesinden sonra bir cümle bulamadım. Lütfen bu komutu kullan: *add Cümle*."
256
+ else:
257
+ return f"There is no sentence found after the word *add*. Please use this command: *add Sentence*"
258
+
259
+ @staticmethod
260
+ def already_added(name: str, language_code: str = "en") -> str:
261
+ if language_code == "tr":
262
+ return f"{name} bu not zaten kasada {Constants.smile}"
263
+
264
+ else:
265
+ return (
266
+ f"{name} the note is already in your memory vault {Constants.smile}"
267
+ )
268
+
269
+ @staticmethod
270
+ def success(name: str, language_code: str = "en", note: str = "") -> str:
271
+ if language_code == "tr":
272
+ return (
273
+ f"{name}, not kasana eklendi. Merak etme, onu güvende tutacağım {Constants.smile}"
274
+ f"\n*Not*: \n{note}"
275
+ f""
276
+ f"\n\n Eğer son eklediğin notu silmek istiyorsan, bu komutu kullan */deletelast*"
277
+ )
278
+
279
+ else:
280
+ return (
281
+ f"{name}, note is added to your memory vault. No worries, I will keep it safe {Constants.smile}"
282
+ f"\n*Note*: \n{note}"
283
+ f""
284
+ f"\n\nIf you want to delete the last added note, you can use */deletelast*"
285
+ )
286
+
287
+ class Delete:
288
+ @staticmethod
289
+ def no_id(name: str, language_code: str = "en") -> str:
290
+ if language_code == "tr":
291
+ return f"{name}, bana notun id'sini vermen gerekiyor, örn: *delete 2*, bu komut ile id'leri öğrenebilirsin *list* veya /list"
292
+
293
+ else:
294
+ return f"{name}, need to give me id of the note, ie: *delete 2*, you can get it by using command, *list* or /list"
295
+
296
+ @staticmethod
297
+ def success(name: str, language_code: str = "en", note: str = "") -> str:
298
+ if language_code == "tr":
299
+ return (
300
+ f"{name}, not kasadan silindi. Unutulan hatıraya elveda {Constants.sad}"
301
+ f"\n*Silinen Not*:"
302
+ f"\n{note}"
303
+ )
304
+ else:
305
+ return (
306
+ f"{name}, your note is deleted from your memory vault. Good bye to the forgotten memory {Constants.sad}"
307
+ f"\n*Deleted Note*:"
308
+ f"\n{note}"
309
+ )
310
+
311
+ class Schedule:
312
+ @staticmethod
313
+ def empty_schedule(name: str, language_code: str = "en") -> str:
314
+ if language_code == "tr":
315
+ return f"{name}, takvimin boş, takvimine saatleri eklemek için bu komutu kullanabilirsin: *schedule add hour1 hour2 hour3*, örn: *schedule add 8 12*"
316
+
317
+ else:
318
+ return f"{name}, your schedule is empty, you can add hours to your schedule via, *schedule add hour1 hour2 hour3*, ie: *schedule add 8 12*"
319
+
320
+ @staticmethod
321
+ def success(name: str, language_code: str = "en", schedule: str = "") -> str:
322
+ if language_code == "tr":
323
+ return (
324
+ f"{name}, güncel takvimin aşağıda, takvimindeki saat başlarında rastgele bir not alacaksın. örn: 8 -> 8:00"
325
+ f"\n*Takvim*: {schedule}"
326
+ f""
327
+ f"\n\nUyarı: Eğer bu bottan faydalanmak istiyorsan, takvimini dolup taşırmamaya dikkat et ve gelen mesajlara dikkatini ver, göz atıp geçme."
328
+ )
329
+ else:
330
+ return (
331
+ f"{name}, your current schedule is below, You will get a random note at each of these hours everyday. ie: 8 -> 8:00"
332
+ f"\n*Schedule*: {schedule}"
333
+ f""
334
+ f"\n\nWarning: If you want to make use of this bot, be careful to not overflow your schedule and give attention to the incoming messages, do not just look and pass."
335
+ )
336
+
337
+ @staticmethod
338
+ def no_number_found(name: str, language_code: str = "en") -> str:
339
+ if language_code == "tr":
340
+ return f"{name}, *schedule add* komutu sonrasında bir sayı bulamadım, doğru kullanım örneği: *schedule add 1 3 5 21*"
341
+
342
+ else:
343
+ return f"{name}, there is no numbers found after *schedule add*, correct usage example: *schedule add 1 3 5 21*"
344
+
345
+ @staticmethod
346
+ def add_incorrect_number_input(name: str, language_code: str = "en") -> str:
347
+ if language_code == "tr":
348
+ return f"{name}, lütfen girdi olarak sayılar kullan, 0<=sayı<=23, örn: *schedule add 1 3 5 21*"
349
+
350
+ else:
351
+ return f"{name}, please use numbers 0<=number<=23, ie: *schedule add 1 3 5 21*"
352
+
353
+ @staticmethod
354
+ def remove_incorrect_number_input(name: str, language_code: str = "en") -> str:
355
+ if language_code == "tr":
356
+ return f"{name}, lütfen girdi olarak sayı kullan, 0<=sayı<=23, örn: *schedule remove 8*"
357
+
358
+ else:
359
+ return (
360
+ f"{name}, please use number 0<=number<=23, ie: *schedule remove 8*"
361
+ )
362
+
363
+ @staticmethod
364
+ def unknown_command(name: str, language_code: str = "en") -> str:
365
+ if language_code == "tr":
366
+ return (
367
+ f"{name}, bu komutu bilmiyorum. Aşağıdaki komutları kullanabilirsin."
368
+ f"\n*schedule*"
369
+ f"\n*schedule add 8 12*"
370
+ f"\n*schedule reset*"
371
+ f"\n*schedule remove 8*"
372
+ )
373
+ else:
374
+ return (
375
+ f"{name}, I do not know that command. You can support the commands below."
376
+ f"\n*schedule*"
377
+ f"\n*schedule add 8 12*"
378
+ f"\n*schedule reset*"
379
+ f"\n*schedule remove 8*"
380
+ )
381
+
382
+ class Gmt:
383
+ @staticmethod
384
+ def success(name: str, language_code: str = "en", gmt: int = 0) -> str:
385
+ if language_code == "tr":
386
+ return f"{name}, güncel saat dilimin: GMT{gmt}."
387
+ else:
388
+ return f"{name}, your current timezone is: GMT{gmt}."
389
+
390
+ @staticmethod
391
+ def incorrect_timezone(name: str, language_code: str = "en") -> str:
392
+ if language_code == "tr":
393
+ return f"{name}, lütfen saat dilimini doğru kullan, örn: *gmt 3* or *gmt -3*"
394
+ else:
395
+ return f"{name}, please give your timezone correctly, ie: *gmt 3* or *gmt -3*"
396
+
397
+ class Broadcast:
398
+ @staticmethod
399
+ def no_sentence_found(name: str, language_code: str = "en") -> str:
400
+ if language_code == "tr":
401
+ return f"{name}, *broadcast* kelimesi sonrasında herhangi bir cümle bulamadım {Constants.sad}, doğru örn: *broadcast Cümle*"
402
+ else:
403
+ return f"{name}, there is no sentence found after the word *broadcast* {Constants.sad}, correct usage: *broadcast Sentence*"
404
+
405
+ @staticmethod
406
+ def success(name: str, language_code: str = "en") -> str:
407
+ if language_code == "tr":
408
+ return f"{name}, broadcast yollandı {Constants.smile}"
409
+ else:
410
+ return f"{name}, broadcast is sent {Constants.smile}"
411
+
412
+ @staticmethod
413
+ def no_right(name: str, language_code: str = "en") -> str:
414
+ if language_code == "tr":
415
+ return f"{name}, broadcast hakkın yok {Constants.smile}"
416
+ else:
417
+ return f"{name}, you have no broadcast right {Constants.smile}"
418
+
419
+ class Status:
420
+ @staticmethod
421
+ def get_status(
422
+ name: str,
423
+ language_code: str = "en",
424
+ gmt: int = 0,
425
+ active: bool = True,
426
+ schedule: str = "",
427
+ note_count: int = 0,
428
+ ) -> str:
429
+ if language_code == "tr":
430
+ if active:
431
+ is_active = "aktif"
432
+ else:
433
+ is_active = "pasif"
434
+ return (
435
+ f"Mevcut durumun:"
436
+ f"\n- Gmt: *GMT{gmt}*"
437
+ f"\n- Günlük gönderim aktif: *{is_active}*"
438
+ f"\n- Hatıra Kasandaki not sayısı: {note_count}"
439
+ f"\n- Takvim: {schedule}"
440
+ f""
441
+ f"\n\nUyarı: Eğer bu bottan faydalanmak istiyorsan, takvimini dolup taşırmamaya dikkat et ve gelen mesajlara dikkatini ver, göz atıp geçme."
442
+ )
443
+ else:
444
+ return (
445
+ f"Your current status:"
446
+ f"\n- Gmt: *GMT{gmt}*"
447
+ f"\n- Daily sending is active: *{active}*"
448
+ f"\n- Number of notes in your Memory Vault: {note_count}"
449
+ f"\n- Schedule: {schedule}"
450
+ f""
451
+ f"\n\nWarning: If you want to make use of this bot, be careful to not overflow your schedule and give attention to the incoming messages, do not just look and pass."
452
+ )
453
+
454
+ class Feedback:
455
+ @staticmethod
456
+ def no_message(name: str, language_code: str = "en"):
457
+ if language_code == "tr":
458
+ return f"{name}, *feedback* sonrasında bir cümle bulamadım {Constants.sad}, doğru örnek: *feedback Cümle*"
459
+ else:
460
+ return f"{name}, there is no message found after the word *feedback* {Constants.sad}, correct example: *feedback Sentence*"
461
+
462
+ @staticmethod
463
+ def success(name: str, language_code: str = "en", feedback: str = ""):
464
+ if language_code == "tr":
465
+ return (
466
+ f"{name}, feedback'ini yöneticiye ilettim, desteğin için çok teşekkür ederim {Constants.smile}"
467
+ f"\nFeedback: *{feedback}*"
468
+ )
469
+ else:
470
+ return (
471
+ f"{name}, I forwarded your feedback to the admin, thank you for your support {Constants.smile}"
472
+ f"\nFeedback: *{feedback}*"
473
+ )
474
+
475
+ @staticmethod
476
+ def fail(name: str, language_code: str = "en"):
477
+ if language_code == "tr":
478
+ return f"{name}, feedback'i yöneticiye iletemedim, bir hata oluştu."
479
+ else:
480
+ return f"{name}, I could not forward your feedback to the admin, an error occurred."
481
+
482
+ class Support:
483
+ @staticmethod
484
+ def support(name: str, language_code: str = "en"):
485
+ if language_code == "tr":
486
+ return (
487
+ f"Teşekkür ederim {name}, beni desteklemek için"
488
+ f"\n- @memory_vault_bot'u arkadaşlarınla paylaşabilir"
489
+ f"\n- Bu komutla bana feedback verebilirsin, *feedback cümle*"
490
+ f"\n- Github repo'ma yıldız verebilirsin, https://github.com/FarukOzderim/Memory-Vault/"
491
+ )
492
+ else:
493
+ return (
494
+ f"Thank you {name}, to support me you can"
495
+ f"\n- Share me with your friends"
496
+ f"\n- Give feedback using the command, *feedback sentence*"
497
+ f"\n- Star the github repository at https://github.com/FarukOzderim/Memory-Vault/"
498
+ )
499
+
500
+ class Tutorial:
501
+ @staticmethod
502
+ def tutorial_1(name: str, language_code: str = "en"):
503
+ if language_code == "tr":
504
+ return (
505
+ f"*gmt zaman-dilimi* ile zaman dilimi belirleyebilirsin, varsayılan zaman dilimi *GMT0*'dır. Bu arada Türkiye GMT+3 zaman diliminde."
506
+ f"\nÖrnek:"
507
+ f"\nGMT+3: *gmt 3*"
508
+ f"\nGMT0: *gmt 0*"
509
+ f"\nGMT-5: *gmt -5*"
510
+ f"\n\nBir sonraki rehber adımına geçmek için, /tutorial2"
511
+ )
512
+ else:
513
+ return (
514
+ f"Use *gmt timezone* to set your timezone, the default timezone is *GMT0*. Btw New York is GMT-5, London is GMT0, Malaysia is GMT+8."
515
+ f"\nExamples:"
516
+ f"\nGMT+3: *gmt 3*"
517
+ f"\nGMT0: *gmt 0*"
518
+ f"\nGMT-5: *gmt -5*"
519
+ f"\n\nFor the next tutorial step please use, /tutorial2"
520
+ )
521
+
522
+ @staticmethod
523
+ def tutorial_2(name: str, language_code: str = "en"):
524
+ if language_code == "tr":
525
+ return (
526
+ f"Hatıra Kasana bir not eklemek için *add Cümle* komutunu kullanabilirsin."
527
+ f"\nÖrnek:"
528
+ f"\n*add Zaman çok kıymetlidir, her daim eriyen bir dondurmaya benzer.*"
529
+ f"\n\nBir sonraki rehber adımına geçmek için, /tutorial3"
530
+ )
531
+ else:
532
+ return (
533
+ f"To add a note to your Memory Vault, please use the command, *add Sentence*."
534
+ f"\nExample:"
535
+ f"\n*add Time never does come back*"
536
+ f"\n\nFor the next tutorial step please use, /tutorial3"
537
+ )
538
+
539
+ @staticmethod
540
+ def tutorial_3(name: str, language_code: str = "en"):
541
+ if language_code == "tr":
542
+ return (
543
+ f"\n- /leave veya *leave* ile günlük hatırlatmayı durdurabilirsin"
544
+ f"\n- /send veya *send* ile rastgele bir not yollarım"
545
+ f"\n- *send number* ile çok sayıda not yollarım"
546
+ f"\n- /status veya *status* ile status bilgini yollarım"
547
+ f"\n- /list veya *list* ile tüm notlarını gönderirim"
548
+ f"\n\n{name} tebrikler rehberi bitirdin {Constants.smile}. "
549
+ f"\nTemel komutlarım bunlardı, günlük takvimi ayarlama vb. diğer komutları görmek için, /help."
550
+ )
551
+
552
+ else:
553
+ return (
554
+ f"\n- /leave or *leave* to deactivate daily reminders"
555
+ f"\n- /send or *send* to get a random note"
556
+ f"\n- *send number* to get multiple random notes"
557
+ f"\n- /status or *status* to get your status information"
558
+ f"\n- /list or *list* to list notes"
559
+ f"\n\n{name} congratulations, you finished the tutorial {Constants.smile}. "
560
+ f"\nThese were my main commands, to see additional commands like editing daily schedule please use, /help."
561
+ )
src/db.py ADDED
@@ -0,0 +1,495 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from typing import List, Optional, Union, Tuple
3
+ from fastapi import Depends, Query
4
+ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, and_
5
+ from sqlalchemy import UniqueConstraint
6
+
7
+ default_schedule = "8,20"
8
+
9
+
10
+ class UserBase(SQLModel):
11
+ __table_args__ = (UniqueConstraint("telegram_chat_id"),)
12
+ name: str
13
+ telegram_chat_id: int
14
+ gmt: Optional[int] = 0
15
+ active: Optional[bool] = True
16
+ scheduled_hours: Optional[str] = default_schedule
17
+
18
+
19
+ class User(UserBase, table=True):
20
+ id: Optional[int] = Field(default=None, primary_key=True)
21
+ reminders: List["Reminder"] = Relationship(back_populates="user")
22
+
23
+
24
+ class UserRead(UserBase):
25
+ id: int
26
+
27
+
28
+ class UserCreate(UserBase):
29
+ pass
30
+
31
+
32
+ class ReminderBase(SQLModel):
33
+ reminder: str
34
+ user_id: Optional[int] = Field(default=None, foreign_key="user.id")
35
+
36
+
37
+ class Reminder(ReminderBase, table=True):
38
+ id: Optional[int] = Field(default=None, primary_key=True)
39
+ user: User = Relationship(back_populates="reminders")
40
+
41
+
42
+ class ReminderRead(ReminderBase):
43
+ id: int
44
+
45
+
46
+ class ReminderCreate(ReminderBase):
47
+ pass
48
+
49
+
50
+ sqlite_file_name = "database.db"
51
+ sqlite_url = f"sqlite:///{sqlite_file_name}"
52
+
53
+ connect_args = {"check_same_thread": False}
54
+ engine = create_engine(sqlite_url, echo=False, connect_args=connect_args)
55
+
56
+
57
+ def create_db_and_tables():
58
+ SQLModel.metadata.create_all(engine)
59
+
60
+
61
+ def get_session():
62
+ with Session(engine) as session:
63
+ yield session
64
+
65
+
66
+ def join_user(
67
+ user: UserCreate,
68
+ session: Session = next(get_session()),
69
+ ) -> Optional[User]:
70
+ """
71
+ Joins the user to the system.
72
+
73
+ Args:
74
+ user:
75
+ session:
76
+
77
+ Returns: User or None
78
+
79
+ """
80
+ found_user = session.exec(
81
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
82
+ ).first()
83
+ if found_user is None:
84
+ user = db_create_user(user, session)
85
+ return user
86
+ else:
87
+ if found_user.active:
88
+ return None
89
+ else:
90
+ found_user.active = True
91
+ session.add(found_user)
92
+ session.commit()
93
+ session.refresh(found_user)
94
+ return found_user
95
+
96
+
97
+ def leave_user(
98
+ user: UserCreate,
99
+ session: Session = next(get_session()),
100
+ ) -> Optional[User]:
101
+ """
102
+ Deactivates the user.
103
+
104
+ Args:
105
+ user:
106
+ session:
107
+
108
+ Returns: User or None
109
+
110
+ """
111
+ found_user = session.exec(
112
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
113
+ ).first()
114
+ if found_user is None:
115
+ return None
116
+ else:
117
+ if not found_user.active:
118
+ return None
119
+ else:
120
+ found_user.active = False
121
+ session.add(found_user)
122
+ session.commit()
123
+ session.refresh(found_user)
124
+ return found_user
125
+
126
+
127
+ def get_user_status(
128
+ telegram_chat_id: int,
129
+ session: Session = next(get_session()),
130
+ ) -> Optional[Tuple[int, bool]]:
131
+ """
132
+ Get status of the user.
133
+
134
+ Args:
135
+ telegram_chat_id:
136
+ session:
137
+
138
+ Returns: (gmt,active)
139
+
140
+ """
141
+ found_user = session.exec(
142
+ select(User).where(User.telegram_chat_id == telegram_chat_id)
143
+ ).first()
144
+ if found_user is None:
145
+ return None, None
146
+ else:
147
+ return found_user.gmt, found_user.active
148
+
149
+
150
+ def select_random_memory(
151
+ user: UserCreate,
152
+ session: Session = next(get_session()),
153
+ ) -> Optional[Union[Reminder, bool]]:
154
+ """
155
+ Select random memory from user's memory-vault.
156
+
157
+ Args:
158
+ user:
159
+ session:
160
+
161
+ Returns:
162
+
163
+ """
164
+ found_user = session.exec(
165
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
166
+ ).first()
167
+ if found_user is None:
168
+ return None
169
+ if len(found_user.reminders) > 0:
170
+ memory_list = found_user.reminders
171
+ random_memory = random.choice(memory_list)
172
+ return random_memory
173
+ else:
174
+ return False
175
+
176
+
177
+ def list_memories(
178
+ user: UserCreate,
179
+ session: Session = next(get_session()),
180
+ ) -> Optional[List[Reminder]]:
181
+ """
182
+ Return all the memory-vault.
183
+
184
+ Args:
185
+ user:
186
+ session:
187
+
188
+ Returns:
189
+
190
+ """
191
+ found_user = session.exec(
192
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
193
+ ).first()
194
+ if found_user is None:
195
+ return None
196
+
197
+ return found_user.reminders
198
+
199
+
200
+ def add_memory(
201
+ user: UserCreate,
202
+ memory: str,
203
+ session: Session = next(get_session()),
204
+ ) -> Optional[Union[Reminder, bool]]:
205
+ """
206
+ Add a memory to user's memory-vault.
207
+
208
+ Args:
209
+ user:
210
+ memory:
211
+ session:
212
+
213
+ Returns: Reminder
214
+
215
+ """
216
+ found_user = session.exec(
217
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
218
+ ).first()
219
+
220
+ if found_user is None:
221
+ return None
222
+
223
+ else:
224
+ found_memory = session.exec(
225
+ select(Reminder).where(
226
+ and_(Reminder.user == found_user, Reminder.reminder == memory)
227
+ )
228
+ ).first()
229
+ if found_memory is not None:
230
+ return False
231
+ else:
232
+ reminder = ReminderCreate(
233
+ reminder=memory,
234
+ user_id=found_user.id,
235
+ )
236
+
237
+ db_reminder = Reminder.from_orm(reminder)
238
+ session.add(db_reminder)
239
+ session.commit()
240
+ session.refresh(db_reminder)
241
+ return db_reminder
242
+
243
+
244
+ def delete_memory(
245
+ user: UserCreate,
246
+ memory_id: int,
247
+ session: Session = next(get_session()),
248
+ ) -> Union[bool, str, None]:
249
+ """
250
+ Delete a memory from user's memory-vault.
251
+
252
+ Args:
253
+ user:
254
+ memory_id:
255
+ session:
256
+
257
+ Returns: bool
258
+
259
+ """
260
+
261
+ found_user = session.exec(
262
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
263
+ ).first()
264
+ if found_user is None:
265
+ return None
266
+ if len(found_user.reminders) > memory_id:
267
+ reminder = found_user.reminders[memory_id]
268
+ memory = reminder.reminder
269
+ session.delete(reminder)
270
+ session.commit()
271
+ return memory
272
+ else:
273
+ return False
274
+
275
+
276
+ def delete_last_memory(
277
+ user: UserCreate,
278
+ session: Session = next(get_session()),
279
+ ) -> Union[bool, str, None]:
280
+ """
281
+ Delete the last memory from user's memory-vault.
282
+
283
+ Args:
284
+ user:
285
+ session:
286
+
287
+ Returns: bool
288
+
289
+ """
290
+
291
+ found_user = session.exec(
292
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
293
+ ).first()
294
+ if found_user is None:
295
+ return None
296
+ if found_user.reminders:
297
+ reminder = found_user.reminders.pop()
298
+ memory = reminder.reminder
299
+ session.delete(reminder)
300
+ session.commit()
301
+ return memory
302
+ else:
303
+ return False
304
+
305
+
306
+ def update_gmt(
307
+ user: UserCreate,
308
+ gmt: int,
309
+ session: Session = next(get_session()),
310
+ ) -> Optional[User]:
311
+ """
312
+ Update GMT of the user.
313
+
314
+ Returns: User or None
315
+ """
316
+ found_user = session.exec(
317
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
318
+ ).first()
319
+ if found_user is None:
320
+ return None
321
+ else:
322
+ found_user.gmt = gmt
323
+ session.add(found_user)
324
+ session.commit()
325
+ session.refresh(found_user)
326
+ return found_user
327
+
328
+
329
+ def get_schedule(
330
+ user: UserCreate,
331
+ session: Session = next(get_session()),
332
+ ) -> Optional[str]:
333
+ """
334
+ Get schedule of the user.
335
+
336
+ Args:
337
+ user:
338
+ session:
339
+
340
+ Returns: str or None
341
+ """
342
+ found_user = session.exec(
343
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
344
+ ).first()
345
+ if found_user is None:
346
+ return None
347
+ else:
348
+ return found_user.scheduled_hours
349
+
350
+
351
+ def reset_schedule(
352
+ user: UserCreate,
353
+ session: Session = next(get_session()),
354
+ ) -> Optional[str]:
355
+ """
356
+ Reset schedule of the user.
357
+
358
+ Args:
359
+ user:
360
+ session:
361
+
362
+ Returns: str or None
363
+
364
+ """
365
+ found_user = session.exec(
366
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
367
+ ).first()
368
+ if found_user is None:
369
+ return None
370
+ else:
371
+ found_user.scheduled_hours = default_schedule
372
+ session.add(found_user)
373
+ session.commit()
374
+ session.refresh(found_user)
375
+ return found_user.scheduled_hours
376
+
377
+
378
+ def remove_hour_from_schedule(
379
+ user: UserCreate,
380
+ hour: int,
381
+ session: Session = next(get_session()),
382
+ ) -> Optional[str]:
383
+ """
384
+ Remove all appearances of hour from schedule of the user.
385
+
386
+ Args:
387
+ user:
388
+ hour:
389
+ session:
390
+
391
+ Returns: str or None
392
+
393
+ """
394
+ found_user = session.exec(
395
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
396
+ ).first()
397
+ if found_user is None:
398
+ return None
399
+ else:
400
+ old_schedule = create_schedule_array(found_user.scheduled_hours)
401
+ new_schedule = []
402
+ for number in old_schedule:
403
+ if not hour == number:
404
+ new_schedule.append(number)
405
+ str_schedule_list = [str(number) for number in new_schedule]
406
+ str_schedule = ",".join(str_schedule_list)
407
+ found_user.scheduled_hours = str_schedule
408
+ session.add(found_user)
409
+ session.commit()
410
+ session.refresh(found_user)
411
+ return found_user.scheduled_hours
412
+
413
+
414
+ def add_hours_to_the_schedule(
415
+ user: UserCreate,
416
+ schedule_list: List[int],
417
+ session: Session = next(get_session()),
418
+ ) -> Optional[str]:
419
+ """
420
+ Add hours to the schedule of the user.
421
+
422
+ Args:
423
+ user:
424
+ schedule_list:
425
+ session:
426
+
427
+ Returns: str or None
428
+
429
+ """
430
+ found_user = session.exec(
431
+ select(User).where(User.telegram_chat_id == user.telegram_chat_id)
432
+ ).first()
433
+ if found_user is None:
434
+ return None
435
+ else:
436
+ old_schedule = create_schedule_array(found_user.scheduled_hours)
437
+ new_schedule = []
438
+ for number in old_schedule:
439
+ new_schedule.append(number)
440
+ for number in schedule_list:
441
+ new_schedule.append(number)
442
+ sorted_schedule = sorted(new_schedule)
443
+ str_sorted_schedule = [str(number) for number in sorted_schedule]
444
+ str_schedule = ",".join(str_sorted_schedule)
445
+ found_user.scheduled_hours = str_schedule
446
+ session.add(found_user)
447
+ session.commit()
448
+ session.refresh(found_user)
449
+ return found_user.scheduled_hours
450
+
451
+
452
+ def db_create_user(
453
+ user: UserCreate,
454
+ session: Session = next(get_session()),
455
+ ) -> Optional[User]:
456
+ try:
457
+ user = User.from_orm(user)
458
+ session.add(user)
459
+ session.commit()
460
+ session.refresh(user)
461
+ return user
462
+
463
+ except Exception as ex:
464
+ return None
465
+
466
+
467
+ def db_read_users(
468
+ *,
469
+ only_active_users: bool = True,
470
+ session: Session = next(get_session()),
471
+ offset: int = 0,
472
+ limit: int = 100,
473
+ ) -> List[User]:
474
+ if only_active_users:
475
+ users = session.exec(
476
+ select(User).where(User.active).offset(offset).limit(limit)
477
+ ).all()
478
+ else:
479
+ users = session.exec(select(User).offset(offset).limit(limit)).all()
480
+ return users
481
+
482
+
483
+ def create_schedule_array(schedule_str: str) -> List[int]:
484
+ """
485
+ Create schedule array from schedule string splitted by comas(,).
486
+ """
487
+ if schedule_str == "":
488
+ return []
489
+ else:
490
+ schedule_list = []
491
+ str_list = schedule_str.split(",")
492
+ for str_number in str_list:
493
+ schedule_list.append(int(str_number))
494
+
495
+ return schedule_list
src/events.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import os
3
+ import sys
4
+
5
+ from typing import List
6
+ import datetime
7
+ import asyncio
8
+ from httpx import AsyncClient, Response
9
+
10
+ from .message_validations import ResponseToMessage
11
+ from .db import db_read_users, Reminder, User
12
+ from .constants import Constants
13
+
14
+
15
+ class Events:
16
+ TOKEN = os.environ.get("TELEGRAM_TOKEN")
17
+ TELEGRAM_SEND_MESSAGE_URL = f"https://api.telegram.org/bot{TOKEN}/sendMessage"
18
+ TELEGRAM_SET_WEBHOOK_URL = f"https://api.telegram.org/bot{TOKEN}/setWebhook"
19
+ TELEGRAM_SEND_DOCUMENT_URL = f"https://api.telegram.org/bot{TOKEN}/sendDocument"
20
+
21
+ PORT = 8000
22
+ HOST_URL = None
23
+ SELF_SIGNED = False
24
+
25
+ @classmethod
26
+ async def main_event(cls) -> None:
27
+ """
28
+ Main Event Loop
29
+
30
+ Runs in a while loop, Triggers Events.send_user_hourly_memories at every hour.
31
+ """
32
+ while True:
33
+ await asyncio.sleep(cls.get_time_until_next_hour())
34
+ async with AsyncClient() as client:
35
+ endpoint = f"http://0.0.0.0:{cls.PORT}/trigger_send_user_hourly_memories/{Events.TOKEN}"
36
+ response = await client.post(url=endpoint)
37
+ endpoint = f"http://0.0.0.0:{cls.PORT}/trigger_archive_db/{Events.TOKEN}"
38
+ response = await client.post(url=endpoint)
39
+
40
+ @classmethod
41
+ def get_time_until_next_hour(cls) -> float:
42
+ # Ref: https://stackoverflow.com/a/52808375/15282482
43
+ delta = datetime.timedelta(hours=1)
44
+ now = datetime.datetime.now()
45
+ next_hour = (now + delta).replace(microsecond=0, second=0, minute=0)
46
+ return (next_hour - now).total_seconds()
47
+
48
+ @classmethod
49
+ def send_user_hourly_memories(
50
+ cls,
51
+ user: User,
52
+ hour: int,
53
+ ) -> None:
54
+ """
55
+ Sends memories to user if the current_hour is in his schedule.
56
+ """
57
+ hour = (hour + user.gmt) % 24
58
+ if user.scheduled_hours == "":
59
+ return
60
+ scheduled_hours = user.scheduled_hours.split(",")
61
+ number_of_messages_at_this_hour = 0
62
+
63
+ for str_hour in scheduled_hours:
64
+ if (
65
+ int(str_hour) > hour
66
+ ): # Scheduled_hours are sorted, next items will be > hour as well.
67
+ break
68
+ if int(str_hour) == hour:
69
+ number_of_messages_at_this_hour += 1
70
+ number_of_messages_at_this_hour = min(
71
+ len(user.reminders), number_of_messages_at_this_hour
72
+ )
73
+ selected_reminders = random.sample(
74
+ user.reminders, number_of_messages_at_this_hour
75
+ )
76
+ for reminder in selected_reminders: # Send the memory in background
77
+ asyncio.create_task(
78
+ cls.send_a_message_to_user(user.telegram_chat_id, reminder.reminder)
79
+ )
80
+ now = datetime.datetime.now()
81
+ print(
82
+ f"Created task to, {user.name}, {reminder.reminder}, hour: {hour}, gmt: {user.gmt}, now: {now}"
83
+ )
84
+
85
+ @classmethod
86
+ async def send_message_list_at_background(
87
+ cls, telegram_chat_id: int, message_list: List[str]
88
+ ) -> bool:
89
+ for message in message_list:
90
+ print(f"sending the message: {message}, to chat: {telegram_chat_id} ")
91
+ await Events.send_a_message_to_user(
92
+ telegram_id=telegram_chat_id, message=message
93
+ )
94
+ return True
95
+
96
+ @classmethod
97
+ async def send_a_message_to_user(
98
+ cls,
99
+ telegram_id: int,
100
+ message: str,
101
+ retry_count: int = 3,
102
+ sleep_time: float = 0.1,
103
+ ) -> bool:
104
+ message = ResponseToMessage(
105
+ **{
106
+ "text": message,
107
+ "chat_id": telegram_id,
108
+ }
109
+ )
110
+ await asyncio.sleep(sleep_time)
111
+ for retry in range(retry_count):
112
+ print(f"Sending the message in send_a_message_to_user, count {retry}")
113
+ # Avoid too many requests error from Telegram
114
+ response = await cls.request(cls.TELEGRAM_SEND_MESSAGE_URL, message.dict())
115
+ if response.status_code == 200:
116
+ return True
117
+ elif response.status_code == 429:
118
+ retry_after = int(response.json()["parameters"]["retry_after"])
119
+ print(f"Retry After: {retry_after}, message: {message}")
120
+ await asyncio.sleep(retry_after)
121
+ else:
122
+ print(
123
+ f"Unhandled response code: {response.status_code}, response: {response.json()}"
124
+ )
125
+ return False
126
+
127
+ @classmethod
128
+ async def broadcast_message(cls, message: str) -> None:
129
+ users = db_read_users(limit=100000, only_active_users=False)
130
+ await asyncio.gather(
131
+ *(
132
+ Events.send_a_message_to_user(
133
+ user.telegram_chat_id,
134
+ message,
135
+ )
136
+ for user in users
137
+ )
138
+ )
139
+
140
+ @classmethod
141
+ async def request(cls, url: str, payload: dict, debug: bool = True) -> Response:
142
+ async with AsyncClient(timeout=30 * 60) as client:
143
+ request = await client.post(url, json=payload)
144
+ if debug:
145
+ print(request.json())
146
+ return request
147
+
148
+ @classmethod
149
+ async def set_telegram_webhook_url(cls) -> bool:
150
+ print(f"webhook_url:{cls.HOST_URL}")
151
+ if cls.SELF_SIGNED:
152
+ payload = {
153
+ "url": f"{cls.HOST_URL}/webhook/{cls.TOKEN}",
154
+ "certificate": open(os.environ.get("PEM_FILE"), "rb"),
155
+ }
156
+ else:
157
+ payload = {"url": f"{cls.HOST_URL}/webhook/{cls.TOKEN}"}
158
+ req = await cls.request(cls.TELEGRAM_SET_WEBHOOK_URL, payload)
159
+ return req.status_code == 200
160
+
161
+ @classmethod
162
+ def archive_db(cls) -> bool:
163
+ command = f'curl -v -F "chat_id={Constants.BROADCAST_CHAT_ID}" -F document=@database.db {cls.TELEGRAM_SEND_DOCUMENT_URL}'
164
+ os.system(command)
165
+
166
+ @classmethod
167
+ async def get_public_ip(cls):
168
+ # Reference: https://pytutorial.com/python-get-public-ip
169
+
170
+ endpoint = "https://ipinfo.io/json"
171
+ async with AsyncClient() as client:
172
+ response = await client.get(endpoint)
173
+
174
+ if response.status_code != 200:
175
+ sys.exit("Could not get the public ip, exiting!")
176
+ data = response.json()
177
+
178
+ return data["ip"]
src/listener.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import datetime
3
+
4
+ from fastapi import FastAPI, Depends
5
+ from fastapi.concurrency import run_in_threadpool
6
+ from .db import *
7
+ from .message_validations import MessageBodyModel, ResponseToMessage
8
+ from .constants import Constants
9
+ from .events import Events
10
+ from .response_logic import ResponseLogic
11
+
12
+ app = FastAPI(openapi_url=None)
13
+
14
+
15
+ @app.on_event("startup")
16
+ def on_startup():
17
+ create_db_and_tables()
18
+ asyncio.create_task(Events.main_event())
19
+
20
+
21
+ @app.get("/health")
22
+ async def health():
23
+ return {"healthy": True}
24
+
25
+
26
+ @app.post(f"/webhook/{Events.TOKEN}")
27
+ async def listen_telegram_messages(message: MessageBodyModel):
28
+ print(message.dict())
29
+
30
+ if message.message:
31
+ name = message.message.from_field.first_name
32
+ chat_id = message.message.chat.id
33
+ text = message.message.text
34
+ language_code = message.message.from_field.language_code
35
+ if not text: # Edit of message etc.
36
+ return
37
+ else:
38
+ response_message = await ResponseLogic.create_response(
39
+ text, name, chat_id, language_code
40
+ )
41
+ return ResponseToMessage(
42
+ **{
43
+ "text": response_message,
44
+ "chat_id": chat_id,
45
+ }
46
+ )
47
+
48
+ if not message.message: # Bot is added to a group
49
+ if not message.my_chat_member:
50
+ return
51
+
52
+ chat_id = message.my_chat_member.chat.id
53
+ name = message.my_chat_member.from_field.first_name
54
+ language_code = message.my_chat_member.from_field.language_code
55
+
56
+ new_member = message.my_chat_member.new_chat_member
57
+ if (
58
+ new_member
59
+ and new_member.user.id == Constants.BOT_ID
60
+ and new_member.status == "member"
61
+ ):
62
+ await Events.send_a_message_to_user(chat_id, Constants.hello)
63
+ await Events.send_a_message_to_user(
64
+ chat_id, Constants.Start.start_message(name, language_code)
65
+ )
66
+ await Events.send_a_message_to_user(
67
+ chat_id, Constants.Start.group_warning(name, language_code)
68
+ )
69
+ return
70
+
71
+ return
72
+
73
+ @app.post(f"/trigger_archive_db/{Events.TOKEN}")
74
+ def trigger_archive_db():
75
+ Events.archive_db()
76
+
77
+
78
+ @app.post(f"/trigger_send_user_hourly_memories/{Events.TOKEN}")
79
+ async def trigger_send_user_hourly_memories(*, session: Session = Depends(get_session)):
80
+ users = db_read_users(limit=100000, session=session)
81
+ now = datetime.datetime.now(datetime.timezone.utc)
82
+ print(f"Sending is triggered at hour {now.hour}")
83
+ for user in users:
84
+ Events.send_user_hourly_memories(user, now.hour)
src/message_validations.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, List
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class Chat(BaseModel):
7
+ last_name: Optional[str]
8
+ id: Optional[int]
9
+ type: Optional[str]
10
+ first_name: Optional[str]
11
+ username: Optional[str]
12
+
13
+
14
+ class From(BaseModel):
15
+ last_name: Optional[str]
16
+ id: Optional[int]
17
+ first_name: Optional[str]
18
+ user_name: Optional[str]
19
+ language_code: Optional[str]
20
+ is_bot: Optional[str]
21
+
22
+
23
+ class ReplyMessage(BaseModel):
24
+ date: Optional[int]
25
+ chat: Optional[Chat]
26
+ message_id: Optional[int]
27
+ text: Optional[str]
28
+
29
+
30
+ class Photo(BaseModel):
31
+ file_id: Optional[str]
32
+
33
+
34
+ class Message(BaseModel):
35
+ date: Optional[int]
36
+ chat: Optional[Chat]
37
+ message_id: Optional[str]
38
+ from_field: From = Field(alias="from")
39
+ text: Optional[str]
40
+ photo: Optional[List[Photo]]
41
+
42
+
43
+ class ChatGroup(BaseModel):
44
+ id: Optional[int]
45
+ title: Optional[str]
46
+ type: Optional[str]
47
+
48
+
49
+ class MockVal(BaseModel):
50
+ rand_int: Optional[int]
51
+
52
+
53
+ class OtherChatMember(BaseModel):
54
+ user: Optional[From]
55
+ status: Optional[str]
56
+
57
+
58
+ class MyChatMember(BaseModel):
59
+ rand_int: Optional[int]
60
+ chat: Optional[ChatGroup]
61
+ from_field: Optional[From] = Field(alias="from")
62
+ date: Optional[int]
63
+ old_chat_member: Optional[OtherChatMember]
64
+ new_chat_member: Optional[OtherChatMember]
65
+
66
+
67
+ class MessageBodyModel(BaseModel):
68
+ update_id: Optional[int]
69
+ message: Optional[Message]
70
+ my_chat_member: Optional[MyChatMember]
71
+ reply_to_message: Optional[ReplyMessage]
72
+
73
+
74
+ class ResponseToMessage(BaseModel):
75
+ method: Optional[str] = "sendMessage"
76
+ chat_id: Optional[int] = 861126057
77
+ text: Optional[str] = ""
78
+ parse_mode: Optional[str] = "Markdown"
src/response_logic.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import asyncio
3
+
4
+ from .db import *
5
+ from .events import Events
6
+ from .constants import Constants
7
+
8
+
9
+ class ResponseLogic:
10
+ @staticmethod
11
+ async def create_response(
12
+ text: str, name: str, chat_id: int, language_code: str
13
+ ) -> str:
14
+
15
+ # Edge case check for "add\nSentence"
16
+ line_split_text = text.split("\n")
17
+ line_split_first_word = line_split_text[0]
18
+ split_text = text.split(" ")
19
+ first_word = split_text[0]
20
+ user = UserCreate(
21
+ name=name,
22
+ telegram_chat_id=chat_id,
23
+ )
24
+ if ResponseLogic.check_command_type(
25
+ first_word, "add"
26
+ ) or ResponseLogic.check_command_type(line_split_first_word, "add"):
27
+ if ResponseLogic.check_command_type(line_split_first_word, "add"):
28
+ memory = "\n".join(split_text[1:])
29
+ else:
30
+ memory = " ".join(split_text[1:])
31
+ if str.isspace(memory) or memory == "":
32
+ return Constants.Add.no_sentence(name, language_code)
33
+ else:
34
+ reminder = add_memory(user, memory)
35
+ if reminder is None:
36
+ return Constants.Common.inactive_user(name, language_code)
37
+ elif reminder is False:
38
+ return Constants.Add.already_added(name, language_code)
39
+ else:
40
+ memory = reminder.reminder
41
+ return Constants.Add.success(name, language_code, memory)
42
+ elif ResponseLogic.check_command_type(first_word, "start"):
43
+ user = join_user(user)
44
+ await Events.send_a_message_to_user(chat_id, Constants.hello)
45
+ return Constants.Start.start_message(name, language_code)
46
+ elif ResponseLogic.check_command_type(first_word, "help"):
47
+ message = Constants.Help.help_message(name, language_code)
48
+ return message
49
+
50
+ elif ResponseLogic.check_command_type(first_word, "join"):
51
+ user = join_user(user)
52
+
53
+ if user is not None:
54
+ return Constants.Join.successful_join(name, language_code)
55
+ else:
56
+ return Constants.Join.already_joined(name, language_code)
57
+
58
+ elif ResponseLogic.check_command_type(first_word, "leave"):
59
+ user = leave_user(user)
60
+ if user is not None and user.telegram_chat_id == chat_id:
61
+ return Constants.Leave.successful_leave(name, language_code)
62
+ else:
63
+ return Constants.Leave.already_left(name, language_code)
64
+
65
+ elif ResponseLogic.check_command_type(first_word, "send"):
66
+ if len(split_text) == 1: # send
67
+ memory = select_random_memory(user)
68
+ if memory is None:
69
+ return Constants.Common.inactive_user(name, language_code)
70
+ elif memory is False:
71
+ return Constants.Common.no_memory_found(name, language_code)
72
+ else:
73
+ return memory.reminder
74
+ else: # send number
75
+ try:
76
+ number_of_sending = int(split_text[1])
77
+ if not (1 <= number_of_sending < 50):
78
+ return Constants.Send.send_count_out_of_bound(
79
+ name, language_code
80
+ )
81
+ except:
82
+ return Constants.Send.send_count_out_of_bound(name, language_code)
83
+
84
+ all_memories = list_memories(user)
85
+ if len(all_memories) == 0:
86
+ return Constants.Common.no_memory_found(name, language_code)
87
+
88
+ send_count = min(len(all_memories), number_of_sending)
89
+ selected_memories = random.sample(all_memories, send_count)
90
+ for memory in selected_memories: # Send the memories in background
91
+ asyncio.create_task(
92
+ Events.send_a_message_to_user(
93
+ user.telegram_chat_id, memory.reminder
94
+ )
95
+ )
96
+ return ""
97
+
98
+ elif ResponseLogic.check_command_type(first_word, "list"):
99
+ memories = list_memories(user)
100
+ memory_count = len(memories)
101
+ if memories is None:
102
+ return Constants.Common.inactive_user(name, language_code)
103
+ elif memory_count == 0:
104
+ return Constants.Common.no_memory_found(name, language_code)
105
+ else:
106
+ background_message_list = []
107
+ for message_id, reminder in enumerate(memories):
108
+ background_message_list.append(
109
+ f"\n{message_id}: {reminder.reminder}"
110
+ )
111
+ asyncio.create_task(
112
+ Events.send_message_list_at_background(
113
+ telegram_chat_id=chat_id, message_list=background_message_list
114
+ )
115
+ )
116
+
117
+ response_message = Constants.List.list_messages(
118
+ name, memory_count, language_code
119
+ )
120
+ return response_message
121
+
122
+ elif ResponseLogic.check_command_type(first_word, "delete"):
123
+ if len(split_text) < 2:
124
+ return Constants.Delete.no_id(name, language_code)
125
+ else:
126
+ try:
127
+ memory_id = int(split_text[1])
128
+ if memory_id < 0:
129
+ return Constants.Delete.no_id(name, language_code)
130
+ else:
131
+ response = delete_memory(user, memory_id)
132
+ if response is None:
133
+ return Constants.Common.inactive_user(name, language_code)
134
+ elif response is False:
135
+ return Constants.Delete.no_id(name, language_code)
136
+ else:
137
+ memory = response
138
+
139
+ return Constants.Delete.success(name, language_code, memory)
140
+
141
+ except Exception as ex:
142
+ return Constants.Delete.no_id(name, language_code)
143
+
144
+ elif ResponseLogic.check_command_type(first_word, "schedule"):
145
+ if len(split_text) == 1:
146
+ schedule = get_schedule(user)
147
+ if schedule is None:
148
+ return Constants.Common.inactive_user(name, language_code)
149
+ elif schedule == "":
150
+ return Constants.Schedule.empty_schedule(name, language_code)
151
+ else:
152
+ return Constants.Schedule.success(name, language_code, schedule)
153
+
154
+ elif len(split_text) > 1:
155
+ if split_text[1] == "reset":
156
+ new_schedule = reset_schedule(user)
157
+ if new_schedule is None:
158
+ return Constants.Common.inactive_user(name, language_code)
159
+ else:
160
+ return Constants.Schedule.success(
161
+ name, language_code, new_schedule
162
+ )
163
+
164
+ elif split_text[1] == "add":
165
+ str_numbers = []
166
+ preceding_text = " ".join(split_text[2:])
167
+ if str.isspace(preceding_text) or preceding_text == "":
168
+ return Constants.Schedule.no_number_found(name, language_code)
169
+
170
+ number_check = True
171
+ try:
172
+ for str_number in split_text[2:]:
173
+ number = int(str_number)
174
+ str_numbers.append(number)
175
+ if not (0 <= number <= 23):
176
+ number_check = False
177
+ break
178
+ except:
179
+ return Constants.Schedule.add_incorrect_number_input(
180
+ name, language_code
181
+ )
182
+
183
+ if not number_check:
184
+ return Constants.Schedule.add_incorrect_number_input(
185
+ name, language_code
186
+ )
187
+
188
+ else:
189
+ new_schedule = add_hours_to_the_schedule(user, str_numbers)
190
+ if new_schedule is None:
191
+ return Constants.Common.inactive_user(name, language_code)
192
+ else:
193
+ return Constants.Schedule.success(
194
+ name, language_code, new_schedule
195
+ )
196
+
197
+ elif split_text[1] == "remove":
198
+ preceding_text = " ".join(split_text[2:])
199
+ number_check = True
200
+ if str.isspace(preceding_text) or preceding_text == "":
201
+ return Constants.Schedule.remove_incorrect_number_input(
202
+ name, language_code
203
+ )
204
+ else:
205
+ try:
206
+ str_number = split_text[2]
207
+ number = int(str_number)
208
+ if not (0 <= number <= 23):
209
+ number_check = False
210
+ except:
211
+ return Constants.Schedule.remove_incorrect_number_input(
212
+ name, language_code
213
+ )
214
+
215
+ if not number_check:
216
+ return Constants.Schedule.remove_incorrect_number_input(
217
+ name, language_code
218
+ )
219
+
220
+ else:
221
+ new_schedule = remove_hour_from_schedule(
222
+ user, int(str_number)
223
+ )
224
+ if new_schedule is None:
225
+ return Constants.Common.inactive_user(
226
+ name, language_code
227
+ )
228
+ else:
229
+ return Constants.Schedule.success(
230
+ name, language_code, new_schedule
231
+ )
232
+
233
+ else:
234
+ return Constants.Schedule.unknown_command(name, language_code)
235
+
236
+ elif ResponseLogic.check_command_type(first_word, "gmt"):
237
+ try:
238
+ gmt = int(split_text[1])
239
+ except Exception as ex:
240
+ return Constants.Gmt.incorrect_timezone(name, language_code)
241
+ if not (-12 <= gmt <= 12):
242
+ return Constants.Gmt.incorrect_timezone(name, language_code)
243
+ else:
244
+ user = update_gmt(user, gmt)
245
+ if user is None:
246
+ return Constants.Common.inactive_user(name, language_code)
247
+ else:
248
+ return Constants.Gmt.success(name, language_code, user.gmt)
249
+
250
+ elif ResponseLogic.check_command_type(first_word, "broadcast"):
251
+ if not chat_id == Constants.BROADCAST_CHAT_ID:
252
+ return Constants.Broadcast.no_right(name, language_code)
253
+ else:
254
+ normalized_text = " ".join(split_text[1:])
255
+ if str.isspace(normalized_text) or normalized_text == "":
256
+ return Constants.Broadcast.no_sentence_found(name, language_code)
257
+ else:
258
+ await Events.broadcast_message(normalized_text)
259
+ return Constants.Broadcast.success(name, language_code)
260
+
261
+ elif ResponseLogic.check_command_type(first_word, "status"):
262
+ gmt, active = get_user_status(chat_id)
263
+ if gmt is None:
264
+ return Constants.Common.inactive_user(name, language_code)
265
+ else:
266
+ schedule = get_schedule(user)
267
+ memory_count = len(list_memories(user))
268
+ return Constants.Status.get_status(
269
+ name, language_code, gmt, active, schedule, memory_count
270
+ )
271
+
272
+ elif ResponseLogic.check_command_type(first_word, "feedback"):
273
+
274
+ message = " ".join(split_text[1:])
275
+ message_with_user_information = (
276
+ message + f"\nFrom the user: *{name}* \nchat id: *{chat_id}*"
277
+ )
278
+
279
+ if str.isspace(message) or message == "":
280
+ return Constants.Feedback.no_message(name, language_code)
281
+ else:
282
+ success = await Events.send_a_message_to_user(
283
+ Constants.FEEDBACK_FORWARD_CHAT_ID, message_with_user_information
284
+ )
285
+ if success:
286
+ return Constants.Feedback.success(name, language_code, message)
287
+ else:
288
+ return Constants.Feedback.fail(name, language_code)
289
+
290
+ elif ResponseLogic.check_command_type(first_word, "deletelast"):
291
+ response = delete_last_memory(user)
292
+ if response is None:
293
+ return Constants.Common.inactive_user(name, language_code)
294
+ elif response is False:
295
+ return Constants.Common.no_memory_found(name, language_code)
296
+ else:
297
+ return Constants.Delete.success(name, language_code, response)
298
+
299
+ elif ResponseLogic.check_command_type(first_word, "support"):
300
+ return Constants.Support.support(name, language_code)
301
+
302
+ elif ResponseLogic.check_command_type(first_word, "tutorial1"):
303
+ return Constants.Tutorial.tutorial_1(name, language_code)
304
+
305
+ elif ResponseLogic.check_command_type(first_word, "tutorial2"):
306
+ return Constants.Tutorial.tutorial_2(name, language_code)
307
+
308
+ elif ResponseLogic.check_command_type(first_word, "tutorial3"):
309
+ return Constants.Tutorial.tutorial_3(name, language_code)
310
+
311
+ else:
312
+ return Constants.Common.unknown_command(name, language_code)
313
+
314
+ @staticmethod
315
+ def check_command_type(input_command: str, correct_command: str) -> bool:
316
+ """
317
+ Checks the first word of the command, which starts the decision tree.
318
+
319
+ Args:
320
+ input_command:
321
+ correct_command:
322
+
323
+ Returns: bool
324
+
325
+ """
326
+ input_command = input_command.lower()
327
+ if (
328
+ input_command == correct_command
329
+ or input_command == f"/{correct_command}"
330
+ or input_command == f"/{correct_command}@memory_vault_bot"
331
+ ):
332
+ return True
333
+ else:
334
+ return False