IGNOU Telegram Bot v2.0
This commit is contained in:
commit
9668553828
7 changed files with 500 additions and 0 deletions
65
bot/__main__.py
Normal file
65
bot/__main__.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
import os
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.enums import ChatType, ParseMode
|
||||
|
||||
from .ignou import IgnouResult, ResultNotFoundError
|
||||
|
||||
try:
|
||||
BOT_TOKEN = os.environ["BOT_TOKEN"]
|
||||
except KeyError:
|
||||
print("BOT_TOKEN not found in environment variables.")
|
||||
exit(1)
|
||||
|
||||
|
||||
app = Client(
|
||||
":ignou:",
|
||||
api_id=os.environ.get("API_ID", "21724"),
|
||||
api_hash=os.environ.get("API_HASH", "3e0cb5efcd52300aec5994fdfc5bdc16"),
|
||||
bot_token=BOT_TOKEN,
|
||||
in_memory=True
|
||||
)
|
||||
|
||||
ignou = IgnouResult()
|
||||
|
||||
|
||||
@app.on_message(filters.command("start"))
|
||||
async def start(_, message):
|
||||
await message.reply_text(
|
||||
f"Hi {message.from_user.mention}!\nSend me your program code with enrollment number to get your grade card result.",
|
||||
)
|
||||
|
||||
|
||||
@app.on_message(filters.regex(r"([a-zA-z]+)\s*?(\d+$)"))
|
||||
async def get_grade_result(_, message):
|
||||
|
||||
if message.chat.type == ChatType.GROUP or message.chat.type == ChatType.SUPERGROUP:
|
||||
await message.delete()
|
||||
|
||||
program_id, enrollment_no = message.matches[0].groups()
|
||||
|
||||
try:
|
||||
footer = f"<strong>Result checked by {message.from_user.mention(message.from_user.first_name)}</strong>"
|
||||
except AttributeError:
|
||||
footer = ""
|
||||
|
||||
try:
|
||||
result = await ignou.gradeResultData(program_id, enrollment_no)
|
||||
except ResultNotFoundError:
|
||||
await message.reply_text("Grade Card Result not found.")
|
||||
return
|
||||
|
||||
if message.chat.type == ChatType.GROUP or message.chat.type == ChatType.SUPERGROUP:
|
||||
await message.reply_text(
|
||||
f"<pre>{result['table']}</pre>\n{footer}",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
elif message.chat.type == ChatType.PRIVATE:
|
||||
await message.reply_text(
|
||||
f"<pre>{result['header']}{result['table']}</pre>",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
224
bot/ignou.py
Normal file
224
bot/ignou.py
Normal file
|
@ -0,0 +1,224 @@
|
|||
from collections import OrderedDict
|
||||
from collections.abc import Mapping
|
||||
|
||||
import httpx
|
||||
from bs4 import BeautifulSoup
|
||||
from prettytable import PrettyTable
|
||||
|
||||
|
||||
class DotDict(OrderedDict):
|
||||
"""
|
||||
Quick and dirty implementation of a dot-able dict, which allows access and
|
||||
assignment via object properties rather than dict indexing.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# we could just call super(DotDict, self).__init__(*args, **kwargs)
|
||||
# but that won't get us nested dotdict objects
|
||||
od = OrderedDict(*args, **kwargs)
|
||||
for key, val in od.items():
|
||||
if isinstance(val, Mapping):
|
||||
value = DotDict(val)
|
||||
else:
|
||||
value = val
|
||||
self[key] = value
|
||||
|
||||
def __getattr__(self, k):
|
||||
return self.get(k, "-")
|
||||
|
||||
__setattr__ = OrderedDict.__setitem__
|
||||
|
||||
|
||||
class ResultNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class IgnouResult:
|
||||
def __init__(self) -> None:
|
||||
transport = httpx.AsyncHTTPTransport(verify=False)
|
||||
self.session: httpx.AsyncClient = httpx.AsyncClient(transport=transport)
|
||||
|
||||
async def get_grade_result(self, program_id: str, enrollment_no: int):
|
||||
|
||||
program_id = program_id.upper()
|
||||
|
||||
params = {
|
||||
"prog": program_id,
|
||||
"eno": enrollment_no,
|
||||
}
|
||||
|
||||
if program_id in [
|
||||
"BCA",
|
||||
"MCA",
|
||||
"MP",
|
||||
"MBP",
|
||||
"PGDHRM",
|
||||
"PGDFM",
|
||||
"PGDOM",
|
||||
"PGDMM",
|
||||
"PGDFMP",
|
||||
]:
|
||||
params["type"] = 1
|
||||
elif program_id in ["ASSSO", "BA", "BCOM", "BDP", "BSC"]:
|
||||
params["type"] = 2
|
||||
elif program_id in [
|
||||
"BAEGH",
|
||||
"BAG",
|
||||
"BAHDH",
|
||||
"BAHIH",
|
||||
"BAPAH",
|
||||
"BAPCH",
|
||||
"BAPSH",
|
||||
"BASOH",
|
||||
"BAVTM",
|
||||
"BCOMG",
|
||||
"BSCANH",
|
||||
"BSCBCH",
|
||||
"BSCG",
|
||||
"BSWG",
|
||||
]:
|
||||
params["type"] = 4
|
||||
else:
|
||||
params["type"] = 3
|
||||
|
||||
grade_card_page = "https://gradecard.ignou.ac.in/gradecard/view_gradecard.aspx"
|
||||
|
||||
response = await self.session.get(grade_card_page, params=params)
|
||||
|
||||
soup = BeautifulSoup(response.text, "lxml")
|
||||
|
||||
name = soup.find(id="ctl00_ContentPlaceHolder1_lblDispname").string
|
||||
|
||||
try:
|
||||
trs = soup.find(string="COURSE").findParent("table")
|
||||
except AttributeError:
|
||||
raise ResultNotFoundError
|
||||
|
||||
trs = soup.find(string="COURSE").findParent("table").find_all("tr")
|
||||
|
||||
data = DotDict()
|
||||
data.enrollment_no = enrollment_no
|
||||
data.program_id = program_id
|
||||
data.name = name
|
||||
|
||||
courses = []
|
||||
data.courses = courses
|
||||
|
||||
total = DotDict()
|
||||
data.total = total
|
||||
total.total_passed = 0
|
||||
total.total_failed = 0
|
||||
total.total_marks = 0
|
||||
total.total_obtained_marks = 0
|
||||
|
||||
column_index = {}
|
||||
# get index of coulmn by name
|
||||
|
||||
cols_list = [data.get_text() for data in trs[0].find_all("th")]
|
||||
for index, col in enumerate(cols_list):
|
||||
if "COURSE" in col:
|
||||
column_index["COURSE"] = index
|
||||
elif "ASG" in col.upper():
|
||||
column_index["ASIGN"] = index
|
||||
elif "THEORY" in col:
|
||||
column_index["THEORY"] = index
|
||||
elif "PRACTICAL" in col:
|
||||
column_index["PRACTICAL"] = index
|
||||
elif "STATUS" in col:
|
||||
column_index["STATUS"] = index
|
||||
|
||||
for tr in trs[1:-1]:
|
||||
course = DotDict()
|
||||
course.max_marks = 100
|
||||
total.total_marks += course.max_marks
|
||||
|
||||
td = tr.find_all("td")
|
||||
|
||||
# course name
|
||||
course.name = td[column_index["COURSE"]].string.strip()
|
||||
|
||||
# assignments marks
|
||||
if assign := td[column_index["ASIGN"]].string.strip():
|
||||
if assign.isnumeric():
|
||||
course.assignment_marks = int(assign)
|
||||
total.total_obtained_marks += course.assignment_marks
|
||||
|
||||
# theory marks
|
||||
try:
|
||||
if theory := td[column_index["THEORY"]].string.strip():
|
||||
if theory.isnumeric():
|
||||
course.theory_marks = int(theory)
|
||||
total.total_obtained_marks += course.theory_marks
|
||||
except Exception:
|
||||
course.theory_marks = "-"
|
||||
|
||||
# lab marks
|
||||
try:
|
||||
if lab := td[column_index["PRACTICAL"]].string.strip():
|
||||
if lab.isnumeric():
|
||||
course.lab_marks = int(lab)
|
||||
total.total_obtained_marks += course.lab_marks
|
||||
except Exception:
|
||||
course.lab_marks = "-"
|
||||
|
||||
# Status # ✅ ❌
|
||||
if "NOT" not in td[column_index["STATUS"]].string.strip():
|
||||
course.status = True
|
||||
total.total_passed += 1
|
||||
|
||||
else:
|
||||
course.status = False
|
||||
total.total_failed += 1
|
||||
|
||||
courses.append(course)
|
||||
|
||||
total.total_courses = len(courses)
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"enrollment_no": enrollment_no,
|
||||
"program_id": program_id,
|
||||
"courses": courses,
|
||||
**total,
|
||||
}
|
||||
|
||||
async def gradeResultData(
|
||||
self,
|
||||
program_id: str,
|
||||
enrollment_no: int,
|
||||
):
|
||||
|
||||
data = DotDict(await self.get_grade_result(program_id, enrollment_no))
|
||||
|
||||
x = PrettyTable()
|
||||
x.padding_width = 0
|
||||
x.field_names = ["Course", "Asign", "Lab", "Term", "Status"]
|
||||
|
||||
header = "Name : {}\nProg : {} [{}]\n".format(
|
||||
data.name, data.program_id, data.enrollment_no
|
||||
)
|
||||
|
||||
for course in data.courses:
|
||||
tick = "✅" if course.status else "❌"
|
||||
|
||||
x.add_row(
|
||||
[
|
||||
course.name,
|
||||
course.assignment_marks,
|
||||
course.lab_marks,
|
||||
course.theory_marks,
|
||||
tick,
|
||||
]
|
||||
)
|
||||
|
||||
x.add_row(
|
||||
[
|
||||
"Total",
|
||||
"T:{}".format(data.total_courses),
|
||||
"-", # data.total_obtained_marks,
|
||||
"-", # data.total_marks,
|
||||
f"[{data.total_passed}/{data.total_failed}]",
|
||||
]
|
||||
)
|
||||
|
||||
return {"header": header, "table": x.get_string()}
|
Loading…
Add table
Add a link
Reference in a new issue