├── requirements.txt ├── .gitignore ├── keep_alive.py ├── README.md └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | python-telegram-bot==20.3 2 | python-dotenv -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # credential 2 | .env 3 | token.pkl 4 | 5 | # Unified portal secrets 6 | Sta.pkl 7 | Stu.pkl 8 | unified.py 9 | 10 | # Unwanted 11 | __pycache__ 12 | reference.py 13 | 14 | # jupyter notebooks 15 | *.ipynb 16 | 17 | 18 | # log file 19 | *.log -------------------------------------------------------------------------------- /keep_alive.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from threading import Thread 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route("/") 7 | def hello_world(): 8 | return "
I am live!
" 9 | 10 | 11 | def run(): 12 | app.run(host='0.0.0.0', port=8080) 13 | 14 | def keep_alive(): 15 | t = Thread(target=run) 16 | t.start() 17 | return t 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REC bot 2 | 3 | 4 | 9 | 10 | As an essential component for all Rajalakshmi Engineering College (REC) students, the web interface provided by [rajalakshmi.in](http://rajalakshmi.in/UI/Modules/Login/UniLogin.aspx) is outdated and lacks the necessary functionality required to meet the needs of the students. 11 | 12 | Therefore, the introduction of the REC bot has become necessary to fetch and view the results in a timely and efficient manner. This will enable students to access their results easily and effectively without any inconvenience. 13 | 14 | ## Features 15 | - Provides easy and simple user interface for the students to access their academic data 16 | - Removes the need to use a browser to access the data 17 | - Allows students to access their data with only their roll number and therefore does not involve any authentication process 18 | - Provides the requested data in an orderly manner 19 | - Provides a plethora of features such as: 20 | - marks - fetch internal marks 21 | - grades - fetch semester grades 22 | - attendance - fetch attendance 23 | - result - fetch recent results 24 | 25 | ## Contribution 26 | If you would like to contribute to REC bot, feel free to fork the repository and submit a pull request. We welcome any and all contributions to make REC bot even better! 27 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import pickle 4 | from unified import * 5 | from dotenv import load_dotenv 6 | from keep_alive import keep_alive 7 | 8 | load_dotenv() 9 | from replit import db 10 | 11 | from telegram.constants import ParseMode 12 | from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update 13 | from telegram.ext import ( 14 | Application, 15 | CommandHandler, 16 | ContextTypes, 17 | ConversationHandler, 18 | MessageHandler, 19 | filters, 20 | ) 21 | 22 | TEST = True 23 | 24 | 25 | # Enable logging 26 | logging.basicConfig( 27 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO 28 | ) 29 | 30 | logger = logging.getLogger(__name__) 31 | 32 | FEEDBACK, ROLLNO, RESULT, REGISTER = range(4) 33 | 34 | async def helper(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 35 | """async default message""" 36 | 37 | await update.message.reply_text( 38 | "Hi! I am REC bot\n" 39 | "I know unified portal is a crap\n" 40 | "I can fetch your marks in seconds\n\n" 41 | "/marks - fetch internal mark\n" 42 | "/grades - fetch semester grades\n" 43 | "/attendance - fetch attendance\n" 44 | "/result - fetch recent result\n" 45 | "/add_rollno - link your rollno with bot\n\n" 46 | "/feedback - like to change me? give a feedback\n" 47 | "/about - about this project" 48 | ) 49 | 50 | return None 51 | 52 | 53 | async def about(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 54 | """Send a about message""" 55 | 56 | 57 | await update.message.reply_text( 58 | "Filtering the cat marks in the unified portal is a nightmare\n" 59 | "so made this simple bot to save your time \n\n" 60 | "Here is the [github link](https://github.com/sunilxd/recbot)\n\n\n" 61 | "\\~\n" 62 | "telegram [DM](tg://user?id=1156186009)\n" 63 | "Insagram [sunilkumar](https://www.instagram.com/sunilkumar.0_o)\n" 64 | "GitHub [sunilxd](https://github.com/sunilxd)\n", 65 | parse_mode=ParseMode.MARKDOWN_V2, disable_web_page_preview=True 66 | ) 67 | 68 | 69 | async def feedback_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 70 | """ask for a feedback""" 71 | await update.message.reply_text( 72 | "Leave your feedback as a message" 73 | ) 74 | 75 | return FEEDBACK 76 | 77 | async def feedback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 78 | """get the feedback and send to me""" 79 | msg = update.message.text 80 | logger.info(f"feedback - {msg}") 81 | 82 | # send feedback to sunil 1156186009 83 | msg = f"{update.message.from_user.id} - {update.message.from_user.full_name}\nfeedback\n{msg}" 84 | await context.bot.send_message(text=msg, chat_id=1156186009) 85 | 86 | 87 | 88 | await update.message.reply_text( 89 | "I send that to the developer\n" 90 | "Can't wait?\n" 91 | "You can give a pull request [here](https://github.com/sunilxd/recbot)", 92 | parse_mode=ParseMode.MARKDOWN_V2, 93 | disable_web_page_preview=True, 94 | ) 95 | 96 | return ConversationHandler.END 97 | 98 | 99 | async def register(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 100 | """Ask about the email""" 101 | 102 | msg = "College email please" 103 | 104 | await update.message.reply_text(msg) 105 | 106 | return REGISTER 107 | 108 | 109 | async def add_student(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 110 | """with the email add the student""" 111 | 112 | email = update.message.text 113 | 114 | try: 115 | response = get_additional_role(email) 116 | person_id = response["PersonId"] 117 | 118 | info = get_personal_info(person_id) 119 | name, rollno = info["Name"], info["RollNumber"] 120 | char_remove = ['Mr..', 'Ms..', 'Mrs..', 'Dr..', 'MR.', 'Miss..', 'M/s.', 'Miss.'] 121 | for c in char_remove: 122 | name = name.replace(c, '') 123 | name = name.lower().strip() 124 | 125 | except Exception as e: 126 | logger.error(e) 127 | 128 | await update.message.reply_text( 129 | "Seems you are not yet added in unified portal\n\n" 130 | "Try after sometime" 131 | ) 132 | 133 | return ConversationHandler.END 134 | 135 | db["stu_data"][rollno] = (person_id, name, email) 136 | msg = "{} : {}\nAdded Successfully!".format(rollno, name) 137 | 138 | await update.message.reply_text(msg) 139 | 140 | return ConversationHandler.END 141 | 142 | 143 | async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 144 | """Ask about the roll number""" 145 | context.user_data["entry"] = update.message.text 146 | 147 | telegram_id = str(update.message.from_user.id) 148 | msg = "Roll number please" 149 | 150 | if telegram_id in db: 151 | 152 | reply_keyboard = [[roll_no] for roll_no in db[telegram_id]] 153 | reply_keyboard.append(['new']) 154 | 155 | 156 | await update.message.reply_text( 157 | msg, 158 | reply_markup=ReplyKeyboardMarkup( 159 | reply_keyboard, one_time_keyboard=True 160 | ), 161 | ) 162 | 163 | else: 164 | await update.message.reply_text( 165 | msg 166 | ) 167 | 168 | return ROLLNO 169 | 170 | 171 | def get_response(id, email, entry): 172 | 173 | if entry == "/marks": 174 | return get_internal_filter(id) 175 | 176 | elif entry == "/grades": 177 | return get_grades_filter(id) 178 | 179 | elif entry == "/attendance": 180 | return get_attendance_filter(email) 181 | 182 | else: 183 | return get_result_filter(email) 184 | 185 | 186 | async def rollno(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 187 | """"Get the roll number and check if the roll number is valid then ask about semester""" 188 | 189 | roll_no = update.message.text 190 | 191 | if roll_no == 'new': 192 | await update.message.reply_text( 193 | "yeah sure\nNew roll number please" 194 | ) 195 | 196 | return ROLLNO 197 | 198 | telegram_id = str(update.message.from_user.id) 199 | user = update.message.from_user.full_name 200 | 201 | sry_message = "Roll number missing\nTry adding your number by /add_rollno" 202 | 203 | 204 | if roll_no not in db["stu_data"]: 205 | await update.message.reply_text(sry_message) 206 | 207 | return ConversationHandler.END 208 | 209 | unified_id, user_name, email = db["stu_data"][roll_no] 210 | entry = context.user_data["entry"] 211 | context.user_data["roll_no"] = roll_no 212 | context.user_data["name"] = user_name 213 | 214 | logger.info(f"{telegram_id}({user}) : {roll_no}({user_name})") 215 | 216 | try: 217 | response = get_response(unified_id, email, entry) 218 | 219 | except Exception as e: 220 | logger.error(e) 221 | 222 | await update.message.reply_text( 223 | "Hmmmm.... rajalakshmi.in is down\n\n" 224 | "Try after sometime" 225 | ) 226 | 227 | return ConversationHandler.END 228 | 229 | # if first roll_no 230 | if telegram_id not in db: 231 | db[telegram_id] = {roll_no:1} 232 | 233 | # not first 234 | else: 235 | db[telegram_id][roll_no] = db[telegram_id].get(roll_no, 0) + 1 236 | 237 | 238 | context.user_data["data"] = response 239 | 240 | if isinstance(response, str): 241 | return await display_result(update, context) 242 | 243 | return await pick_result(update, context) 244 | 245 | 246 | async def pick_result(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 247 | """Pick one result""" 248 | 249 | reply_keyboard = [[clickable] for clickable in sorted(context.user_data["data"])] 250 | 251 | await update.message.reply_text( 252 | "Pick one", 253 | reply_markup=ReplyKeyboardMarkup( 254 | reply_keyboard, one_time_keyboard=True 255 | ), 256 | ) 257 | 258 | return RESULT 259 | 260 | 261 | async def display_result(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 262 | """Display the result""" 263 | 264 | data = "\n"+context.user_data["data"]+"\n{}: {}\n".format(context.user_data["roll_no"], context.user_data["name"]) 265 | 266 | await update.message.reply_text(f"```{data}```", parse_mode=ParseMode.MARKDOWN_V2, reply_markup=ReplyKeyboardRemove(),) 267 | return ConversationHandler.END 268 | 269 | 270 | async def filter_result(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 271 | """Get the semester check if it is valid then ask about Exam""" 272 | 273 | user_msg = update.message.text 274 | 275 | if user_msg not in context.user_data["data"]: 276 | await update.message.reply_text( 277 | "That is not a valid pick\n" 278 | "Please Try again" 279 | ) 280 | 281 | return ConversationHandler.END 282 | 283 | context.user_data["data"] = context.user_data["data"][user_msg] 284 | 285 | if isinstance(context.user_data["data"], str): 286 | return await display_result(update, context) 287 | 288 | return await pick_result(update, context) 289 | 290 | 291 | async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 292 | """Cancels and ends the conversation.""" 293 | 294 | await update.message.reply_text( 295 | "Bye! I hope we can talk again some day.", reply_markup=ReplyKeyboardRemove() 296 | ) 297 | 298 | return ConversationHandler.END 299 | 300 | 301 | def main() -> None: 302 | """Run the bot""" 303 | token = os.getenv("TEST_TOKEN") if TEST else os.getenv("TOKEN") 304 | 305 | application = Application.builder().token(token).build() 306 | 307 | # conversation handler 308 | conv_handler = ConversationHandler( 309 | entry_points=[ 310 | CommandHandler("marks", start), 311 | CommandHandler("grades", start), 312 | CommandHandler("attendance", start), 313 | CommandHandler("result", start), 314 | CommandHandler("feedback", feedback_start), 315 | CommandHandler("about", about), 316 | CommandHandler("add_rollno", register), 317 | MessageHandler(filters.TEXT, helper), 318 | ], 319 | states={ 320 | FEEDBACK: [MessageHandler(filters.TEXT, feedback)], 321 | ROLLNO: [MessageHandler(filters.TEXT, rollno)], 322 | RESULT: [MessageHandler(filters.TEXT, filter_result)], 323 | REGISTER: [MessageHandler(filters.TEXT, add_student)] 324 | }, 325 | fallbacks=[CommandHandler("cancel", cancel)], 326 | ) 327 | 328 | application.add_handler(conv_handler) 329 | 330 | 331 | # run 332 | application.run_polling() 333 | 334 | 335 | 336 | if __name__ == "__main__": 337 | keep_alive() 338 | main() --------------------------------------------------------------------------------