commit 3c12b45d62612f8f54de71f1b132f4b88407d6e7 Author: roshan Date: Thu Feb 16 17:48:01 2023 +0530 IGNOU Telegram Bot v1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28a7b2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +*.session \ No newline at end of file diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..45d2d1d --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: python3 -m bot diff --git a/bot/__main__.py b/bot/__main__.py new file mode 100644 index 0000000..1724bb8 --- /dev/null +++ b/bot/__main__.py @@ -0,0 +1,18 @@ +from .ignou import Ignou +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from bot.database import Database + +db = Database() +client = Ignou() + +if __name__ == "__main__": + + # schedular for checking result + scheduler = AsyncIOScheduler() + + scheduler.add_job(client.tee_crawler, 'cron', day_of_week='0-6', hour="0-19", minute='0-59/20') + + scheduler.add_job(client.grade_crawler, 'cron', day_of_week='0-6', hour="0-19", minute='0-59/25') + + scheduler.start() + client.run() \ No newline at end of file diff --git a/bot/config.py b/bot/config.py new file mode 100644 index 0000000..bf31f2f --- /dev/null +++ b/bot/config.py @@ -0,0 +1,15 @@ +import os + +class Config: + NAME = "IGNOU" + USERNAME = os.environ.get("USERNAME") + BOT_TOKEN = os.environ.get("BOT_TOKEN") + API_ID = os.environ.get("API_ID") + API_HASH = os.environ.get("API_HASH") + SUDO_CHAT = os.environ.get("SUDO_CHAT") + SUDO_ADMIN = os.environ.get("SUDO_ADMIN","").split(",") + DATABASE_URL = os.environ.get("DB_URL","localhost:27017") + SESSION_NAME = os.environ.get("SESSION_NAME") + FOOTER_CREDIT = f"\nResult fetched using {USERNAME}" + HELP_URL = os.environ.get("HELP_URL") + diff --git a/bot/database/__init__.py b/bot/database/__init__.py new file mode 100644 index 0000000..bae9c71 --- /dev/null +++ b/bot/database/__init__.py @@ -0,0 +1 @@ +from .database import Database \ No newline at end of file diff --git a/bot/database/database.py b/bot/database/database.py new file mode 100644 index 0000000..7d12ab5 --- /dev/null +++ b/bot/database/database.py @@ -0,0 +1,114 @@ +import datetime + +import motor.motor_asyncio + +from bot.config import Config + + +class Singleton(type): + __instances__ = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls.__instances__: + cls.__instances__[cls] = super(Singleton, cls).__call__(*args, **kwargs) + + return cls.__instances__[cls] + + +class Database(metaclass=Singleton): + def __init__(self): + self._client = motor.motor_asyncio.AsyncIOMotorClient(Config.DATABASE_URL) + self.db = self._client[Config.SESSION_NAME] + + self.user = self.db.user + self.crawler = self.db.crawler + self.site = self.db.site + + async def get_student(self, _id): + return await self.crawler.find_one({"_id" : _id}) + + async def get_user(self, id): + + user = await self.user.find_one({"_id": id}) + return user + + def get_time(self): + return datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S") + + async def add_user(self, _id, name): + + user = dict( + _id=_id, + name = name, + join_date=datetime.date.today().isoformat(), + action = dict( + last_used_on=self.get_time(), + ), + role_status=dict( + is_admin = False + ) + ) + await self.user.insert_one(user) + + async def is_user_exist(self, id): + user = await self.get_user(id) + return True if user else False + + async def total_users_count(self): + count = await self.user.count_documents({}) + return count + + async def total_crawlers_count(self): + count = await self.crawler.count_documents({}) + return count + + async def get_all_users(self): + all_users = self.user.find({}) + return all_users + + async def get_all_crawlers(self): + all_users = self.crawler.find({}) + return all_users + + async def get_last_used_on(self, _id): + user = await self.get_user(_id) + return user.get("last_used_on", datetime.date.today().isoformat()) + + # Last Used Time + async def update_last_used_on(self, _id): + await self.user.update_one( + {"_id": _id}, {"$set": {"action.last_used_on": self.get_time()}} + ) + + # Get When Last Time Used + async def get_last_used_on(self, _id): + user = await self.get_user(_id) + return user.last_used_on + + # Last Action + async def update_last_action(self,_id, query): + query['last_used_on'] = self.get_time() + self.user.update_one( + {"_id": _id}, + {"$set": {"action": query}}) + + async def update(self,col, _id, info_dict): + await col.update_one( + {"_id" : _id}, + info_dict) + + async def find(self, col,_id,info_dict = {}): + return await col.find_one( + {"_id" : _id }, + info_dict) + + async def insert(self, col, info_dict): + return await col.insert_one(info_dict) + + async def get_site_update(self,site): + site_update = await self.site.find_one({"_id": site}) + + if site_update is None: + await self.site.insert_one({"_id": site}) + return {} + return site_update diff --git a/bot/helper/__init__.py b/bot/helper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/helper/extractor.py b/bot/helper/extractor.py new file mode 100644 index 0000000..95e8fa3 --- /dev/null +++ b/bot/helper/extractor.py @@ -0,0 +1,55 @@ + +class User: + def __init__(self, user): + if user is None: + user = {} + + self._id = user.get("_id") + self.name = user.get("name") + self.course = user.get("action", {}).get("course") + self.myenrollment = user.get("myenrollment") + self.following = user.get("following", {}) + self.enrollment = user.get("action", {}).get("enrollment") + self.last_used_on = user.get("action", {}).get("last_used_on") + self.is_admin = user.get("role_status", {}).get("is_admin", False) + self.user = user + + def dict(self): + return self.user + +# +class Student: + def __init__(self, student): + if student is None: + student = {} + + self._id = student.get("_id") + self.name = student.get("name") + self.course = student.get("course") + self.myenrollment = student.get("myenrollment") + self.followers = student.get("followers", {}) + self.enrollment = student.get("action", {}).get("enrollment") + self.last_used_on = student.get("action", {}).get("last_used_on") + self.is_admin = student.get("role_status", {}).get("is_admin", False) + + self.grade = self.Grade(student.get("grade")) + self.tee = self.Tee(student.get("tee")) + + self.student = student + + def dict(self): + return self.student + + # grade card + class Grade: + def __init__(self, grade): + self.passed = grade.get("count",{}).get("passed", 0) + self.failed = grade.get("count",{}).get("failed", 0) + self.checked = grade.get("checked") + + # tee card + class Tee: + def __init__(self, tee): + self.count = tee.get("count",0) + self.checked = tee.get("checked") + diff --git a/bot/helper/ignoubooks.py b/bot/helper/ignoubooks.py new file mode 100644 index 0000000..55a4d63 --- /dev/null +++ b/bot/helper/ignoubooks.py @@ -0,0 +1,77 @@ +import requests +import os + +class IgnouBooks: + + def __init__(self,course=None,subject='None') -> None: + self.headers = {'User-Agent':'Dalvik/2.1.0 (Linux; U; Android 10; Redmi Note 7 Pro Build/QQ3A.200605.001)','b8S3lCfLoGo4DVAzNQnl5OpMyALyq8e2WpWbZTMlwZ5iPHr.UaVNW':'J0lta3zy@19' } + self.course = course.upper() # Course Name like BCA or MCA + self.subject = subject.upper() # If subject code passed like BCS011 or MCS023 + self.pId = '' # IGNOU course id like BCA 23 and MCA 25 + self.cId = '' # IGNOU Course subject id list + self.courseList = '' # get all subject of course + + def courseCode(self): + courses = ['https://egkapp.ignouonline.ac.in/api/programmes/type/Bachelor','https://egkapp.ignouonline.ac.in/api/programmes/type/Master'] + for courseurl in courses: + response = requests.get(courseurl,headers = self.headers).json() + for res in response: + if '(' not in res['pCode']: + if self.course == res['pCode'].upper(): + self.pId = res['pId'] + return + elif '(' in res['pCode'] and 'English' == res['pMedium']: + if self.course == res['pCode'].split("(")[0].upper(): + self.pId = res['pId'] + return + + def subjectCode(self): + self.courseCode() + subjects = 'https://egkapp.ignouonline.ac.in/api/courses/p/' + str(self.pId) + + response = requests.get(subjects,headers = self.headers).json() + + substring ='' # 〽️ + + for res in response: + fullname = res['cCode'] + con = any(map(str.isdigit, fullname)) + if not con: + con = False + continue + if '-' in fullname: + first = fullname.split('-')[0] + last = str(int(fullname.split('-')[1])//1) + finalname = first + last + + if finalname == self.subject: + self.cId = res['cId'] + title = res['cTitle'] + coursename = res['cpId']['pCode'] + substring += f'{title} \n〽️ `/book {coursename} {finalname}`\n\n' + + self.courseList = substring + + def getCourseSubjectlist(self): + self.courseCode() + self.subjectCode() + return self.courseList + + def getDownload(self): + self.courseCode() + self.subjectCode() + downloads = 'https://egkapp.ignouonline.ac.in/api/blocks/c/' + str(self.cId) + + response = requests.get(downloads,headers = self.headers).json() + + namelist = [] + for res in response: + downurl = 'https://egkapp.ignouonline.ac.in/api/blocks/download/' + str(res['bId']) + + downloadresponse = requests.get(downurl,headers = self.headers) + + open(f"{res['bCode']} [{self.subject}][@IGNOUpyBoT].pdf", 'wb').write(downloadresponse.content) + namelist.append(f"{res['bCode']} [{self.subject}][@IGNOUpyBoT].pdf") + print(f"{res['bCode']} [{self.subject}][@IGNOUpyBoT].pdf") + + return namelist diff --git a/bot/helper/ignoucrawler.py b/bot/helper/ignoucrawler.py new file mode 100644 index 0000000..a682c1f --- /dev/null +++ b/bot/helper/ignoucrawler.py @@ -0,0 +1,233 @@ +import asyncio +from bot.helper.ignouresult import IgnouResult +from bot.database import Database +from bot.helper.extractor import Student +import datetime +import time + +from pyrogram import Client +from pyrogram.errors import FloodWait,PeerIdInvalid + +db = Database() + + +class IgnouCrawler: + + def __init__(self,client) -> None: + + self.db = db + self.client = client + + self.greeted = { + "grade" : {}, + "tee" : {} + } + self.greet_msg = { + "grade" : "Grade Card Updated Today ", + "tee" : "One more result out Today 🤒" + } + + self.todayDate = datetime.datetime.today().strftime('%B %d, %Y') + + async def greet_user(self, result_type, user_id): + + if not self.greeted.get(result_type).get(user_id): + self.greeted[result_type][user_id] = True + try: + await self.client.send_message( + user_id, + f"{self.greet_msg.get(result_type)}👩🏻‍🎨",parse_mode='html') + except FloodWait as e: + time.sleep(e.x) + await self.client.send_message( + user_id, + f"{self.greet_msg.get(result_type)}👩🏻‍🎨",parse_mode='html') + except PeerIdInvalid as e: + print(f"{user_id} -> {e}") + + async def teeCrawl(self, student: Student): + + data = IgnouResult('roshan'+ student._id).teeResultString() + + if data and student.tee.count != data.get("count"): + + title = '
' + f'Name : {student.name} -> {student.course}\n' + '
' + + for user_id in student.followers: + + if not self.greeted.get("tee").get(user_id): + await self.greet_user("tee", user_id) + + try: + await self.client.send_message( + chat_id=user_id, + text= title + data.get("result"), + parse_mode='html') + + except FloodWait as e: + time.sleep(e.x) + await self.client.send_message( + chat_id=user_id, + text= title + data.get("result"), + parse_mode='html') + except PeerIdInvalid as e: + print(f"{user_id} -> {e}") + + await self.db.update( + self.db.crawler, + student._id, + { + "$set": { + "tee.count": data.get("count"), + "tee.checked": self.todayDate + } + } + ) + + async def teeTask(self): + + print("Tee Result Crawling : {}".format(datetime.datetime.today().strftime("%d/%m/%Y %H:%M:%S"))) + + await self.db.update( + self.db.site, + "ignou", + { + "$set" : { + "tee_checked": datetime.datetime.today().strftime("%d/%m/%Y %H:%M:%S") + } + } + ) + + tasks = [] + + students = await db.get_all_crawlers() + self.greeted['tee'] = {} + + # Check first TEE SIte Updated or not + site = await IgnouResult().teeCardUpdated() + if not site.get("updated"): + # print("Tee card Site not Updated") + return + + # if site updated check for results + async for student in students: + student_info = Student(student) + + if student_info.tee.checked == self.todayDate: + continue + + tasks.append( + asyncio.create_task( + self.teeCrawl(student_info) + ) + ) + + await asyncio.gather(*tasks) + + await self.db.update( + self.db.site, + "ignou", + { + "$set" : { + "tee" : site.get("date"), + "tee_checked": datetime.datetime.today().strftime("%d/%m/%Y %H:%M:%S") + } + } + ) + + async def gradeCrawl(self,student: Student): + + data = IgnouResult(student.course + student._id).gradeResultString() + + grade_passed = data.get("json", {}).get("count", {}).get("passed", 0) + grade_failed = data.get("json", {}).get("count", {}).get("failed", 0) + + if data and (int(student.grade.passed) != grade_passed or int(student.grade.failed) != grade_failed) or True: + + for user_id in student.followers: + + if not self.greeted.get("grade").get(user_id): + await self.greet_user("grade", user_id) + + try: + await self.client.send_message( + chat_id= user_id, + text = data.get("result"), + parse_mode ='html') + + except FloodWait as e: + time.sleep(e.x) + await self.client.send_message( + chat_id= user_id, + text = data.get("result"), + parse_mode ='html') + except PeerIdInvalid as e: + print(f"{user_id} -> {e}") + + await self.db.update( + self.db.crawler, + student._id, + { + "$set" : { + "grade.count.passed": grade_passed, + "grade.count.failed": grade_failed, + "grade.checked": self.todayDate + } + } + ) + + async def gradeTask(self): + print("Grade Card Crawling : {}".format(datetime.datetime.today().strftime("%d/%m/%Y %H:%M:%S"))) + + await self.db.update( + self.db.site, + "ignou", + { + "$set": { + "grade_checked": datetime.datetime.today().strftime("%d/%m/%Y %H:%M:%S") + } + } + ) + + tasks = [] + + students = await db.get_all_crawlers() + self.greeted['grade'] = {} + + # Check first Grade Site Updated or not + site = await IgnouResult().gradeCardUpdated() + if not site.get("updated"): + # print("Grade card Site not Updated") + return + + # if site updated check for results + + async for student in students: + student_info = Student(student) + + if student_info.grade.checked == self.todayDate: + continue + + tasks.append( + asyncio.create_task( + self.gradeCrawl(student_info) + ) + ) + + await asyncio.gather(*tasks) + + await self.db.update( + self.db.site, + "ignou", + { + "$set" : { + "grade": site.get("date"), + "grade_checked": datetime.datetime.today().strftime("%d/%m/%Y %H:%M:%S") + } + } + ) + + +if __name__ == "__main__": + pass + diff --git a/bot/helper/ignouresult.py b/bot/helper/ignouresult.py new file mode 100644 index 0000000..66faf9a --- /dev/null +++ b/bot/helper/ignouresult.py @@ -0,0 +1,218 @@ +import requests +from bs4 import BeautifulSoup +from prettytable import PrettyTable +import re +from bot.database import Database + +db = Database() + +class IgnouResult: + + def __init__(self,text='Bca 197940316') -> None: + self.text = text.upper().strip() + self.enrollmentNo = '' + self.courseId = '' + self.extractCourseIDandEnrollmentNo() + self.sem = 'Dec20' + + self.session = requests.Session() + self.session.headers.update({ + 'Connection': 'keep-alive', + 'Cache-Control': 'max-age=0', + 'Upgrade-Insecure-Requests': '1', + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0.1; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Mobile Safari/537.36', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', + 'Sec-GPC': '1', + 'Sec-Fetch-Site': 'same-origin', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-User': '?1', + 'Sec-Fetch-Dest': 'document', + 'Accept-Language': 'en-US,en;q=0.9,ru;q=0.8', + } + + ) + def extractCourseIDandEnrollmentNo(self): + + self.courseId = re.findall("\D+", self.text)[0].strip() + self.enrollmentNo = re.findall('\d+', self.text)[0].strip() + return self.courseId,self.enrollmentNo + + def teeResultJson(self): + + data = { + 'eno': self.enrollmentNo, + 'myhide': 'OK' + } + teeUrl = f'https://termendresult.ignou.ac.in/TermEnd{self.sem}/TermEnd{self.sem}.asp' + response = self.session.post(teeUrl, data=data) + + if 'Not found' in response.text: + return {'status':'notok'} # result not found + + soup = BeautifulSoup(response.text,'lxml') + + trs = soup.find_all('tr')[1:] + resultJson = list() + + for tr in trs: + info = tr.find_all("td") + data = list() + for index,col in enumerate(info): + if index == len(info) - 1: + break + data.append(info[index].text.strip()) + resultJson.append(data) + + return {'result' : resultJson,'count' : len(trs),'html':response.text, 'status':'ok'} + + + + + def gradeResultJson(self): + + data = { + 'Program': self.courseId, + 'eno': self.enrollmentNo, + 'submit': 'Submit', + 'hidden_submit': 'OK' + } + + if self.courseId in ['BCA', 'MCA', 'MP', 'MBP', 'PGDHRM', 'PGDFM', 'PGDOM', 'PGDMM', 'PGDFMP']: + url = 'https://gradecard.ignou.ac.in/gradecardM/Result.asp' + elif self.courseId in ['ASSSO', 'BA', 'BCOM', 'BDP', 'BSC']: + url = 'https://gradecard.ignou.ac.in/gradecardB/Result.asp' + else: + url = 'https://gradecard.ignou.ac.in/gradecardR/Result.asp' + + response = self.session.post(url, data=data) + + soup = BeautifulSoup(response.text,'lxml') + + if 'Not found' in response.text or response.status_code != 200: + return {'status':'notok'} # result not found + + trs = soup.find_all("tr")[1:] + + resultJson = list() + student = dict() + btags = soup.find_all('b')[3:6] + + for n,ba in enumerate(btags,1): + if n == 1: + student['enrollment'] = ba.text.split(":")[1].strip() + elif n == 2: + student['name'] = ba.text.split(":")[1].strip() + else: + student['course'] = ba.text.split(":")[1].strip() + + count = dict() + count['passed'] = 0 + count['failed'] = 0 + count['total'] = 0 + for tr in trs: + data = list() + + td = tr.find_all("td") + + data.append(td[0].string.strip()) + data.append(td[1].string.strip()) + data.append(td[2].string.strip()) + data.append(td[6].string.strip()) + + status = True if 'Not' not in td[7].string.strip() else False # ✅ ❌ + data.append(status) + + if status: + count['passed'] += 1 + else: + count['failed'] += 1 + count['total'] += 1 + + resultJson.append(data) + + return {'result' : resultJson,'student' : student,'count' : count,'html':response.text,'status':'ok'} + + def gradeResultString(self): + x = PrettyTable() + x.field_names = ["Course","Asign","Lab","Term","Status"] + + gradeJson = self.gradeResultJson() + + if gradeJson['status'] != 'ok': + return False + header = 'Name : {}\n -> {} -> {}\n'.format(gradeJson['student']['name'],self.courseId,self.enrollmentNo) + + for sub in gradeJson['result']: + tick = '✅' if sub[-1] else '❌' + sub[-1] = tick + x.add_row(sub) + + footer = ['count','T:{}'.format(gradeJson['count']['total']), + 'P:{}'.format(gradeJson['count']['passed']), + 'F:{}'.format(gradeJson['count']['failed']), + 'L:{}'.format(gradeJson['count']['total']-gradeJson['count']['passed'])] + x.add_row(footer) + + return { + 'enrollmentno' : self.enrollmentNo, + 'course' : self.courseId, + 'result' : '
' + header + x.get_string() + '
', + 'json' : gradeJson + } + + def teeResultString(self): + x = PrettyTable() + header = 'Enrollment no : {} ({})\n'.format(self.enrollmentNo,self.sem) + x.field_names = ["Course","Marks","Max","Month","Updation"] + + teeJson = self.teeResultJson() + + if teeJson['status'] != 'ok': + return False + + for sub in teeJson['result']: + x.add_row(sub) + + return { + 'enrollmentno' : self.enrollmentNo, + 'course' : self.courseId, + 'count' : teeJson['count'], + 'result' : '
' + header + x.get_string() + '
', + 'json' : teeJson + } + + async def gradeCardUpdated(self): + + response = self.gradeResultJson() + + if response['status'] != 'ok': + return False + + updated = re.findall('([\w]+ ?[\d]+, ?[\d]+)',response['html'])[0] + + last_updateed = await db.get_site_update("ignou") + + if updated != last_updateed.get("grade",''): + return {"date" : updated,"updated" : True} + return {"date" : updated,"updated" : False} + + + async def teeCardUpdated(self): + + teeUrl = f'https://termendresult.ignou.ac.in/TermEnd{self.sem}/TermEnd{self.sem}.asp' + + response = self.session.post(teeUrl) + + if response.status_code != 200: + return False + + updated = re.findall('([\w]+ ?[\d]+, ?[\d]+)',response.text)[0] + + # todayDate = datetime.datetime.today().strftime('%B %d, %Y') + + last_updateed = await db.get_site_update("ignou") + + if updated != last_updateed.get("tee",''): + return {"date" : updated,"updated" : True} + return {"date" : updated,"updated" : False} diff --git a/bot/ignou.py b/bot/ignou.py new file mode 100644 index 0000000..34285d2 --- /dev/null +++ b/bot/ignou.py @@ -0,0 +1,24 @@ +from pyrogram import Client +from bot.config import Config +from bot.helper.ignoucrawler import IgnouCrawler + + + + +class Ignou(Client): + def __init__(self): + super().__init__( + session_name=Config.SESSION_NAME, + bot_token=Config.BOT_TOKEN, + api_id=Config.API_ID, + api_hash=Config.API_HASH, + plugins=dict(root="bot/plugins"), + ) + + self.IgnouCrawler = IgnouCrawler(self) + + async def grade_crawler(self): + await self.IgnouCrawler.gradeTask() + + async def tee_crawler(self): + await self.IgnouCrawler.teeTask() \ No newline at end of file diff --git a/bot/plugins/__init__.py b/bot/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/plugins/book.py b/bot/plugins/book.py new file mode 100644 index 0000000..373f5b3 --- /dev/null +++ b/bot/plugins/book.py @@ -0,0 +1,33 @@ +from pyrogram import Client +from pyrogram import filters + +from bot.helper.ignoubooks import IgnouBooks +from bot.config import Config + +# Completed Add DB Support +@Client.on_message(filters.command(['book']) & filters.chat(Config.SUDO_CHAT)) +def books(client: Client, message): + text = message.text + try: + course_code = text.split(" ")[1].upper() + subject_code = text.split(" ")[2].upper() + except IndexError: + message.reply_text('Wrong Course Name or Subject Code 😐') + return + + if books.find_one(subject_code): + # add code for database books for sending from cache + pass + + if subject_code.upper() == 'LIST': + message.reply_text(IgnouBooks(course=course_code).get_courseSubjectlist()) + else: + message.reply_text("please wait.. sending books") + files = IgnouBooks(course=course_code, subject=subject_code).getDownload() + for file in files: + client.send_document( + message. + chat, + id, + file, + caption=f'Downloaded using {Config.USERNAME}') diff --git a/bot/plugins/result.py b/bot/plugins/result.py new file mode 100644 index 0000000..d69deee --- /dev/null +++ b/bot/plugins/result.py @@ -0,0 +1,272 @@ +from pyrogram import Client +from pyrogram import filters +from pyrogram.types import ( + InlineKeyboardMarkup, + InlineKeyboardButton, + CallbackQuery, + Message +) + +import datetime + +from bot.database import Database +from bot.config import Config +from bot.helper.ignouresult import IgnouResult + +from bot.helper.extractor import User + +db = Database() + + +# Completed +@Client.on_message(filters.regex('^(m|M)y')) +@Client.on_message(filters.command(['my'])) +async def my(_, message): + user: User = User(await db.get_user(message.from_user.id)) + + if 'my' == message.text.lower(): + if user.myenrollment: + await result_card(_, message) + else: + await message.reply_text("Set Your Enrollment using \n my 197xx00xx22") + return + else: + student = message.text.split() + try: + await db.update( + db.user, + message.from_user.id, + {"$set": {"myenrollment": student[1] + student[2]}}) + + await message.reply_text("Data Saved Successfully ") + except IndexError: + await message.reply_text("First Set your Enrollment using \n my course_code enrollment") + + +# Completed +@Client.on_message(filters.regex("^\D+\d{8,10}") | filters.regex("^\d{8,10}")) +async def result_card(_, message: Message): + get_course = False + user: User = User(await db.get_user(message.from_user.id)) + + if message.text.isnumeric(): + if not user.course: + await message.reply("You must check once result with Course code \n example : bca 197xx00xx22") + return + else: + get_course = user.course + # elif 'my' in message.text.lower() or 'last' in message.text.lower(): + # user: User = await db.get_user(message.from_user.id) + + result_query = '' + if get_course: + result_query = get_course + message.text + elif 'my' in message.text.lower(): + result_query = user.myenrollment + elif "last" in message.text.lower(): + result_query = user.course + user.enrollment + else: + result_query = message.text + + student: IgnouResult = IgnouResult(result_query) + result = student.gradeResultString() + + if not result: + await message.reply_text('Enrollment no is not correct or \nGrade Card Site is Down 🤕') + return + + if not user.following.get(student.enrollmentNo): + inline_keyboard = InlineKeyboardMarkup( + [ + [ # First row + InlineKeyboardButton( # Generates a callback query when pressed + "Add to Watch List 👀", + callback_data=f"add_{student.courseId}_{student.enrollmentNo}" + ), + ], + [ + InlineKeyboardButton( + "Share this 🤖", + switch_inline_query=f"\nTry this Easy IGNOU Bot\n 👉🏻 {Config.USERNAME} \n\n Created by @r0sh7n" + ) + ] + ] + ) + else: + inline_keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "Share this 🤖", + switch_inline_query=f"\nTry this Easy IGNOU Bot\n 👉🏻 {Config.USERNAME} \n Created by @r0sh7n" + ) + ] + ] + ) + + await message.reply_text( + result.get("result", '') + Config.FOOTER_CREDIT, + parse_mode='html', + reply_markup=inline_keyboard) + + await db.update_last_action( + message.from_user.id, + {"course": student.courseId, "enrollment": student.enrollmentNo}) + + # Tee Result Card + result: IgnouResult = IgnouResult(result_query).teeResultString() + if result: + await message.reply_text( + result.get("result", '') + Config.FOOTER_CREDIT, + parse_mode='html') + + +@Client.on_callback_query(filters.regex("^add") | filters.regex("^remove")) +async def watch_list(_, callback_query: CallbackQuery): + """ + callback_query example: + add_bca_192112313 + remove_bca_1092313 + """ + _, course, enrollment = callback_query.data.split("_") + + today_date = datetime.datetime.today().strftime('%B %d, %Y') + + # info about follower who is following enrollment + user_dict = { + "username": callback_query.from_user.username, + "name": callback_query.from_user.first_name, + "_id": callback_query.from_user.id, + "added_on": datetime.date.today().isoformat() + } + + # add enrollment in following list + if 'add' in callback_query.data: + + # Grade Card + student = IgnouResult(course + enrollment) + result = student.gradeResultJson() + + # if unable to fetch result from ignou site + if result.get("status") != "ok": + await callback_query.answer("Unable to fetch details\nTry after sometime ", show_alert=True) + return + + # student data + student_info = result.get("student", {}) + + # result pass fail in dict(passed,failed) + count = result.get("count", {}) + + # student info in user following section + await db.update( + db.user, + callback_query.from_user.id, + { + "$set": { + f"following.{enrollment}": { + "name": student_info.get("name"), + "course": student_info.get("course") + } + } + }) + + # this dict gonna update in crawler db + info_dict = { + "_id": enrollment + } + + # check if student is already in crawler db + if not await db.find( + db.crawler, + enrollment + ): + # collection info then update in info_dict : Dict variable + student_db = { + "name": student_info.get("name", ""), + "course": course, + "grade": { + "count": { + "passed": count.get("passed", 0), + "failed": count.get("failed", 0) + }, + "checked": today_date + }, + } + + # updating info_dict : Dict with student_db + info_dict.update(student_db) + + # Tee Card Json + result = student.teeResultJson() + + # list of out result in tee + count = result.get("count", 0) + + # this for tee section in crawler + student_db = { + "tee": { + "count": count or 0, + "checked": today_date + }, + } + + # updating again info_dict with new tee student_db + info_dict.update(student_db) + + # adding follower info in info_dict + info_dict.update( + { + "followers": { + str(callback_query.from_user.id): user_dict + } + } + ) + + # inserting first time student in crawlre db + await db.insert( + db.crawler, + info_dict + ) + + # if student is already in crawler db then + # just update following and followers secion + # in user db in crawler db + else: + await db.update( + db.crawler, + enrollment, + { + "$set": { + f"followers.{callback_query.from_user.id}": user_dict} + }) + + # remove inline_button from result + await callback_query.edit_message_reply_markup() + # push notification to user + await callback_query.answer( + f"{student_info.get('name')} Added in Watch List 🙃\n😇You will automatically receive result once it published in IGNOU 🌐", + show_alert=True) + + # this conditon for remove followed user + # from crawler followers section and in user following section + elif "remove" in callback_query.data: + + await db.update( + db.user, + callback_query.from_user.id, + { + "$unset": { + f"following.{enrollment}": "" + } + }) + await db.update( + db.crawler, + enrollment, + { + "$unset": { + f"followers.{callback_query.from_user.id}": "" + } + } + ) + await callback_query.answer("User Removed from Watchlist 👀") diff --git a/bot/plugins/user.py b/bot/plugins/user.py new file mode 100644 index 0000000..d1a8a5e --- /dev/null +++ b/bot/plugins/user.py @@ -0,0 +1,141 @@ +import re + +from pyrogram import Client +from pyrogram import filters + +from pyrogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + CallbackQuery, + Message +) + +from bot.database import Database +from bot.helper.extractor import User, Student +from bot.config import Config +from bot.plugins.result import result_card + +db = Database() + + +@Client.on_message(filters.command(['start', 'help'])) +async def start(_, message): + if 'start' in message.text.lower(): + + if not await db.is_user_exist(message.from_user.id): + await db.add_user(message.from_user.id, message.from_user.first_name) + + await message.reply_text(f"Welcome , {message.from_user.first_name} 🥳 \n \ + [Click here for more details 🔍]({Config.HELP_URL})", disable_web_page_preview=True) + + elif 'help' in message.text.lower(): + + await message.reply_text(f"{Config.HELP_URL}") + + await db.update_last_used_on(message.from_user.id) + +@Client.on_message(filters.command(["stats"])) +async def stats(client: Client,message : Message): + + user: User = User(await db.get_user(message.from_user.id)) + total_user = await db.total_users_count() + total_crawler = await db.total_crawlers_count() + total_following = len(user.following) + + msg = f""" +Hi, {message.from_user.first_name} + +Your Stat 🙃 + TG 🆔 : {message.from_user.id} + Following 🕵️ : {total_following} +""" + if user.is_admin or message.from_user.id in Config.SUDO_ADMIN: + try: + user_enrollment_not = re.findall("\d+",user.myenrollment)[0] + user_crawler_info: Student = Student(await db.get_student(user_enrollment_not)) + msg += f" Followers 👼 : {len(user_crawler_info.followers)}\n" + except (IndexError, AttributeError, TypeError): + pass + + site_info = await db.get_site_update("ignou") + grade_checked = site_info.get("grade_checked","error in monitoring") + tee_checked = site_info.get("tee_checked","error in monitoring ") + + msg += f""" +{Config.USERNAME} Stat 🤖 + Total User 🙆: {total_user} + Result Monitoring 😎: {total_crawler} + +👀 Last Grade Card Checked + 🕗 -> {grade_checked} + +Last Tee Result Check + 🕗 -> {tee_checked} +""" + await message.reply_text(msg) + + +@Client.on_callback_query(filters.regex("^user")) +async def user_info(_, callback_query: CallbackQuery): + _, enrollment = callback_query.data.split("_") + + user: User = User(await db.get_user(callback_query.from_user.id)) + + student: Student = Student(await db.find( + db.crawler, + enrollment, + {"_id" : 0} + )) + + followed_by = len(student.followers) + + msg_string = f"""👩🏻‍🎓 {student.name} + 🆔 {enrollment} ({student.course}) + Grade Card : ✝️ {student.grade.passed+student.grade.failed} ✅ {student.grade.passed} ❎ {student.grade.failed} + Grade Card Updated on {student.grade.checked} + """ + if user.is_admin or callback_query.from_user.id in Config.SUDO_ADMIN: + msg_string += f"Followed by {followed_by} 👀" + + await callback_query.answer(msg_string, show_alert=True) + + +@Client.on_message(filters.command(['watchlist'])) +async def followed_list(_, message: Message): + user: User = User(await db.get_user(message.from_user.id)) + + if len(user.following) == 0: + await message.reply_text("Not followed anyone") + return + + buttons = [] + for enrollment, usr in user.following.items(): + row = [ + InlineKeyboardButton( + usr.get("name").split()[0], + callback_data=f"user_{enrollment}" + ), + InlineKeyboardButton( + "🗑", + callback_data=f"remove_{usr.get('course')}_{enrollment}" + ), + ] + + buttons.append( + row + ) + await message.reply_text( + "👩🏻‍🎓 Users in 👀 Watchlist", + reply_markup=InlineKeyboardMarkup(buttons), + parse_mode="html") + + +@Client.on_message(filters.command(['last'])) +async def last_result_check(_, message): + user: User = User(await db.get_user(message.from_user.id)) + + if user.enrollment: + await result_card(_, message) + else: + await message.reply_text("No recent result checked") + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..96a1f48 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,35 @@ +aiohttp==3.8.4 +aiosignal==1.3.1 +APScheduler==3.10.0 +async-timeout==4.0.2 +asyncio==3.4.3 +attrs==22.2.0 +beautifulsoup4==4.11.2 +certifi==2022.12.7 +cffi==1.15.1 +charset-normalizer==3.0.1 +cryptography==39.0.1 +dnspython==2.3.0 +frozenlist==1.3.3 +idna==3.4 +lxml==4.9.2 +motor==3.1.1 +multidict==6.0.4 +prettytable==3.6.0 +pyaes==1.6.1 +pycparser==2.21 +pymongo==4.3.3 +pyOpenSSL==23.0.0 +Pyrogram==2.0.99 +PySocks==1.7.1 +pytz==2022.7.1 +pytz-deprecation-shim==0.1.0.post0 +requests==2.28.2 +six==1.16.0 +soupsieve==2.4 +TgCrypto==1.2.5 +tzdata==2022.7 +tzlocal==4.2 +urllib3==1.26.14 +wcwidth==0.2.6 +yarl==1.8.2 diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 0000000..30a1be6 --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.9.2