Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
54decd5
Initial take on sending quizzes
harshil21 Apr 14, 2020
8e8ae5a
Merge branch 'master' into quizizz-and-logging
harshil21 Apr 14, 2020
aef52ea
PEP-8
harshil21 Apr 14, 2020
b37e78b
Added the !r feature
harshil21 Apr 14, 2020
6cae79b
Added deep linking for /tell
harshil21 Apr 14, 2020
b06c877
implement logging module wide
harshil21 Apr 14, 2020
2a5754f
Also make it not reply if msg ends with `!r`
harshil21 Apr 14, 2020
16e5e41
Attempts to improve quiz.py
harshil21 Apr 16, 2020
3417f6a
Closed #20
harshil21 Apr 17, 2020
6263ac3
added type hints, updated docs
harshil21 Apr 17, 2020
7508213
Good morning quotes will only be sent at or before 11am
harshil21 Apr 18, 2020
380db4c
Closed #21 and fixed annotations in bar graph
harshil21 Apr 20, 2020
505717e
Closed #22 #23 #24
harshil21 Apr 21, 2020
ec3c65a
main.py refactored
harshil21 Apr 23, 2020
4d41663
Update README.md
harshil21 Apr 23, 2020
cfff704
Fixed bugs
harshil21 Apr 26, 2020
ab42f44
Fixed bug, prep for release
harshil21 Apr 26, 2020
11649fa
add GPL license
harshil21 Apr 27, 2020
3586f25
fix old data type in sql_table
harshil21 Apr 27, 2020
2b244db
Optimized for loop
harshil21 Apr 28, 2020
8acded5
Create pythonapp.yml
harshil21 May 2, 2020
04e2828
Merge pull request #26 from tmslads/tests-1
harshil21 May 2, 2020
57a1ecf
Update requirements.txt
harshil21 May 2, 2020
c478434
fix?
harshil21 May 2, 2020
f67011f
Update pythonapp.yml
harshil21 May 2, 2020
508d374
Update pythonapp.yml
harshil21 May 2, 2020
240bb42
Update requirements.txt
harshil21 May 2, 2020
96fb2fc
Merge branch 'tests' of https://github.com/tmslads/ShanispeakBot into…
harshil21 May 2, 2020
159357d
Update pythonapp.yml
harshil21 May 2, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: shanibot tester

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: windows-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install dependencies
run: |
python -W ignore -m pip install --upgrade pip
pip install flake8 pytest
pip install cython
pip install -r requirements.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
*.pyc
token.txt
creds/github_token.txt
sentence_tokenizer.pickle
db.sqlite3-shm
db.sqlite3-wal
*idea
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,22 @@ The following commands are currently available to use:
1. `/start` - Start the bot! (Highly recommended :P)
2. `/help` - General help and information about the bot.
3. `/facts` - Get a random fact from the internet.
4. `/8ball` - Magic 8 ball on messages. Usage: reply to a message with /8ball, or just type /8ball and then type the question.
4. `/8ball` - Magic 8 ball on messages. Usage: reply to a message with /8ball, or just type /8ball and
     then type the question.
5. `/snake` - A roast
6. `/tell` - Get more personal with the bot by providing a nickname and/or birthday, which it uses in regular conversations.
7. `/settings` - Modify the behaviour of the bot in groups and private chat. You can toggle on/off morning messages, and choose the probability with which the bot reacts to profanity and media.
8. More coming soon...
6. `/tell` - Get more personal with the bot by providing a nickname and/or birthday, which it uses in regular
    conversations.
7. `/settings` - Modify the behaviour of the bot in groups and private chat. You can toggle on/off morning messages,
      and choose the probability with which the bot reacts to profanity and media.
8. `/quizizz` - The bot will send you one physics quiz (MCQ) from the internet.
9. More coming soon...

### Behaviour in groups:

* If this bot is added to a group, it will try and behave like an actual user and reply to media sent (40% of the time by default) with some reactions.
* The bot will also check for profanity (20% of the time by default) and will suggest to stop using it.
* If any user replies to a message from the bot, it will reply to the user just like it would in private chat. The only difference is that here it does not learn from your replies.
* The bot will not reply back to your reply if you prepend or append your message with `!r`.
* The bot will reply to the user if he is mentioned (@) in the message.

### Behaviour in private chat:
Expand All @@ -30,4 +35,4 @@ The following commands are currently available to use:
* It still reacts to media files just like in groups.

### Inline mode:
This bot will give you clips from [Shanisirmodule](https://github.com/tmslads/Shanisirmodule), @ the bot and type a name of a clip and click on a result to send it to the target chat as a mp3 file.
This bot will give you clips from [Shanisirmodule](https://github.com/tmslads/Shanisirmodule) (private repo sorry!), @ the bot and type a name of a clip and click on a result to send it to the target chat as a mp3 file.
45 changes: 45 additions & 0 deletions bot_funcs/bday_wisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from datetime import datetime, date

from telegram.ext import CallbackContext

from constants import group_ids
from helpers.logger import logger
from online import gcalendar


def wish(context: CallbackContext) -> None:
"""Gets the next birthday from Google Calendar and wishes you if today is your birthday."""

gcalendar.main()
days_remaining, name = gcalendar.get_next_bday()

bday_msgs = [f"Happy birthday {name}! !🎉 I don't know why like, but I know you despise me with the burning "
f"passion of a thousand suns. I don't give a flux, like you say. I implore you to let go of "
f"hate and embrace love. Spend the rest of your days with love in your heart and faith in your "
f"soul. Life's cyclotron may sometimes send you tumbling around, but remember that it is "
f"necessary to do so in order to hit the targit. Negative emotions act as charge for the "
f"velocity selector of life. Remove them from your being and you shall not stray from the "
f"straight path. I wish you the best. May your jockeys be unpressed and your apertures small. "
f"Enjoy your 18th. Forget about coronabitch. Godspeed.",

f"Happy birthday {name}! I wish you the best of luck for life. Remember: You matter. Until you "
f"multiply yourself times the speed of light squared. Then you energy, like you say!🎉 What "
f"your going to do today like?",

f"Happy birthday {name}! !🎉 What your going to do today like?"]

_12B = group_ids['12b']

# Wishes from Google Calendar-
if days_remaining == 0:
msg = context.bot.send_message(chat_id=_12B, text=bday_msgs[0])
context.bot.pin_chat_message(chat_id=_12B, message_id=msg.message_id, disable_notification=True)
logger(message=f"Happy birthday message to {name} was just sent.")

now = str(date.today())
today = datetime.strptime(now, "%Y-%m-%d") # Parses today's date (time object) into datetime object
new_date = today.replace(year=today.year + 1)

gcalendar.CalendarEventManager(name=name).update_event(new_date) # Updates bday to next year

# TODO: Wishes from /tell birthday input-
231 changes: 231 additions & 0 deletions bot_funcs/conversation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import random as r
import re

import chatterbot
import emoji
from telegram import Update
from telegram.ext import CallbackContext
from textblob import TextBlob

from chatbot import get_tags, shanisirbot
from commands import prohibited
from helpers.db_connector import connection
from helpers.logger import logger
from helpers.namer import get_nick, get_chat_name

bot_response = None

rebukes = ["This is not the expected behaviour", "I don't want you to talk like that", "Expand your vocabulary now",
"Bad language is not allowed okay", "See this is not my policy", "This is not a fruitful conversation",
"This language is embarrassingassing to me like basically"]

responses1 = ["I am so sowry", "i don't want to talk like that", "it is embarrassing to me like basically",
"it's not to trouble you like you say", "go for the worksheet", "it's not that hard"]

responses2 = ["it will be fruitful", "you will benefit", "that is the expected behaviour",
"now you are on the track like", "now class is in the flow like", "don't press the jockey",
"aim to hit the tarjit"]

JJ_RB = ["like you say", "like you speak"] # For Adjectives or Adverbs


def shanifier(update: Update, context: CallbackContext, is_group: bool = False, the_id=None) -> None:
user = update.message.from_user
full_name = user.full_name
bot_username = context.bot.name # Bot username with @
today = update.message.date
org_text = update.message.text
chat_id = update.effective_chat.id

flag = 0 # To check if a modal is present in the sentence
lydcount = 0 # Counts the number of times "like you do" has been added
JJ_RBcount = 0 # Counts the number of times a phrase from JJ_RB has been added
temp = 0

name = get_nick(update, context)

add_update_records(update, context)

context.bot.send_chat_action(chat_id=chat_id, action='typing') # Sends 'typing...' status for 6 sec

if bot_username in org_text: # Sends response if bot is @'ed in group
msg_text = re.sub(rf"(\s*){bot_username}(\s*)", ' ', org_text) # Remove mention from text so response is better
the_id = update.message.message_id
else:
msg_text = org_text

reply_to, bot_msg, user_msg = get_response(update, text=msg_text)

if not is_group:
shanisirbot.learn_response(user_msg, bot_response)
chat_type = "(PRIVATE)"

else:
chat_type = f"(GROUP: {update.effective_chat.title})"

punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
bot_msg = ''.join(c for c in bot_msg if c not in punctuation)
blob = TextBlob(bot_msg)
cleaned = blob.words # Returns list with no punctuation marks

if len(cleaned) < 20:
lydlim = 1 # to limit the number of times we add
JJ_RBlim = 1 # lyd and JJ_RB
else:
lydlim = len(cleaned) // 20
JJ_RBlim = len(cleaned) // 20

for word, tag in blob.tags: # returns list of tuples which tells the POS
index = cleaned.index(word)
if index - temp < 7: # Do not add lad things too close to each other
continue

if tag == 'MD' and not flag: # Modal
cleaned.insert(index + 1, "(if the laws of physics allow it)")
flag = 1

if tag in ['JJ', 'JJR', 'JJS', 'RB', 'RBR', 'RBS'] and JJ_RBcount < JJ_RBlim: # Adjective or Adverb
cleaned.insert(index + 1, r.choice(JJ_RB))
JJ_RBcount += 1
temp = index

elif tag in ['VB', 'VBD', 'VBG', 'VBN', 'VBP', 'VBZ'] and lydcount < lydlim: # Verb
cleaned.insert(index + 1, "like you do")
lydcount += 1
temp = index

if r.choice([0, 1]):
if r.choice([0, 1]):
cleaned.append(r.choice(responses1))
else:
cleaned.append(r.choice(responses2))
cleaned.insert(0, name)
else:
cleaned.append(name)

if len(cleaned) < 5: # Will run if input is too short
cleaned.append(r.choice(["*draws perfect circle*", "*scratches nose*"]))

if re.search('when|time', ' '.join(cleaned), flags=re.IGNORECASE):
cleaned.insert(-1, 'decide a date')

for word in update.message.text:
if word in emoji.UNICODE_EMOJI: # Checks if emoji is present in message
cleaned.append(r.choice(list(emoji.UNICODE_EMOJI))) # Adds a random emoji

shanitext = ' '.join(cleaned)
shanitext = shanitext[0].upper() + shanitext[1:]

inp = f"UTC+0 {today} {chat_type} {reply_to} {full_name} ({user.username}) SAID: {msg_text}\n"
out = shanitext

context.bot.send_message(chat_id=chat_id, text=out, reply_to_message_id=the_id) # Sends message
logger(message=f"\nThe input by {full_name} to the bot in {get_chat_name(update)} was:\n{msg_text}"
f"\n\n\nThe output by the bot was:\n{out}")

with open("files/interactions.txt", "a") as f1:
f1.write(emoji.demojize(inp))
f1.write(f"BOT REPLY: {emoji.demojize(out)}\n\n")


def reply(update: Update, context: CallbackContext) -> None:
text = update.message.text
if update.message.reply_to_message.from_user.username == context.bot.username: # If the reply is to a bot:
if not (text.startswith('!r') or text.endswith('!r')): # Don't reply if this is prepended or appended.
logger(message=f"Bot received a reply from {update.effective_user.first_name} in "
f"{update.effective_chat.title}.")
shanifier(update, context, is_group=True, the_id=update.message.message_id)

elif context.bot.name in text:
shanifier(update, context, is_group=True, the_id=update.message.message_id)


def group(update: Update, context: CallbackContext) -> None:
"""Checks for profanity in messages and responds to that."""

chat_id = update.effective_chat.id
text = update.message.text

if any(bad_word in text.lower().split() for bad_word in prohibited):

query = f"SELECT PROFANE_PROB FROM CHAT_SETTINGS WHERE CHAT_ID={chat_id};"
true = connection(query, update)
logger(message=f"The query executed on the database was:\n{query}\nand the result was:\n{true=}")

false = 1 - true

if r.choices([0, 1], weights=[false, true])[0]: # Probabilities are 0.8 - False, 0.2 - True by default.
name = get_nick(update, context)

out = f"{r.choice(rebukes)} {name}"
context.bot.send_message(chat_id=chat_id, text=out,
reply_to_message_id=update.message.message_id) # Sends message
logger(message=f"{update.effective_user.first_name} used profane language in {get_chat_name(update)}."
f"\nThe rebuke by the bot was: '{out}'.")

elif context.bot.name in text:
shanifier(update, context, is_group=True, the_id=update.message.message_id)


def get_response(update: Update, text: str) -> (str, str, str):
global bot_response

if bot_response is None:
search_in_response_text = None
else:
search_in_response_text = get_tags(bot_response.text)

user_msg = chatterbot.conversation.Statement(text=text, search_text=get_tags(text), in_response_to=bot_response,
search_in_response_to=search_in_response_text)

# If the user's message is a reply to a message
if update.message.reply_to_message is not None:
reply_text = update.message.reply_to_message.text
if reply_text is not None:
bot_response = chatterbot.conversation.Statement(text=reply_text, search_text=get_tags(reply_text))
user_msg = chatterbot.conversation.Statement(text=text, search_text=get_tags(text),
in_response_to=bot_response,
search_in_response_to=get_tags(reply_text))

reply_to = f"(REPLY TO [{user_msg.in_response_to}])"

bot_response = shanisirbot.get_response(user_msg.text)

if hasattr(bot_response, 'text'):
bot_msg = bot_response.text
else:
bot_msg = 'Hello'

return reply_to, bot_msg, user_msg


def add_update_records(update: Update, context: CallbackContext) -> None:
chat_id = update.effective_chat.id
user = update.message.from_user
full_name = user.full_name
username = user.username

# Checks if your username or fullname or chat id is present in our records. If not, adds them.
if 'username' not in context.user_data:
context.user_data['username'] = [username]

elif username != context.user_data['username'][-1]:
context.user_data['username'].append(username)
logger(message=f"{full_name} changed their username to: {username}.")

if 'full_name' not in context.user_data:
context.user_data['full_name'] = [full_name]

elif full_name != context.user_data['full_name'][-1]:
context.user_data['full_name'].append(full_name)
logger(message=f"{username} changed their full name to: {full_name}.")

if "chat_ids" not in context.chat_data:
context.chat_data["chat_ids"] = [chat_id]
logger(message=f"{full_name} is talking to the bot for the first time.")

elif chat_id not in context.chat_data['chat_ids']: # Gets chat id of the user in which they have talked to the bot
context.chat_data['chat_ids'].append(chat_id)

context.dispatcher.persistence.update_user_data(user.id, context.user_data)
context.dispatcher.persistence.update_chat_data(chat_id, context.chat_data)
11 changes: 11 additions & 0 deletions bot_funcs/delete_pin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from telegram import Update
from telegram.ext import CallbackContext

from helpers.logger import logger


def de_pin(update: Update, context: CallbackContext) -> None:
"""Deletes pinned message service status from the bot."""

context.bot.delete_message(chat_id=update.effective_chat.id, message_id=update.message.message_id)
logger(message=f"Bot deleted a pinned service message from {update.effective_chat.title}.")
Loading