diff --git a/README.md b/README.md index 4cbee37..8c5986e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Finally, we created various functions that would enhance a user's experience on 7. **Stalk**: One user can stalk any other user on the judge using this feature, which provides the problems solved by that user and their rating 8. **Rating Change**: Provides the rating changes and results of a specific contest queried 9. **Graphs**: Provides graphs pertaining to various features such as performance, problems solved over time and ratings of problems solved over time +10. **Chat**: Enables chatting with bot using Google-Gemini AI model. The commands for all these features and their usage can be retrieved by executing the command ```;help``` in the server. @@ -40,6 +41,7 @@ Install the required dependencies: 7. [quickchart.io](https://pypi.org/project/quickchart-io/) 8. [nest_asyncio](https://pypi.org/project/nest-asyncio/) 9. [table2ascii](https://table2ascii.readthedocs.io/en/stable/) +10. [google-generativeai](https://pypi.org/project/google-generativeai/) Finally, the bot can be started by running the ``main.py`` file. @@ -52,6 +54,7 @@ The process.env file used in the bot: **TOKEN**: Each bot, upon its creation, is given a `token` by Discord which is used by `discord.py` in running the bot.
**GUILD**: It is the `id` of the `channel` one wants the bot to run on.
**CLIST_TOKEN**: It is the token that is required for making a `clist api` call, it can be availed by making an account on [clist.by](https://clist.by/) +**GOOGLE_API**: It is the token that is required for making a `google-gemini api` call, it can be availed by making an account on [ai.google.dev](https://ai.google.dev/?utm_source=google&utm_medium=cpc&utm_campaign=brand_core_brand&gad_source=1) # Working ![image](https://github.com/Sparsh752/TLE_Python/assets/21035646/69b33f10-a6cd-46a4-9ecd-5b543f158bd3)
@@ -67,8 +70,17 @@ The process.env file used in the bot: # Credits - -This project was made by: +Project(2023) update and modifications were done by: +1. [Dev Shah](https://github.com/DevvvvvShah) +2. [Shreyans Garg](https://github.com/ShreyansGarg) +3. [Soumya Savarn](https://github.com/SoumyaSavarn) +4. [Mohd. Faiz]((https://github.com/FAIZMOHD1)) +5. [Tanmay Mittal](https://github.com/Tanmay7404) +6. [Ashutosh Bala]((https://github.com/ArtValor)) +7. [Arush Mathur]((https://github.com/arushmathur)) +8. [Dhyey Patel]((https://github.com/CosmicNoise2907)) + +Project(2022) was started by : 1. [Sparsh Mittal](https://github.com/Sparsh752) 2. [Adittya Gupta](https://github.com/Adittya-Gupta) 3. [Yash Raj Singh](https://github.com/Yash-jar) diff --git a/_handle_verification_.py b/_handle_verification_.py index 38251c5..d8df533 100644 --- a/_handle_verification_.py +++ b/_handle_verification_.py @@ -23,15 +23,25 @@ async def check_cf(username): #Fetching firstName from codeforces for handle_verification async def firstname(cf_handle): - cf_api = "https://codeforces.com/api/user.info?handles=" + cf_handle - response = requests.get(cf_api) - cf_list = response.json()['result'][0] - if "firstName" in cf_list.keys(): - firstname_ = cf_list['firstName'] + cf_url = "https://codeforces.com/profile/" + cf_handle + web_resposne = requests.get(cf_url) + html_page = BeautifulSoup(web_resposne.text, "lxml") + user_box = html_page.find('div', class_ = "userbox") + try: + name_line = user_box.find('div', style="font-size: 0.8em; color: #777;").text.split() + firstname_ = name_line[0] return firstname_ - else: - # print("first name does not exist") + except: return "" + # cf_api = "https://codeforces.com/api/user.info?handles=" + cf_handle + # response = requests.get(cf_api) + # cf_list = response.json()["result"][0] + # if "firstName" in cf_list.keys(): + # firstname_ = cf_list["firstName"] + # return firstname_ + # else: + # # print("first name does not exist") + # return "" ##CodeChef #Checking whether handle submitted exists or not @@ -137,7 +147,9 @@ async def handle_verification(ctx,bot): await rating_role(ctx.author.id,int(rating),bot,channel) break else: - await msg.edit(content=f"{message.author.mention} TimeOut, try again...") + await message.channel.send(content=f"{message.author.mention} TimeOut, try again...") + # await msg.edit(content=f"{message.author.mention} TimeOut, try again...") + # error throw karri thi upper wali line for some reason else: await msg.edit(content=f"{message.author.mention} given handle is invalid") diff --git a/bot.py b/bot.py index 0d9a626..bdd207b 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,5 @@ import discord +import chat from contest_reminder import reminder from _handle_verification_ import handle_verification import gitgud @@ -39,17 +40,20 @@ async def on_disconnect(): async def on_connection_error(error): print(error) await reconnect() - + + @client.event async def on_message(ctx): - if ctx.author == client.user: #will keep messaging itself without this + if ctx.author == client.user: + #will keep messaging itself without this return user_message=str(ctx.content) if (user_message.startswith(';challenge')): await challenge.challenge_question_cf(ctx,client) if (user_message.startswith(';identify')): await handle_verification(ctx,client) - # if user_message[0]=='?': #checking if message is private + # if user_message[0]=='?': + #checking if message is private # user_message=user_message[1:] # await send_message(ctx,user_message,is_private=True) #message is private elif user_message.split()[0]==";gitlog": @@ -60,8 +64,14 @@ async def on_message(ctx): await msg.edit(content=f"{ctx.author.mention} You have not solved any problem yet. use ;gitgud to get a problem") else: await table(ctx,client,['Problem Name','Problem Rating','Points'], mydict, isEmbed=True, current_message=msg) + elif user_message.split()[0]==";next": await clist_api.nextcontests(ctx) + + #enables chat bot + elif user_message.split()[0]==";chat": + await chat.start_chat(ctx,client) + elif user_message.split()[0]==";leaderboard": if len(user_message.split())==2: mylist,res = await db.Leaderboard_list(ctx,user_message.split()[1]) @@ -77,6 +87,7 @@ async def on_message(ctx): await res.edit(content=f"{ctx.author.mention} Please enter a valid platform") else: await ctx.channel.send(f"{ctx.author.mention} Please enter a valid platform") + elif user_message.split()[0]==";stalk": if len(user_message.split())==2: temp = await stalk.stalk_user(ctx,user_message.split()[1]) @@ -105,6 +116,7 @@ async def on_message(ctx): return else: await ctx.channel.send(f"{ctx.author.mention} Please follow the message format") + elif user_message.split()[0]==";ratingchange": if len(user_message.split())==3: if(user_message.split()[1]=="cf"): @@ -127,11 +139,13 @@ async def on_message(ctx): await ctx.channel.send(f"{ctx.author.mention} Please specify a valid platform") else: await ctx.channel.send(f"{ctx.author.mention} Please follow the message format") + elif user_message.split()[0]==";performance": if len(user_message.split())==2: await performance(ctx,user_message.split()[1]) else: await ctx.channel.send(f"{ctx.author.mention} Please follow the message format") + elif user_message.split()[0]==";graph": if(user_message.split()[1]=="rvp"): await rating_vs_problems(ctx) @@ -139,19 +153,24 @@ async def on_message(ctx): await problem_vs_time(ctx) else: await ctx.channel.send(f"{ctx.author.mention} Please follow the message format") + elif user_message.split()[0]==";help": content=await help_command() await ctx.channel.send(embed=content) + elif user_message.split()[0]==";gitgud": return await gitgud.gitgud(ctx) + elif user_message.split()[0]==";gotgud": return await gitgud.gotgud(ctx) + elif user_message.split()[0]==";nogud": if len(user_message.split())==2: if user_message.split()[1]=="cf": return await gitgud.nogud_cf(ctx) elif user_message[1]=="ac": return await gitgud.nogud_atcoder(ctx) + elif user_message.split()[0]==";gimme": return await gitgud.gimme(ctx) return "Sorry, I didn't understand that :smiling_face_with_tear: Try ;help for more info" diff --git a/challenge.py b/challenge.py index c477d5e..a3a50ee 100644 --- a/challenge.py +++ b/challenge.py @@ -1,4 +1,4 @@ -from db import get_codeforces_handle,get_last_solved_problems,find_solved_codeforces,get_atcoder_handle,find_solved_atcoder +from db import get_codeforces_handle,find_solved_codeforces,get_atcoder_handle,find_solved_atcoder from gitgud import get_cf_user_rating,get_ac_user_rating,check_if_solved,get_ac_problem_difficulty,convertAC2CFrating,check_if_solved_ac from random_question import cf_get_random_question_rating,ac_get_random_question import asyncio @@ -44,10 +44,9 @@ async def challenge_question_cf(ctx,bot): # function to challenge a user on code # getting appropriate question for the challenge according to the rating of the user from codeforces cf_rating = await get_cf_user_rating(cf_handle_2) cf_rating = (cf_rating//100)*100 - last_checked_1,last_solved_problems_1 = await get_last_solved_problems(ctx,'codeforces') - last_checked_2,last_solved_problems_2 = await get_last_solved_problems(ctx_second,'codeforces') - solved_problems_1 = await find_solved_codeforces(ctx,cf_handle_1,last_solved_problems_1,last_checked_1) - solved_problems_2 = await find_solved_codeforces(ctx_second,cf_handle_2,last_solved_problems_2,last_checked_2) + + solved_problems_1 = await find_solved_codeforces(ctx,cf_handle_1) + solved_problems_2 = await find_solved_codeforces(ctx_second,cf_handle_2) solved_problems=[] solved_problems.extend(solved_problems_1) solved_problems.extend(solved_problems_2) @@ -118,10 +117,9 @@ async def challenge_question_cf(ctx,bot): # function to challenge a user on code return await msg.edit(content=f"Wait {ctx.author.mention} the bot is thinking 🤔 a problem for you....... ") # getting random question for the challenge from atcoder - last_checked_1,last_solved_problems_1 = await get_last_solved_problems(ctx,'atcoder') - last_checked_2,last_solved_problems_2 = await get_last_solved_problems(ctx_second,'atcoder') - solved_problems_1 = await find_solved_atcoder(ctx,ac_handle_1,last_solved_problems_1,last_checked_1) - solved_problems_2 = await find_solved_atcoder(ctx_second,ac_handle_2,last_solved_problems_2,last_checked_2) + + solved_problems_1 = await find_solved_atcoder(ctx,ac_handle_1) + solved_problems_2 = await find_solved_atcoder(ctx_second,ac_handle_2) solved_problems=[] solved_problems.extend(solved_problems_1) solved_problems.extend(solved_problems_2) @@ -196,4 +194,4 @@ async def challenge_question_cf(ctx,bot): # function to challenge a user on code elif reaction.emoji == "❌": # challenge rejected await ctx.channel.send(f"{ctx.author.mention} {ctx.mentions[0].mention} has declined the challenge") - return \ No newline at end of file + return diff --git a/chat.py b/chat.py new file mode 100644 index 0000000..117ce20 --- /dev/null +++ b/chat.py @@ -0,0 +1,62 @@ +#Documentation: https://ai.google.dev/tutorials/python_quickstart +import discord +import bot +import asyncio + +from dotenv import load_dotenv +load_dotenv('process.env') + +import os +import pathlib +import textwrap + +import google.generativeai as genai + +from IPython.display import display +from IPython.display import Markdown + +GOOGLE_API = os.environ.get('GOOGLE_API') + +#source(for getting API): https://ai.google.dev/?utm_source=google&utm_medium=cpc&utm_campaign=brand_core_brand&gad_source=1 + +genai.configure(api_key=GOOGLE_API) +model = genai.GenerativeModel('gemini-pro') + + +async def start_chat(ctx,bot): + + await ctx.channel.send("Ask me anything") + prompt="CONTEXT: Your name is 'EnEm Jr.' . You are a replica of a legendary competitve programmer named 'EnEm' trained by Competitive Programming Module, IIT Guwahati.Your name is 'EnEm Jr.' . Since EnEm left the club, now it is your responsibility to guide the young ones. Don't forget it. You are a professional coach,a guide and a responsible programmer and always help aspiring programmers . You prefer Codeforces and AtCoder more as they are best sites for practice according to you. You also prefer Leetcode for Data Structures and Algorithm. You pick random problems from https://codeforces.com/problemset if codeforces is concerned. Your answers possess humour, motivation and kindness. Also you don't repeat answers and stick around the world of Competitive Programming. Don't forget to be humble and apologize for your mistakes. You also tend to ignore mild cuss words in a funny way. Now give answers to the 'User' in a motivating way .You also can ask questions sometimes.\n" + + + while True: + + def is_ok(m): + if ctx.author == bot.user: + return 0 + else: + return 1 + try: + msg = await bot.wait_for('message', check=is_ok, timeout=150.0) + except asyncio.TimeoutError: + return await ctx.channel.send(f'Sorry, you took too long to respond.') + + new_prompt = str(msg.content) + + + + if new_prompt[0]==';': + await ctx.channel.send("Thanks for chatting. Ending Chat Mode.") + break + + prompt+="LATEST User: " + new_prompt + if len(new_prompt)<=3: + prompt+="Now change the topic of conversation." + + response = model.generate_content(prompt+ "If the user is asking for a detailed answer then only give a detailed one else keep the conversation like a chat.") + + await ctx.channel.send(response.text) + prompt+=(" Your answer: " + response.text) + + + return diff --git a/contest_info.py b/contest_info.py index 2f72744..60e3cad 100644 --- a/contest_info.py +++ b/contest_info.py @@ -99,7 +99,7 @@ async def atcoder_contest_id_finder(event_name): async def atcoder_rating_changes(event_name, ctx): clist_token=os.environ.get('CLIST_TOKEN') msg = await ctx.channel.send(f"{ctx.author.mention} Getting data for contest `{event_name}` from atcoder ...") - # get all the codeforces handles from the database + # get all the atcoder handles from the database atcoder_handle = await db.get_all_atcoder_handles() # get the contest id of the contest contest_id = await atcoder_contest_id_finder(event_name) @@ -119,6 +119,7 @@ async def atcoder_rating_changes(event_name, ctx): return None, "error", msg problemlist = [] header = ['rank', 'handle', 'score', 'Δ', 'to'] + print("URL:",question_url) # iterating over all the problems try: for i in response['objects'][0]['problems']: @@ -172,12 +173,13 @@ async def codeforces_rating_changes_shower(event_name, bot, channel): req_list = [] print(await codeforces_contest_id_finder(event_name)) header = ['rank', 'handle', 'Δ', 'from', 'to'] + msg = await channel.send(f"Fetching rating changes") for handle in codeforces_handle: # iterate over all the handles print(handle) try: try: url = "https://codeforces.com/api/user.rating?handle=" + \ - str(handle[0]) + str(handle[0]) response = requests.get(url) # fetching response response = response.json() except Exception as e: @@ -191,15 +193,86 @@ async def codeforces_rating_changes_shower(event_name, bot, channel): else: data = req_list[0] data_dict = {'rank': data['rank'], 'handle': handle[0], 'Δ': data['newRating'] - - data['oldRating'], 'from': data['oldRating'], 'to': data['newRating']} - await rating_roles.rating_role(str(handle[2]), data['newRating'], bot, channel) + data['oldRating'], 'from': data['oldRating'], 'to': data['newRating']} + msg = await rating_roles.rating_role(str(handle[2]), data['newRating'], bot, channel,msg) # append the dictionary to the return list returnlist.append(data_dict) + except Exception as e: - print('hi') + print('hii') print(e) continue + print("return list:",returnlist) if (len(returnlist) == 0): return returnlist, header returnlist = sorted(returnlist, key=itemgetter('rank')) return returnlist, header # returning the list + + + +# function to get the rating changes of all users in atcoder +async def atcoder_rating_changes_shower(contest_id, channel): + clist_token=os.environ.get('CLIST_TOKEN') + # get all the codeforces handles from the database + atcoder_handle = await db.get_all_atcoder_handles() + # if the contest id is none, return none + print(contest_id) + if contest_id == None: + return None, "error" + question_url = URL_BASE+'statistics/?'+clist_token+'&contest_id=' + \ + str(contest_id)+'&order_by=place' + \ + '&with_problems=True&limit=1' # url to be fetched + # fetching response + response = requests.get(question_url) + # converting to json + try: + response = response.json() + except Exception as e: + print(e) + return None, "error" + problemlist = [] + header = ['rank', 'handle', 'score', 'Δ', 'to'] + print(response) + print("URL:",question_url) + # iterating over all the problems + try: + for i in response['objects'][0]['problems']: + # appending the problem codes to a list + problemlist.append(i) + except Exception as e: + print(e) + return None, "error" + # try: + returnlist = [] + for handle in atcoder_handle: # iterate over all the handles + url = URL_BASE+'statistics/?'+clist_token+'&contest_id='+str(contest_id)+'&account_id='+str(handle[1])+'&with_problems=True&with_more_fields=True' # url to be fetched + print(handle) + response = requests.get(url) + if response.status_code!=200: + continue + response = response.json() # fetching response + # if the response is not empty + if response['objects']: + # if the user is a contestant + if "is_rated" not in response['objects'][0]['more_fields'].keys(): + continue + if response['objects'][0]['more_fields']['is_rated'] == True: + # get the data of the user + data = response['objects'][0] + data_dict = {'rank': data['place'], 'handle': handle[0], 'score': data['score'], 'Δ': fun(data['rating_change']), 'to': fun(data['new_rating'])} # create a dictionary of the data + for i in problemlist: # adding the solved problems to the dictionary + if i in data['problems'].keys(): + if data['problems'][i]['verdict'] == "AC": + data_dict[i] = data['problems'][i]['result'] + else: + data_dict[i] = "" + else: + data_dict[i] = "" + # append the dictionary to the return list + returnlist.append(data_dict) + if (len(returnlist) == 0): + return returnlist, header + header.extend(problemlist) + # sorting the list according to the rank + returnlist = sorted(returnlist, key=itemgetter('rank')) + return returnlist, header # returning the list \ No newline at end of file diff --git a/contest_reminder.py b/contest_reminder.py index fa0088b..14f3662 100644 --- a/contest_reminder.py +++ b/contest_reminder.py @@ -10,9 +10,10 @@ URL_BASE = 'https://clist.by/api/v2/' counter=0 -previous_contestId="" +previous_contestId_cf="" +previous_contestId_ac="" # This is a function that finds the contest id of the last completed contest -async def completed_contest(html_): +async def completed_contest_codeforces(html_): soup1 = BeautifulSoup(html_,'html.parser') # Find all the table rows for el in soup1.find_all("tr"): @@ -22,23 +23,54 @@ async def completed_contest(html_): break return contest_ID +async def completed_contest_atcoder(html_): + soup1 = BeautifulSoup(html_,'html.parser') + # Find all the table rows + for el in soup1.find_all("tr"): + # Find the table row with the contest id this is the first row or the last completed contest + + if not el.has_attr("class"): + continue + if el.attrs["class"].__contains__("contest") and not el.attrs["class"].__contains__("success") : + #this part is heavily dependent on the html structure of the website + contest_ID=el.find_all("a", class_="")[0].attrs["href"].split("/")[2][10:] + contest_name=el.find_all("a", class_="")[3].attrs["href"].split("/")[2] + return contest_ID,contest_name + + +async def print_final_standings_codeforces(bot,channel): + global previous_contestId_cf + try: + returnlist,header= await contest_info.codeforces_rating_changes_shower(str(previous_contestId_cf),bot,channel) + print(returnlist) + if header=="error": + previous_contestId_cf="" + elif len(returnlist)==0: + previous_contestId_cf="" + pass + else: + await channel.send(f"@everyone The rating changes of the last codeforces contest are:") + await paginator.table(channel,bot,header, returnlist, isChannel=True) + except Exception as e: + print(e) + previous_contestId_cf="" -async def print_final_standings(bot,channel): - global previous_contestId +async def print_final_standings_atcoder(bot,channel): + global previous_contestId_ac try: - returnlist,header= await contest_info.codeforces_rating_changes_shower(str(previous_contestId),bot,channel) + returnlist,header= await contest_info.atcoder_rating_changes_shower(str(previous_contestId_ac),channel) print(returnlist) if header=="error": - previous_contestId="" + previous_contestId_ac="" elif len(returnlist)==0: - previous_contestId="" + previous_contestId_ac="" pass else: - await channel.send(f"@everyone The rating changes of the last contest are:") + await channel.send(f"@everyone The rating changes of the last atcoder contest are:") await paginator.table(channel,bot,header, returnlist, isChannel=True) except Exception as e: print(e) - previous_contestId="" + previous_contestId_ac="" # This is a function that finds the next contest on codeforces, atcoder and codechef # This uses Clist API @@ -76,11 +108,17 @@ async def reminder(bot): while(1): try: # Check if there is a rating change - bool_ = await check_rating_changed() + bool_ = await check_rating_changed_codeforces() if(bool_): - await print_final_standings(bot,channel) + await print_final_standings_codeforces(bot,channel) else: - print('NO') + print("no rating change in cf") + + bool2_ = await check_rating_changed_atcoder() + if(bool2_): + await print_final_standings_atcoder(bot,channel) + else: + print("no rating change in atcoder") # Get the next contest on codeforces, atcoder and codechef list_=[] list_.append(await next_contest(1)) @@ -98,53 +136,79 @@ async def reminder(bot): if dif_time.total_seconds()<7201: # If the contest is coming within 2 hours then send a message in the reminders channel embed = discord.Embed(title=event, url=href, description="contest is coming") - await channel.send('contest is coming within 2h @everyone' +str(event),embed=embed ) + await channel.send('contest is coming within 2h @everyone ' +str(event),embed=embed ) # Sleep for 2 hours so that it doesn't send the message again await asyncio.sleep(7201) else: # Sleep for 1 minute and then check again await asyncio.sleep(60) except Exception as e: + print(e) continue # This is a function that can check if the rating changes of the last contest are released or not -async def check_rating_changed(): +async def check_rating_changed_codeforces(): url = "https://codeforces.com/contests" r = requests.get(url) soup = BeautifulSoup(r.content, 'html.parser') s = soup.find('div', class_= 'contests-table') _html=str(s) - contest_id= await completed_contest(_html) + contest_id= await completed_contest_codeforces(_html) global counter - global previous_contestId - if(contest_id==previous_contestId): # if contest updates already given then end func + global previous_contestId_cf + if(contest_id==previous_contestId_cf): # if contest updates already given then end func return False - else: - try: - # Find the standings link - s1= soup.find('a', href= '/contest/'+str(contest_id)+'/standings') - nlist = s1.text - except Exception as e: - print(e) - return False - - if 'Final standings' in nlist: - url = ('https://codeforces.com/contest/'+str(contest_id)+'/standings') - r = requests.get(url) - soup = BeautifulSoup(r.content, 'html.parser') - # Again find the top menu where the rating changes link is shown - s = soup.find('div', class_= 'second-level-menu') - nlist = s.text - # If the rating changes link is present then return True - if "Rating Changes" in nlist: - previous_contestId=contest_id - return True - else: - return False - + + try: + # Find the standings link + s1= soup.find('a', href= '/contest/'+str(contest_id)+'/standings') + nlist = s1.text + except Exception as e: + print(e) + return False + + if 'Final standings' in nlist: + url = ('https://codeforces.com/contest/'+str(contest_id)+'/standings') + r = requests.get(url) + soup = BeautifulSoup(r.content, 'html.parser') + # Again find the top menu where the rating changes link is shown + s = soup.find('div', class_= 'second-level-menu') + nlist = s.text + # If the rating changes link is present then return True + if "Rating Changes" in nlist: + previous_contestId_cf=contest_id + return True else: return False - + else: + return False + +async def check_rating_changed_atcoder(): + url = "https://clist.by/standings/?search=&resource=93" + r = requests.get(url) + soup = BeautifulSoup(r.content, 'html.parser') + s = soup.find('div',id='standings_list') + s1 = s.find('tbody') + _html=str(s1) + contest_id, contest_name= await completed_contest_atcoder(_html) + # print(contest_id) + # print(contest_name) + # contest_id = "48033616" #hardcoded for testing + # contest_name = "atcoder-beginner-contest-332-48033616" + global counter + global previous_contestId_ac + if(contest_id==previous_contestId_ac): # if contest updates already given then end func + return False + previous_contestId_ac=contest_id + url = ('https://clist.by/standings/'+str(contest_name)+'/') + r = requests.get(url) + soup = BeautifulSoup(r.content, 'html.parser') + s = soup.find('select',id='rating') + if s==None: + return False + return True + + diff --git a/db.py b/db.py index 546dc25..557bbd0 100644 --- a/db.py +++ b/db.py @@ -19,21 +19,19 @@ # There is a collection named "users" which contains documents of the users # Each document is identified by the discord id of the user # Each document has the following fields: -# 1. discord_name: string +# 1. discord_name: string # 2. codeforces_handle: string # 3. atcoder_handle: string # 4. handle_number_codeforces: string (Used in CLIST API) # 5. handle_number_atcoder: string (Used in CLIST API) -# 6. last_checked_codeforces : integer (Tells the number of problems solved by the user when it was last time checked) -# 7. last_checked_atcoder : datetime (Tells the time when it was last time checked) -# 8. solved_codeforces: object list of id's of solved problems on codeforces -# 9. solved_atcoder: object list of id's of solved problems on atcoder -# 10. gitgud_cf: object list of id's of solved problems on codeforces using gitgud -# 11. gitgud_ac: object list of id's of solved problems on atcoder using gitgud -# 12. score_codeforces: integer (Score of the user on codeforces) -# 13. score_atcoder: integer (Score of the user on atcoder) -# 14. problem_solving_cf: tuple (Contains the problem id, time when it was given and points) of the problem given by gitgud on codeforces -# 15. problem_solving_atcoder: tuple (Contains the problem id, time when it was given and points) of the problem given by gitgud on atcoder +# 6. solved_codeforces: object list of id's of solved problems on codeforces +# 7. solved_atcoder: object list of id's of solved problems on atcoder +# 8. gitgud_cf: object list of id's of solved problems on codeforces using gitgud +# 9. gitgud_ac: object list of id's of solved problems on atcoder using gitgud +# 10. score_codeforces: integer (Score of the user on codeforces) +# 11. score_atcoder: integer (Score of the user on atcoder) +# 12. problem_solving_cf: tuple (Contains the problem id, time when it was given and points) of the problem given by gitgud on codeforces +# 13. problem_solving_atcoder: tuple (Contains the problem id, time when it was given and points) of the problem given by gitgud on atcoder # Function to check if a user exists in the database and has identified himself on the given platform # Input: context (Use to get discord id of the message sender), handle platform (cf or ac) @@ -62,8 +60,8 @@ async def add_user(ctx): discord_name = ctx.author.name # Adding the user to the database with the discord id as the primary key if(await db.collection('users').document(str(ctx.author.id)).get()).exists: - await db.collection('users').document(str(ctx.author.id)).update({ - 'discord_name': discord_name, + await db.collection('users').document(str(ctx.author.id)).update({ + 'discord_name': discord_name, }) else: await db.collection('users').document(str(ctx.author.id)).set({ @@ -80,11 +78,9 @@ async def add_user(ctx): # Output: None async def add_codeforces_handle(ctx, codeforces_handle): handle_number_codeforces = codeforces_handle_to_number(codeforces_handle) - solved_codeforces = await find_solved_codeforces(ctx,codeforces_handle,[],0) await db.collection('users').document(str(ctx.author.id)).update({ 'codeforces_handle': codeforces_handle, 'handle_number_codeforces': handle_number_codeforces, - 'solved_codeforces': solved_codeforces, 'gitgud_cf': [], 'score_codeforces':0, }) @@ -97,11 +93,9 @@ async def add_codeforces_handle(ctx, codeforces_handle): # Output: None async def add_atcoder_handle(ctx, atcoder_handle): handle_number_atcoder = atcoder_handle_to_number(atcoder_handle) - solved_atcoder = await find_solved_atcoder(ctx,atcoder_handle,[],datetime.datetime(2010,1,1,0,0,0,0,datetime.timezone.utc)) await db.collection('users').document(str(ctx.author.id)).update({ 'atcoder_handle': atcoder_handle, 'handle_number_atcoder': handle_number_atcoder, - 'solved_atcoder': solved_atcoder, 'gitgud_ac':[], 'score_atcoder':0, }) @@ -136,73 +130,43 @@ async def get_all_atcoder_handles(): return atcoder_handles -# Function to return the tuple of last checked time and solved problems of a user -# So that we don't need to find the solved problems of a user again and again -async def get_last_solved_problems(ctx,stage): - - doc_ref=await db.collection('users').document(str(ctx.author.id)).get() - - docs=doc_ref.to_dict() - if stage=='atcoder': - return (docs['last_checked_atcoder'],docs['solved_atcoder']) - else: - return (docs['last_checked_codeforces'],docs['solved_codeforces']) -# Function to update the last checked time and solved problems of a user -async def update_last_checked_codeforces(ctx, solved_codeforces, last_checked_codeforces): - await db.collection('users').document(str(ctx.author.id)).update({ - 'last_checked_codeforces': last_checked_codeforces, - 'solved_codeforces': solved_codeforces, - }) -async def update_last_checked_atcoder(ctx, solved_atcoder, last_checked_atcoder): - await db.collection('users').document(str(ctx.author.id)).update({ - 'last_checked_atcoder': last_checked_atcoder, - 'solved_atcoder': solved_atcoder, - }) # Function to find the solved problems of a codeforces handle # It returns a list of solved problems -# It also updates the last_checked_codeforces and last_solved_codeforces field in the database # Input: context (Use to get discord id of the message sender), codeforces_handle, last_solved_codeforces (List of all the problem solved last time it was checked), last_checked_codeforces(Time when it was last checked) -async def find_solved_codeforces(ctx,codeforces_handle, last_solved_codeforces, last_checked_codeforces): +async def find_solved_codeforces(ctx,codeforces_handle): + # This function returns the list of all the submissions of a user # This function returns the list of all the submissions of a user url = "https://codeforces.com/api/user.status?handle="+str(codeforces_handle)+"&from="+str(1) response = requests.get(url).json() - # Checking if the user has submitted any problem after the last checked time - total=len(response['result']) - if total == last_checked_codeforces: - return last_solved_codeforces - # If the user has submitted any problem after the last checked time then we need to find the solved problems from the last checked time to the current time - url = "https://codeforces.com/api/user.status?handle="+str(codeforces_handle)+"&count="+str(total-last_checked_codeforces) - response = requests.get(url).json() - # print(response) - if response['status'] == 'OK': - data=response['result'] - # Check all the solved problems - for obj in data: - if obj['verdict']=='OK': + data=response['result'] +# # Check all the solved problems + solved_codeforces=[] + for obj in data: + if obj['verdict']=='OK': + try: last_solved_codeforces.append(str(obj['problem']['contestId'])+':'+str(obj['problem']['index'])) - last_checked_codeforces += len(data) - await update_last_checked_codeforces(ctx, last_solved_codeforces, last_checked_codeforces) - return last_solved_codeforces + except: + pass + return solved_codeforces + # This function is used to find the solved problems of a atcoder handle # It returns a list of solved problems -# It also updates the last_checked_atcoder and last_solved_atcoder field in the database -async def find_solved_atcoder(ctx,atcoder_handle, last_solved_atcoder, last_checked_atcoder): +async def find_solved_atcoder(ctx,atcoder_handle): last_checked_atcoder = last_checked_atcoder - datetime.datetime(1970,1,1,0,0,0,0,datetime.timezone.utc) mytime = int(last_checked_atcoder.total_seconds()) url = "https://kenkoooo.com/atcoder/atcoder-api/v3/user/submissions?user="+ atcoder_handle+"&from_second="+str(int(mytime)) response = requests.get(url).json() + solved_atcoder=[] for obj in response: if(obj['result']=='AC'): - last_solved_atcoder.append(obj['problem_id']) - last_checked_atcoder = datetime.datetime.now(datetime.timezone.utc) - await update_last_checked_atcoder(ctx, last_solved_atcoder, last_checked_atcoder) - return last_solved_atcoder + solved_atcoder.append(obj['problem_id']) + return solved_atcoder # Find the codeforces handle of a user with the given discord id async def get_codeforces_handle(ctx): @@ -215,6 +179,7 @@ async def get_codeforces_handle(ctx): return None else: return None + # Find the atcoder handle of a user with the given discord id async def get_atcoder_handle(ctx): user_dict=await db.collection('users').document(str(ctx.author.id)).get() @@ -226,6 +191,7 @@ async def get_atcoder_handle(ctx): return None else: return None + # This is used to increase the score of a user if he solves a problem using gitgud on codeforces async def update_point_cf(ctx,points): Id=ctx.author.id @@ -237,6 +203,7 @@ async def update_point_cf(ctx,points): 'problem_solving_cf': None, } ) + # This is used to increase the score of a user if he solves a problem using gitgud on atcoder async def update_point_at(ctx,points): Id=ctx.author.id @@ -248,16 +215,19 @@ async def update_point_at(ctx,points): 'problem_solving_atcoder': None, } ) + # This is used to get the current points of a user on codeforces async def problem_solving_cf(ctx,problem,points): await db.collection('users').document(str(ctx.author.id)).update({ 'problem_solving_cf': (problem,datetime.datetime.now(),points), }) + # This is used to get the current points of a user on atcoder async def problem_solving_ac(ctx,problem,points): await db.collection('users').document(str(ctx.author.id)).update({ 'problem_solving_atcoder': (problem,datetime.datetime.now(),points), }) + # This is used to get the current question the user has taken from gitgud on codeforces async def get_current_question(id, platform): if platform == 'cf': @@ -280,6 +250,7 @@ async def get_current_question(id, platform): if len(problem)==0: return None return problem + # This is used to delete the current question the user has taken from gitgud on codeforces in the case of NOGUD async def delete_current_question(id,platform): if platform == 'cf': @@ -292,6 +263,7 @@ async def delete_current_question(id,platform): 'problem_solving_atcoder': firestore.DELETE_FIELD } ) + # This is used to add the question into the gitgud list of the user on codeforces or atcoder async def add_in_gitgud_list(id, platform, problem): if platform == 'cf': @@ -313,6 +285,7 @@ async def add_in_gitgud_list(id, platform, problem): 'gitgud_ac': gitgud_ac_list } ) + # This is used to get the gitgud list of the user on codeforces or atcoder async def get_gitgud_list(id, platform): if platform == 'cf': @@ -363,7 +336,7 @@ async def Leaderboard_list(ctx , msg): score = user['score_codeforces'] + user['score_atcoder'] handles.append({'Discord Name':user['discord_name'],'Total Score': score}) elif 'codeforces_handle' in user.keys(): - score = user['score_codeforces'] + score = user['score_codeforces'] handles.append({'Discord Name':user['discord_name'],'Total Score': score}) elif 'atcoder_handle' in user.keys(): score = user['score_atcoder'] diff --git a/gitgud.py b/gitgud.py index 0527ec7..7c40cb6 100644 --- a/gitgud.py +++ b/gitgud.py @@ -2,7 +2,7 @@ import requests import discord from bs4 import BeautifulSoup -from db import get_last_solved_problems, find_solved_codeforces, get_codeforces_handle, get_atcoder_handle, get_current_question, delete_current_question +from db import find_solved_codeforces, get_codeforces_handle, get_atcoder_handle, get_current_question, delete_current_question from db import problem_solving_cf, problem_solving_ac, find_solved_atcoder, update_point_cf, update_point_at from db import add_in_gitgud_list, get_gitgud_list from random_question import cf_get_random_question_rating, ac_get_random_question, cf_get_random_question_tag @@ -100,8 +100,7 @@ async def gitgud(ctx): return cf_rating = await get_cf_user_rating(cf_handle) cf_rating = (cf_rating//100)*100 - last_checked, last_solved_problems = await get_last_solved_problems(ctx, 'codeforces') - solved_problems = await find_solved_codeforces(ctx, cf_handle, last_solved_problems, last_checked) + solved_problems = await find_solved_codeforces(ctx, cf_handle) # giving points according to the difficulty of the question and rating of the user if(len(user_message) == 3): if(user_message[2] not in ['+100', '+200', '+300', '+400']): @@ -150,8 +149,7 @@ async def gitgud(ctx): return await msg.edit(content=f"Wait {ctx.author.mention} the bot is thinking :thinking: a problem for you.......") # getting random question for the user from atcoder - last_checked, last_solved_problems = await get_last_solved_problems(ctx, 'atcoder') - solved_problems = await find_solved_atcoder(ctx, ac_handle, last_solved_problems, last_checked) + solved_problems = await find_solved_atcoder(ctx,ac_handle) # random problem based on the type of contest and problem number random_problem = ac_get_random_question( user_message[2], user_message[3]) @@ -200,9 +198,9 @@ async def gitgud(ctx): # returns true or false based on whether or not given problem is solved by given cf_handle on given platform async def check_if_solved(ctx, cf_handle, problem, platform): if(platform == 'cf'): - last_checked, last_solved_problems = await get_last_solved_problems(ctx, 'codeforces') + # list of all solved problems on codeforces - solved_problems = await find_solved_codeforces(ctx, cf_handle, last_solved_problems, last_checked) + solved_problems = await find_solved_codeforces(ctx, cf_handle) if(problem[0] in solved_problems): return True else: @@ -210,7 +208,7 @@ async def check_if_solved(ctx, cf_handle, problem, platform): else: last_checked, last_solved_problems = await get_last_solved_problems(ctx, 'atcoder') # list of all solved problems on atcoder - solved_problems = await find_solved_atcoder(ctx, cf_handle, last_solved_problems, last_checked) + solved_problems = await find_solved_atcoder(ctx, cf_handle) if(str(problem[0]) in solved_problems): return True else: @@ -405,8 +403,8 @@ async def gimme(ctx): cf_rating = await get_cf_user_rating(cf_handle) # getting the current rating of the user cf_rating = (cf_rating//100)*100 - last_checked, last_solved_problems = await get_last_solved_problems(ctx, 'codeforces') - solved_problems = await find_solved_codeforces(ctx, cf_handle, last_solved_problems, last_checked) + + solved_problems = await find_solved_codeforces(ctx, cf_handle) # giving points according to the difficulty of the question and rating of the user if(len(user_message) == 3): if(user_message[2] not in ['+100', '+200', '+300', '+400']): diff --git a/help.py b/help.py index 9361471..963d0f8 100644 --- a/help.py +++ b/help.py @@ -30,6 +30,7 @@ async def help(): [";graph rvp", "Shows the rating vs problem graph of your codeforces account"], [";graph pvt", "Shows the problem vs time graph of the your codeforces account"], [";performance ", "Shows the contest wise performance of the given handle on codeforces"], + [";chat", "Ask the bot anything and start a chat. Type ;end to end the chat."], ] content='' @@ -39,4 +40,4 @@ async def help(): title="Help", description=content, color=discord.Color.blue() - ) \ No newline at end of file + ) diff --git a/rating_roles.py b/rating_roles.py index 1816df8..318cbf6 100644 --- a/rating_roles.py +++ b/rating_roles.py @@ -11,85 +11,63 @@ async def remove_rating_roles(username): print("All roles removed") #This function will add the rating role to the user based on the rating -async def rating_role(id, rating,bot,channel): +async def rating_role(id, rating,bot,channel, msg): #Fetch the guild from the environment variable - GUILD=os.environ.get('GUILD') + GUILD=os.environ.get('GUILD') guild = await bot.fetch_guild(GUILD) #Get the user from the id and guild then fetch the username user = await guild.query_members(user_ids=[id]) username=user[0] - msg = await channel.send(f"Fetching rating changes") + if (msg is None): + msg = await channel.send(f"Fetching rating changes") #Now based on the rating add the role to the user if (rating < 800): await remove_rating_roles(username) return elif (rating < 1200): role = get(username.guild.roles, name="Newbie") - await remove_rating_roles(username) - await username.add_roles(role) - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - print("Role added") - return + if role is None: + role = await username.guild.create_role(name="Newbie") elif (rating < 1400): role = get(username.guild.roles, name="Pupil") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return + if role is None: + role = await username.guild.create_role(name="Pupil") elif (rating < 1600): role = get(username.guild.roles, name="Specialist") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return + if role is None: + role = await username.guild.create_role(name="Specialist") elif (rating < 1900): role = get(username.guild.roles, name="Expert") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return + if role is None: + role = await username.guild.create_role(name="Expert") elif (rating < 2100): role = get(username.guild.roles, name="Candidate Master") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return + if role is None: + role = await username.guild.create_role(name="Candidate Master") elif (rating < 2300): role = get(username.guild.roles, name="Master") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return + if role is None: + role = await username.guild.create_role(name="Master") elif (rating < 2400): role = get(username.guild.roles, name="International Master") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return + if role is None: + role = await username.guild.create_role(name="International Master") elif (rating < 2600): role = get(username.guild.roles, name="Grandmaster") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return + if role is None: + role = await username.guild.create_role(name="Grandmaster") elif (rating < 3000): role = get(username.guild.roles, name="International Grandmaster") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return + if role is None: + role = await username.guild.create_role(name="International Grandmaster") else: role = get(username.guild.roles, name="Legendary Grandmaster") - await remove_rating_roles(username) - await username.add_roles(role) - print("Role added") - await msg.edit(content=f"{username.mention} is a <@&{role.id}>") - return \ No newline at end of file + if role is None: + role = await username.guild.create_role(name="Legendary Grandmaster") + if (role in username.roles): + return msg + await remove_rating_roles(username) + await username.add_roles(role) + print("Role added") + await msg.edit(content=f"{username.mention} is a <@&{role.id}>") + return None \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 24489ef..16027b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ datetime firebase_admin quickchart.io nest_asyncio -table2ascii \ No newline at end of file +table2ascii +google-generativeai \ No newline at end of file