diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..5abdb30 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,23 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint $(git ls-files '*.py') diff --git a/commands.py b/commands.py index 130dc4e..075b1f1 100644 --- a/commands.py +++ b/commands.py @@ -2,9 +2,9 @@ default commands for the bot """ commands = { - 'start': 'Hello there {}! I am a bot sent by the heavens to be yours. Type /help and know what I can do for you.', - 'hi':'Hello there {}!', - 'hello':'Hello there {}!', - 'hey':'Hello there {}!', - 'help': 'Hey, {}. I see you sought my help.\n\nI know the following commands:\n/Hi, /Hey, /Hello - They\'re pretty much the same😬\n/Help - To know how I can help you.\n\n/q followed by your query - regarding IIT Mandi. \ne.g:\n/q What all facilities are available in campus?\n\nFor General questions you can just ask straight-away.\ne.g:\nWhat is your name\nTell me a joke\n\nYou can also use /u, /d and /n to upvote, downvote and get the next answer respectively. These three are only valid when you ask a query.' + "start": "Hello there {}! I am a bot sent by the heavens to be yours. Type /help and know what I can do for you.", + "hi": "Hello there {}!", + "hello": "Hello there {}!", + "hey": "Hello there {}!", + "help": "Hey, {}. I see you sought my help.\n\nI know the following commands:\n/Hi, /Hey, /Hello - They're pretty much the same😬\n/Help - To know how I can help you.\n\n/q followed by your query - regarding IIT Mandi. \ne.g:\n/q What all facilities are available in campus?\n\nFor General questions you can just ask straight-away.\ne.g:\nWhat is your name\nTell me a joke\n\nYou can also use /u, /d and /n to upvote, downvote and get the next answer respectively. These three are only valid when you ask a query.", } diff --git a/db.py b/db.py index 9505a53..211a0de 100755 --- a/db.py +++ b/db.py @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__) + def init_db(): """ Initializes the database client and returns a reference to the database. @@ -112,12 +113,17 @@ def update_vote_qna(vote_type: str, answer: str): """ try: qna = get_collection(DB, "qna") - where_clause = {'answers.answer': answer} - update_clause = {"$inc": {"answers.$.upvotes": 1}} if vote_type == "u" else {"$inc": {"answers.$.downvotes": 1}} + where_clause = {"answers.answer": answer} + update_clause = ( + {"$inc": {"answers.$.upvotes": 1}} + if vote_type == "u" + else {"$inc": {"answers.$.downvotes": 1}} + ) qna.update_one(where_clause, update_clause) except Exception as exception: logger.exception("Error updating vote count for answer: %s", answer) raise exception + global DB DB = init_db() diff --git a/requirements.txt b/requirements.txt index a7cf50c..c1ebea2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -pymongo==4.3.3 +pymongo==4.6.3 pyTelegramBotAPI==4.11.0 python-dotenv tensorflow_hub tensorflow -urllib3==1.26.6 \ No newline at end of file +urllib3==1.26.18 \ No newline at end of file diff --git a/resources/populateDB.py b/resources/populateDB.py index 3b81a80..b49b7ca 100644 --- a/resources/populateDB.py +++ b/resources/populateDB.py @@ -1,8 +1,10 @@ import sys -sys.path.append('..') + +sys.path.append("..") from db import * from pprint import pprint + def restructureQnA(): collection = get_collection(DB, "qna") all_documents = collection.find() @@ -18,7 +20,11 @@ def restructureQnA(): # if the question is not in the dictionary, add it if question not in question_answer_dict: - question_answer_dict[question] = {"_id": document["_id"], "question": question, "answers": [answer]} + question_answer_dict[question] = { + "_id": document["_id"], + "question": question, + "answers": [answer], + } else: # if the question is already in the dictionary, add the answer to the list of answers question_answer_dict[question]["answers"].append(answer) @@ -29,10 +35,15 @@ def restructureQnA(): # update the documents to add upvotes and downvotes to each answer for document in combined_documents: for answer_index, answer in enumerate(document["answers"]): - document["answers"][answer_index] = {"answer": answer, "upvotes": 0, "downvotes": 0} + document["answers"][answer_index] = { + "answer": answer, + "upvotes": 0, + "downvotes": 0, + } pprint(combined_documents) # insert the updated documents into the collection collection.delete_many({}) collection.insert_many(combined_documents) -restructureQnA() \ No newline at end of file + +restructureQnA() diff --git a/utils.py b/utils.py index 0aaf89b..cbeb2a3 100755 --- a/utils.py +++ b/utils.py @@ -23,12 +23,14 @@ user_specific_answers = {} + def get_chat_id(update): """ Returns the chatID from the update. """ return update.message.chat.id + def get_last_update(req, offset: int = None): """ Returns the latest update from the getUpdates bot API call. @@ -47,9 +49,11 @@ def get_last_update(req, offset: int = None): response = requests.get(updates_url, timeout=300).json() result = response.get("result", []) return result[-5:] - except (requests.exceptions.Timeout, - requests.exceptions.ConnectionError, - requests.exceptions.RequestException) as exception: + except ( + requests.exceptions.Timeout, + requests.exceptions.ConnectionError, + requests.exceptions.RequestException, + ) as exception: logger.error("Error fetching updates: %s", exception) return [] @@ -71,7 +75,7 @@ def address_query(update): Returns: None. """ - + command, message = parse_incoming_message(update) if command == "invalid": @@ -123,7 +127,9 @@ def parse_incoming_message(update): incoming_message = get_message_text(update).lower() command, incoming_message_bifurcated = bifurcate_incoming(incoming_message) - logger.info("Parsed incoming message: command='%s', message='%s'", command, incoming_message) + logger.info( + "Parsed incoming message: command='%s', message='%s'", command, incoming_message + ) return command, incoming_message_bifurcated @@ -146,12 +152,18 @@ def send_message(chat_id, message_text): """ params = {"chat_id": chat_id, "text": message_text} try: - response = requests.post(BASE_TELEGRAM_URL + "sendMessage", timeout=10, data=params) - logger.info("Message sent successfully to chat ID %s: %s", chat_id, params["text"]) + response = requests.post( + BASE_TELEGRAM_URL + "sendMessage", timeout=10, data=params + ) + logger.info( + "Message sent successfully to chat ID %s: %s", chat_id, params["text"] + ) return response - except (requests.exceptions.Timeout, - requests.exceptions.ConnectionError, - requests.exceptions.HTTPError) as exception: + except ( + requests.exceptions.Timeout, + requests.exceptions.ConnectionError, + requests.exceptions.HTTPError, + ) as exception: logger.exception("Error sending message to chat ID %s: %s", chat_id, exception) @@ -167,10 +179,13 @@ def bifurcate_incoming(incoming_message_command: str): command, incoming_message = None, None if incoming_message_command[0] != "/": return "smalltalk", incoming_message_command - + iterator = 1 command = "" - while iterator < len(incoming_message_command) and incoming_message_command[iterator] != " ": + while ( + iterator < len(incoming_message_command) + and incoming_message_command[iterator] != " " + ): command += incoming_message_command[iterator] iterator += 1 if iterator < len(incoming_message_command): @@ -184,6 +199,7 @@ def bifurcate_incoming(incoming_message_command: str): logger.exception("Error bifurcating incoming message: %s", exception) return "invalid", None + def vote_on_answer(chat_id: int, vote_type: str, update) -> None: """ Registers the user's vote on a specific answer and updates the database accordingly. @@ -200,18 +216,22 @@ def vote_on_answer(chat_id: int, vote_type: str, update) -> None: if user_id not in user_specific_answers: send_message(chat_id, "Your Vote could not be registered!") return - + voted_answer = user_specific_answers[user_id][-1] user_specific_answers[user_id][-1] = voted_answer - + try: db.update_vote_qna(vote_type, answer=voted_answer["answer"]) logger.info("Vote registered successfully.") send_message(chat_id, "Your Vote was registered!") except Exception as exception: logger.exception("Error registering vote: %s", exception) - send_message(chat_id, "An error occurred while registering your vote. Please try again later.") + send_message( + chat_id, + "An error occurred while registering your vote. Please try again later.", + ) + def give_one_answer(chat_id: int, answers): """ @@ -235,10 +255,12 @@ def give_one_answer(chat_id: int, answers): answers[i] = answers[i + 1] answers[-1] = selected_answer - message = f"{selected_answer['answer']}\n\n" \ - "___________________________________\n" \ - f"Upvotes: {selected_answer['upvotes']}, Downvotes: {selected_answer['downvotes']}\n\n" \ - "/u to upvote, /d to downvote, or /n for next answer." + message = ( + f"{selected_answer['answer']}\n\n" + "___________________________________\n" + f"Upvotes: {selected_answer['upvotes']}, Downvotes: {selected_answer['downvotes']}\n\n" + "/u to upvote, /d to downvote, or /n for next answer." + ) send_message(chat_id, message) @@ -248,7 +270,7 @@ def give_one_answer(chat_id: int, answers): def answer_query(incoming_message: str, update) -> None: """ Answer a user's query by finding relevant answers and displaying them. - + Args: incoming_message (str): The user's query. update (Telegram update): The Telegram update object. @@ -259,7 +281,9 @@ def answer_query(incoming_message: str, update) -> None: logger.info("Received answers for query: %s", incoming_message) if len(answers) == 0: message = "This question hasn't yet been answered. I will ask maintainers to answer it.\nTry a Google search till then:\n" - search_url = f"https://www.google.com/search?q={url.quote(incoming_message)}" + search_url = ( + f"https://www.google.com/search?q={url.quote(incoming_message)}" + ) send_message(get_chat_id(update), message + search_url) else: answers = give_one_answer(get_chat_id(update), answers) @@ -269,6 +293,7 @@ def answer_query(incoming_message: str, update) -> None: error_msg = "Oops! Something went wrong while answering your query. Please try again later." send_message(get_chat_id(update), error_msg) + def address_smalltalk(question: str) -> str: """ Generates a small talk response based on the given question. @@ -286,19 +311,20 @@ def address_smalltalk(question: str) -> str: if max(corr[0][1:]) < 0.4 or corr[0][1:].argmax() >= len(small_talk_answers): return "I didn't understand that :(" - + return random.choice(small_talk_answers[corr[0][1:].argmax()].split("&&")) except Exception as exception: logger.exception("Error while generating small talk response: %s", exception) return "Oops! Something went wrong while generating a response. Please try again later." + def find_answers(features): """ Find answers from the database for a given set of features. - + Args: features (List[float]): The features of the user's question. - + Returns: List[Dict[str, Union[str, int]]]: The list of matching answers from the database. """