Skip to content

Commit

Permalink
Dev->Master (#29)
Browse files Browse the repository at this point in the history
* tweaks

* tweaks

* tweaks

* tweaks

* tweaks

* tweaks

* Implement Package Feature (#24)

- package add feature is added
- TR Stocks & Currency package is added

* Package feature (#25)

* Implement Package Feature

- package add feature is added
- TR Stocks & Currency package is added

* Implement Package Feature

- package add feature is added
- TR Stocks & Currency package is added

* tweaks

* tweaks

* tweaks

* tweaks

* tweaks

* tweaks

* tweaks

* tweaks

* fix group auto join

* tweaks

* tweaks

* cleanup

* tweaks

* tweaks

* tweaks

* Photo file forward support (#28)

* Photo-File-Forward-Support

- Add support for photo, file and forward types of notes.

* Photo-File-Forward-Support

- Add support for voice, video and video_note types of notes.
- Tweaks on response to add files and documents.

* Photo-File-Forward-Support

- Change communication into message_id for all types except "add Memory"
- Tweaks

* tweaks

* tweaks

* hotfix

* - refactor
- add exception handling middleware

* - refactor
- add exception handling middleware

* Do not notify when listing the vault.

* Do not notify when listing the vault.
  • Loading branch information
omerXfaruq authored Jan 25, 2023
1 parent 4a3fb7f commit 1ad7fe6
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 92 deletions.
25 changes: 15 additions & 10 deletions src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class Constants:
FEEDBACK_FORWARD_CHAT_ID = -683998033
BOT_ID = 5065052385

# BOT_ID = 5015215848 # MemRem

class Common:
@staticmethod
def inactive_user(name: str, language_code: str = "en") -> str:
Expand Down Expand Up @@ -269,20 +271,25 @@ def already_added(name: str, language_code: str = "en") -> str:

@staticmethod
def success(name: str, language_code: str = "en", note: str = "") -> str:
words = note.split(" ")
if words[0] == "message_id:":
note_message = ""
else:
note_message = f"*Note*: \n{note}"
if language_code == "tr":
return (
f"{name}, not kasana eklendi. Merak etme, onu güvende tutacağım {Constants.smile}"
f"\n*Not*: \n{note}"
f""
f"\n\n Eğer son eklediğin notu silmek istiyorsan, bu komutu kullan */deletelast*"
f"\n{note_message}"
f"\n"
f"\n Eğer son eklediğin notu silmek istiyorsan, bu komutu kullan */deletelastadd*"
)

else:
return (
f"{name}, note is added to your memory vault. No worries, I will keep it safe {Constants.smile}"
f"\n*Note*: \n{note}"
f""
f"\n\nIf you want to delete the last added note, you can use */deletelast*"
f"{name}, the note is added to your memory vault. No worries, I will keep it safe {Constants.smile}"
f"\n{note_message}"
f"\n"
f"\nIf you want to delete the last added note, you can use */deletelastadd*"
)

class Delete:
Expand All @@ -295,18 +302,16 @@ def no_id(name: str, language_code: str = "en") -> str:
return f"{name}, need to give me id of the note, ie: *delete 2*, you can get it by using command, *list* or /list"

@staticmethod
def success(name: str, language_code: str = "en", note: str = "") -> str:
def success(name: str, language_code: str = "en") -> str:
if language_code == "tr":
return (
f"{name}, not kasadan silindi. Unutulan hatıraya elveda {Constants.sad}"
f"\n*Silinen Not*:"
f"\n{note}"
)
else:
return (
f"{name}, your note is deleted from your memory vault. Good bye to the forgotten memory {Constants.sad}"
f"\n*Deleted Note*:"
f"\n{note}"
)

class Schedule:
Expand Down
105 changes: 67 additions & 38 deletions src/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Events:
TOKEN = os.environ.get("TELEGRAM_TOKEN")
TELEGRAM_SEND_MESSAGE_URL = f"https://api.telegram.org/bot{TOKEN}/sendMessage"
TELEGRAM_SET_WEBHOOK_URL = f"https://api.telegram.org/bot{TOKEN}/setWebhook"
TELEGRAM_SEND_DOCUMENT_URL = f"https://api.telegram.org/bot{TOKEN}/sendDocument"
TELEGRAM_COPY_MESSAGE_URL = f"https://api.telegram.org/bot{TOKEN}/copyMessage"

PORT = 8000
HOST_URL = None
Expand Down Expand Up @@ -80,69 +80,106 @@ def send_user_hourly_memories(

@classmethod
async def send_message_list_at_background(
cls, telegram_chat_id: int, message_list: List[str]
cls,
telegram_chat_id: int,
message_list: List[str],
notify: bool = True,
) -> bool:
for message in message_list:
await Events.send_a_message_to_user(
telegram_id=telegram_chat_id, message=message
chat_id=telegram_chat_id,
message=message,
notify=notify,
)
return True

@classmethod
async def get_package_message(
async def create_response_message(
cls,
message: str,
) -> str:
chat_id: int,
convert: bool,
notify: bool = True,
) -> (str, ResponseToMessage):
"""
Runs the related package if the reminder is a package type.
Creates the response message
Runs the related package and sends the resulting message if the reminder is a package type.
Sends the photo if the message is photo type.
Sends the document if the message is document type.
Sends the text if the message is text type.
Args:
message:
chat_id:
convert: Converts the encoded message to related type of message, if True
notify: If false, send the message without notifying.
Returns:
converted_message
converted_message:
"""
words = message.split(" ")
if words[0] == "package:":
package_id = 0
try:
package_id = int(words[1])
except:
return message
return await (Packages.functions[package_id]())
message_id = None
from_chat_id = None
text = None
print(f"%% {datetime.datetime.now()}: Message is: {message}")

if convert:
words = message.split(" ")
if len(words) == 2:
if words[0] == "package:":
fn_id = int(words[1])
text = await (Packages.functions[fn_id]())

elif words[0] == "message_id:":
message_id = int(words[1])
from_chat_id = chat_id

else:
text = message

return message
else:
text = message

return cls.TELEGRAM_SEND_MESSAGE_URL, ResponseToMessage(
**{
"text": text,
"message_id": message_id,
"chat_id": chat_id,
"from_chat_id": from_chat_id,
"disable_notification": notify,
}
)

@classmethod
async def send_a_message_to_user(
cls,
telegram_id: int,
chat_id: int,
message: str,
retry_count: int = 3,
sleep_time: float = 0.1,
sleep_time: float = 0.01,
convert: bool = True,
notify: bool = True,
) -> bool:
message = await cls.get_package_message(message)
message = ResponseToMessage(
**{
"text": message,
"chat_id": telegram_id,
}
)
url, message = await cls.create_response_message(message, chat_id, convert)
print(f"%% {datetime.datetime.now()}: Message is: {message}")

await asyncio.sleep(sleep_time)
for retry in range(retry_count):
# Avoid too many requests error from Telegram
response = await cls.request(cls.TELEGRAM_SEND_MESSAGE_URL, message.dict())
response = await cls.request(url, message.dict())
if response.status_code == 200:
print(f"%% {datetime.datetime.now()}: Sent message {retry}")
# print(f"%% {datetime.datetime.now()}: Sent message ")
return True
elif response.status_code == 429:
retry_after = int(response.json()["parameters"]["retry_after"])
print(f"%% {datetime.datetime.now()} Retry After: {retry_after}, message: {message}")
print(
f"%% {datetime.datetime.now()} Retry After: {retry_after}, message: {message}"
)
await asyncio.sleep(retry_after)
else:
print(
f"%% {datetime.datetime.now()} Unhandled response code: {response.status_code}, response: {response.json()}"
f"%% {datetime.datetime.now()} Unhandled response code: {response.status_code}, response: {response.json()}, chat: {chat_id}, message: {message}, url: {url}"
)
return False

Expand All @@ -151,10 +188,7 @@ async def broadcast_message(cls, message: str) -> None:
users = db_read_users(limit=100000, only_active_users=False)
await asyncio.gather(
*(
Events.send_a_message_to_user(
user.telegram_chat_id,
message,
)
Events.send_a_message_to_user(user.telegram_chat_id, message)
for user in users
)
)
Expand All @@ -179,11 +213,6 @@ async def set_telegram_webhook_url(cls) -> bool:
req = await cls.request(cls.TELEGRAM_SET_WEBHOOK_URL, payload)
return req.status_code == 200

@classmethod
def archive_db(cls) -> bool:
command = f'curl -v -F "chat_id={Constants.BROADCAST_CHAT_ID}" -F [email protected] {cls.TELEGRAM_SEND_DOCUMENT_URL}'
os.system(command)

@classmethod
async def get_public_ip(cls):
# Reference: https://pytutorial.com/python-get-public-ip
Expand Down
110 changes: 81 additions & 29 deletions src/listener.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import asyncio
import datetime

from fastapi import FastAPI, Depends, Request
from fastapi.concurrency import run_in_threadpool
import logging
import time
from fastapi import FastAPI, Request
from fastapi.responses import PlainTextResponse
from .db import *
from .message_validations import MessageBodyModel, ResponseToMessage
from .constants import Constants
from .events import Events
from .response_logic import ResponseLogic

app = FastAPI(openapi_url=None)
logging.basicConfig(filename="exceptions.log", encoding="utf-8", level=logging.ERROR)


@app.on_event("startup")
Expand All @@ -18,6 +19,20 @@ def on_startup():
asyncio.create_task(Events.main_event())


@app.middleware("http")
async def exception_handler(request: Request, call_next):
start_time = time.time()
try:
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(round(process_time, 4))
except Exception as ex:
logging.exception(f"$${datetime.datetime.now()}: Exception in Middleware:")
logging.exception(ex)
return PlainTextResponse(str("An Error Occurred"), status_code=200)
return response


@app.get("/health")
async def health():
return {"healthy": True}
Expand All @@ -27,26 +42,66 @@ async def health():
async def listen_telegram_messages(r: Request, message: MessageBodyModel):
print(f"%% {datetime.datetime.now()} Incoming Message: {message.dict()}")
print(f"%% {datetime.datetime.now()} Incoming Request: {await r.json()}")

response_message = None
chat_id = 0

if message.message:
name = message.message.from_field.first_name
chat_id = message.message.chat.id
text = message.message.text
language_code = message.message.from_field.language_code

if not text: # Edit of message etc.
return
else:
if message.message.photo:
response_message = await ResponseLogic.create_response(
f"add message_id: {message.message.message_id}",
name,
chat_id,
language_code,
)
elif message.message.document:
response_message = await ResponseLogic.create_response(
text, name, chat_id, language_code
f"add message_id: {message.message.message_id}",
name,
chat_id,
language_code,
)
return ResponseToMessage(
**{
"text": response_message,
"chat_id": chat_id,
}
elif message.message.video:
response_message = await ResponseLogic.create_response(
f"add message_id: {message.message.message_id}",
name,
chat_id,
language_code,
)
elif message.message.video_note:
response_message = await ResponseLogic.create_response(
f"add message_id: {message.message.message_id}",
name,
chat_id,
language_code,
)
elif message.message.voice:
response_message = await ResponseLogic.create_response(
f"add message_id: {message.message.message_id}",
name,
chat_id,
language_code,
)
elif message.message.forward_date:
if message.message.text:
response_message = await ResponseLogic.create_response(
f"add message_id: {message.message.message_id}",
name,
chat_id,
language_code,
)
elif message.message.text:
response_message = await ResponseLogic.create_response(
message.message.text, name, chat_id, language_code
)
else:
return

if not message.message: # Bot is added to a group
elif not message.message: # Bot is added to a group
if not message.my_chat_member:
return

Expand All @@ -60,21 +115,18 @@ async def listen_telegram_messages(r: Request, message: MessageBodyModel):
and new_member.user.id == Constants.BOT_ID
and new_member.status == "member"
):
start_message = await ResponseLogic.create_response("start", name, chat_id, language_code)
await Events.send_a_message_to_user(
chat_id, start_message
start_message = await ResponseLogic.create_response(
"start", name, chat_id, language_code
)
await Events.send_a_message_to_user(
chat_id, Constants.Start.group_warning(name, language_code)
)
return

return


@app.post(f"/trigger_archive_db/{Events.TOKEN}")
def trigger_archive_db():
Events.archive_db()
await Events.send_a_message_to_user(chat_id, start_message)
response_message = Constants.Start.group_warning(name, language_code)

return ResponseToMessage(
**{
"text": response_message,
"chat_id": chat_id,
}
)


@app.post(f"/trigger_send_user_hourly_memories/{Events.TOKEN}")
Expand Down
Loading

0 comments on commit 1ad7fe6

Please sign in to comment.