Skip to content

Commit

Permalink
Added feature to comment on last tweet; Added redis to store last twe…
Browse files Browse the repository at this point in the history
…et data for persistency; Improved logging; Code cleanup and restructuring

Signed-off-by: avaakash <[email protected]>
  • Loading branch information
avaakash committed Jan 8, 2022
1 parent 5792ac5 commit 18edff7
Show file tree
Hide file tree
Showing 18 changed files with 272 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
env
env.json
__pycache__
__pycache__*/
run_logs.txt
5 changes: 4 additions & 1 deletion env.json.sample
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"TWITTER_ACCESS_SECRET": "",
"TWITTER_USERNAME": "",
"TELEGRAM_SECRET": "",
"TELEGRAM_ALLOWED_IDS": "[]"
"TELEGRAM_ALLOWED_IDS": "[]",
"REDIS_HOST": "",
"REDIS_PORT": "",
"REDIS_PASSWORD": ""
}
Empty file added redis_py/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions redis_py/connect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Connect to redis server
"""
import redis

from settings import get_env

def connect():
"""Connect to redis server"""
environ_secrets = get_env()
host = environ_secrets["redis_host"]
port = environ_secrets["redis_port"]
password = environ_secrets["redis_password"]
if password:
return redis.Redis(host=host, port=port, password=password)
return redis.Redis(host=host, port=port)
33 changes: 33 additions & 0 deletions redis_py/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Database actions
"""

from .connect import connect

redis_connect = connect()

def set_tweet(tweet_id, tweet_text):
"""Set a tweet in redis"""
redis_connect.set("tweet_id", tweet_id)
redis_connect.set("tweet_text", tweet_text)

def get_tweet():
"""Get a tweet from redis"""
return (redis_connect.get("tweet_id").decode('UTF-8'),
redis_connect.get("tweet_text").decode('UTF-8'))

def delete_tweet():
"""Delete a tweet from redis"""
redis_connect.delete("tweet_id", "tweet_text")

def set_tweet_list(tweet_id):
"""Set a list of tweets in redis"""
redis_connect.lpush("tweet_list", tweet_id)

def get_latest_tweet_list():
"""Get the latest tweet from redis"""
return redis_connect.lrange("tweet_list", 0, -1)

def remove_last_tweet():
"""Remove the latest tweet from redis"""
redis_connect.lpop("tweet_list")
8 changes: 8 additions & 0 deletions redis_py/manifests/redis-conf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
data:
redis-config: |
maxmemory 2mb
maxmemory-policy allkeys-lru
35 changes: 35 additions & 0 deletions redis_py/manifests/redis-pod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: v1
kind: Pod
metadata:
name: redis
labels:
app: redis
spec:
containers:
- name: redis
image: redis:5.0.4
command:
- redis-server
- "/redis-master/redis.conf"
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: data
- mountPath: /redis-master
name: config
volumes:
- name: data
emptyDir: {}
- name: config
configMap:
name: redis-config
items:
- key: redis-config
path: redis.conf
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ backports.zoneinfo==0.2.1
cachetools==4.2.2
certifi==2021.10.8
charset-normalizer==2.0.10
Deprecated==1.2.13
idna==3.3
isort==5.10.1
lazy-object-proxy==1.7.1
mccabe==0.6.1
oauthlib==3.1.1
packaging==21.3
platformdirs==2.4.1
pylint==2.12.2
pyparsing==3.0.6
python-telegram-bot==13.10
pytz==2021.3
pytz-deprecation-shim==0.1.0.post0
redis==4.1.0
requests==2.27.1
requests-oauthlib==1.3.0
six==1.16.0
Expand Down
3 changes: 3 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ def get_env():
environ_secrets["twitter_username"] = env.get("TWITTER_USERNAME", "")
environ_secrets["telegram_secret"] = env.get("TELEGRAM_SECRET", "")
environ_secrets["telegram_allowed_ids"] = env.get("TELEGRAM_ALLOWED_IDS", "")
environ_secrets["redis_host"] = env.get("REDIS_HOST", "")
environ_secrets["redis_port"] = env.get("REDIS_PORT", "")
environ_secrets["redis_password"] = env.get("REDIS_PASSWORD", "")

return environ_secrets
3 changes: 1 addition & 2 deletions start.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# !/bin/bash
echo $HOME
source "$HOME/tweet-from-message/env/bin/activate"
echo Starting Bot...
python main.py > run_logs.txt &
python main.py > $HOME/tweet-from-message/run_logs.txt &
sleep 3
echo Bot started.
90 changes: 11 additions & 79 deletions telegram_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
import logging

# Importing telegram python module
from telegram import Update
from telegram.ext import Updater, CallbackContext, CommandHandler, MessageHandler, Filters
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters

# Importing app modules
from settings import get_env
from twitter import twitter, models
import validation as check
from twitter import models
import telegram_handler.utils as utils
import telegram_handler as handler

# Setting up the logger
logging.basicConfig(
Expand All @@ -19,93 +18,26 @@
# Global object to store last tweet data
tweet = models.Tweet(None, "", [])

def get_secret():
"""Fetches the secrets"""
environ_secrets = get_env()
return environ_secrets["telegram_secret"]

def update_message(update: Update, context: CallbackContext, command: str = None):
"""sends a message if an old message was edited"""
if command is None:
message = "You edited an old message."
else:
message = f"You edited an old message with the /{command} command."
return context.bot.send_message(
chat_id=update.effective_chat.id,
text=message
)

def start(update: Update, context: CallbackContext):
"""Start will listen to the /start command and send a message to the user"""
if update.message is not None:
context.bot.send_message(
chat_id=update.effective_chat.id,
text="I am the tweet bot. Send me a message and I will tweet it for you!"
)
else:
update_message(update, context, "start")

@check.restricted
def test(update: Update, context: CallbackContext):
"""Test function to test the bot"""
if update.message is not None:
print(check.check_tweet_length(update.message.text))
context.bot.send_message(
chat_id=update.effective_chat.id, text="Test\n" + update.message.text)
else:
update_message(update, context, "test")


@check.restricted
def get_tweet_text(update: Update, context: CallbackContext):
"""get_tweet_text will get the text from the message and tweet it"""
if update.message is not None:
print("Message: " + update.message.text)
if not check.check_tweet_length(update.message.text):
context.bot.send_message(chat_id=update.effective_chat.id, text="Tweet too long!")
return
try:
tweet.tweet_id, tweet.text = twitter.tweet(update.message.text)
message = "Tweeted: " + tweet.text
context.bot.send_message(chat_id=update.effective_chat.id, text=message)
except Exception as error:
print(error)
context.bot.send_message(
chat_id=update.effective_chat.id, text="Something went wrong!" + str(error))
else:
update_message(update, context)

def delete_tweet(update: Update, context: CallbackContext):
"""Delete a tweet command"""
if update.message is not None:
print(tweet.tweet_id, tweet.text, sep="\n")
if tweet.tweet_id is not None:
twitter.delete_tweet(tweet.tweet_id)
tweet.tweet_id = None
context.bot.send_message(chat_id=update.effective_chat.id, text="Tweet deleted!")
else:
context.bot.send_message(chat_id=update.effective_chat.id, text="No tweet to delete!")
else:
update_message(update, context, "delete")

def bot():
"""Main function to start the bot"""
# Setting up the updater
updater = Updater(token=get_secret(), use_context=True)
updater = Updater(token=utils.get_secret(), use_context=True)
# Setting up the dispatcher
dispatcher = updater.dispatcher

# Handlers
start_handler = CommandHandler('start', start)
message_handle = MessageHandler(Filters.text & ~Filters.command, get_tweet_text)
delete_handle = CommandHandler('delete', delete_tweet)
test_handle = CommandHandler('test', test)
start_handler = CommandHandler('start', handler.start)
message_handle = MessageHandler(Filters.text & ~Filters.command, handler.send_tweet)
delete_handle = CommandHandler('delete', handler.delete_tweet)
comment_handle = CommandHandler('comment', handler.comment_tweet, pass_args=True)
test_handle = CommandHandler('test', handler.test)

# Adding handlers to the dispatcher
dispatcher.add_handler(start_handler)
dispatcher.add_handler(message_handle)
dispatcher.add_handler(delete_handle)
dispatcher.add_handler(test_handle)
dispatcher.add_handler(comment_handle)

# Starting the bot
updater.start_polling()
5 changes: 5 additions & 0 deletions telegram_handler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
package: telegram_handler
"""
from .basic import *
from .tweet import *
27 changes: 27 additions & 0 deletions telegram_handler/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Basic handler functions
"""
from telegram import Update
from telegram.ext import CallbackContext

from .utils import update_message

def start(update: Update, context: CallbackContext):
"""Start will listen to the /start command and send a message to the user"""
if update.message is not None:
context.bot.send_message(
chat_id=update.effective_chat.id,
text="I am the tweet bot. Send me a message and I will tweet it for you!"
)
else:
update_message(update, context, "start")

def test(update: Update, context: CallbackContext):
"""Test function to test the bot"""
if update.message is not None:
context.bot.send_message(
chat_id=update.effective_chat.id,
text="This command does nothing, it is for testing purposes only."
)
else:
update_message(update, context, "test")
79 changes: 79 additions & 0 deletions telegram_handler/tweet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Handler function for tweets
"""
import logging

from telegram import Update
from telegram.ext import CallbackContext
import tweepy

from validation import restricted, check_tweet_length

from twitter import twitter

from redis_py import database

from .utils import update_message, get_tweet_url


@restricted
def send_tweet(update: Update, context: CallbackContext):
"""get_tweet_text will get the text from the message and tweet it"""
if update.message is not None:
logging.info("Message Received: %s", update.message.text)
if not check_tweet_length(update.message.text):
context.bot.send_message(chat_id=update.effective_chat.id, text="Tweet too long!")
return
try:
tweet_id, text = twitter.tweet(update.message.text)
logging.info("Tweet sent: %s", text)
database.set_tweet(tweet_id, text)
message = f"Tweeted: {text}\n{get_tweet_url(tweet_id)}"
context.bot.send_message(chat_id=update.effective_chat.id, text=message)
except tweepy.errors.BadRequest as error:
logging.error(str(error))
context.bot.send_message(
chat_id=update.effective_chat.id, text="Something went wrong!" + str(error))
else:
update_message(update, context)

def delete_tweet(update: Update, context: CallbackContext):
"""Delete a tweet command"""
if update.message is not None:
tweet_id, tweet_text = database.get_tweet()
logging.info("Tweet Data in Redis: %s - %s", tweet_id, tweet_text)
if tweet_id is not None:
twitter.delete_tweet(tweet_id)
database.delete_tweet()
logging.info("Tweet Deleted")
context.bot.send_message(chat_id=update.effective_chat.id, text="Tweet deleted!")
else:
context.bot.send_message(chat_id=update.effective_chat.id, text="No tweet to delete!")
else:
update_message(update, context, "delete")

def comment_tweet(update: Update, context: CallbackContext):
"""Tweets as a comment to the last tweet"""
if update.message is not None:
tweet_id, tweet_text = database.get_tweet()
logging.info("Tweet Data in Redis: %s - %s", tweet_id, tweet_text)
if tweet_id is not None:
try:
# Send tweet
tweet_message = " ".join(context.args)
reply_tweet_id, reply_text = twitter.reply_to_tweet(
tweet_message, tweet_id=tweet_id)
logging.info("Tweet reply sent: %s", reply_text)
# Setup message for bot
message = f"{reply_text}\n{get_tweet_url(reply_tweet_id)}"
message = message + f"\n\nIn reply to: {get_tweet_url(tweet_id)}\n{tweet_text}"
context.bot.send_message(chat_id=update.effective_chat.id, text=message)
except tweepy.errors.BadRequest as error:
logging.error(str(error))
context.bot.send_message(
chat_id=update.effective_chat.id, text=str(error))
else:
context.bot.send_message(
chat_id=update.effective_chat.id, text="No tweet to comment to!")
else:
update_message(update, context, "comment")
Loading

0 comments on commit 18edff7

Please sign in to comment.