Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 0 additions & 5 deletions source-code/chapter-3/python-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,3 @@ Refer to the [book repo](https://github.com/apress/practical-docker-with-python)

where `<token>` is the [Telegram Bot API](https://core.telegram.org/bots/api) token


### Credits

- [Ken](https://github.com/KenStoneBlue) for letting me know that I didn't add requirements!
- [Karan](https://github.com/mr-karan) for initial work
1 change: 1 addition & 0 deletions source-code/chapter-3/python-app/last_updated.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
232420721
2 changes: 1 addition & 1 deletion source-code/chapter-3/python-app/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


def get_updates(last_updated):
log.debug('Checking for requests, last updated passed is: {last_updated}')
log.info('Checking for requests, last updated passed is: {last_updated}')
sleep(UPDATE_PERIOD)
response = requests.get(f"{API_BASE}/getUpdates", params={'offset': last_updated+1})
json_response = FALSE_RESPONSE
Expand Down
2 changes: 1 addition & 1 deletion source-code/chapter-4/exercise-1/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
### README

This exercise contains the source code for the first exercise of chapter 4. At the start of the chapter, we introduced a simple Dockerfile that did not build due to syntax errors. Here, you’ll fix the Dockerfile and add some of the instructions that you learned about in this chapter.
This directory contains the source code for the first exercise of chapter 4. At the start of the chapter, we introduced a simple Dockerfile that did not build due to syntax errors. Here, you’ll fix the Dockerfile and add some of the instructions that you learned about in this chapter.
2 changes: 1 addition & 1 deletion source-code/chapter-4/exercise-2/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### README

This exercise contains the source code for the second exercise of chapter 4. In this exercise, you will build two Docker images
This directory contains the source code for the second exercise of chapter 4. In this exercise, you will build two Docker images

- Using the standard build process using python:3 as the base image (present in [docker-multi-stage/standard-build](docker-multi-stage/standard-build) directory.
- Using Multi-Stage builds (present [docker-multi-stage/multistage-build](docker-multi-stage/multistage-build) directory.
2 changes: 1 addition & 1 deletion source-code/chapter-4/exercise-3/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### README

This exercise contains the source code for the third exercise of chapter 4. In this exercise, we compose a Dockerfile for Newsbot and then use the Dockerfile to build a Docker image and run the container.
This directory contains the source code for the third exercise of chapter 4. In this exercise, we compose a Dockerfile for Newsbot and then use the Dockerfile to build a Docker image and run the container.


### Building the Docker image
Expand Down
2 changes: 1 addition & 1 deletion source-code/chapter-5/exercise-1/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
### README

This exercise contains the source code for the first exercise of chapter 5. In this exercise, we build an nginx Docker image with a Docker volume attached, which contains a custom nginx configuration. Toward the second part of the exercise, we will attach a bind mount and a volume containing a static web page and a custom nginx configuration. The intent of the exercise is help the readers understand how to leverage volumes and bind mounts to make local development easy.
This directory contains the source code for the first exercise of chapter 5. In this exercise, we build an nginx Docker image with a Docker volume attached, which contains a custom nginx configuration. Toward the second part of the exercise, we will attach a bind mount and a volume containing a static web page and a custom nginx configuration. The intent of the exercise is help the readers understand how to leverage volumes and bind mounts to make local development easy.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Empty file.
9 changes: 9 additions & 0 deletions source-code/chapter-5/exercise-2/newsbot/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3-alpine

RUN apk add gcc musl-dev python3-dev libffi-dev openssl-dev
WORKDIR /apps/subredditfetcher/
COPY . .

RUN pip install -r requirements.txt
VOLUME ["/data"]
CMD ["python", "newsbot.py"]
21 changes: 21 additions & 0 deletions source-code/chapter-5/exercise-2/newsbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# themnewsbot

Telegram bot which fetches Bot posts top submissions from a subreddit. NewsBot is used to demo a short, simple Python project in my book, [Practical Docker With Python](https://www.apress.com/gp/book/9781484237830).

Refer to the [book repo](https://github.com/apress/practical-docker-with-python) for other exercises using this code.

### Getting started

- Clone the repo or download the code
- Install the requirements with pip

pip3 install -r requirements.txt

- Set the environment variable `NBT_ACCESS_TOKEN` where the value is the Bot token generated using Telegram BotFather.
- See instructions on how to generate the token in Chapter 3 or [refer to this guide](https://core.telegram.org/bots/api#authorizing-your-bot)
- See this guide on [how to set environment variables](https://core.telegram.org/bots/api#authorizing-your-bot)
- Start the bot
python newsbot.py

where `<token>` is the [Telegram Bot API](https://core.telegram.org/bots/api) token

15 changes: 15 additions & 0 deletions source-code/chapter-5/exercise-2/newsbot/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
__author__ = 'Sathyajith'

from os import environ
from sys import exit
ERR_NO_SOURCE = 'No sources defined! Set a source using /source list, of, sub, reddits'
skip_list = []
sources_dict = {}
UPDATE_PERIOD = 1
FALSE_RESPONSE = {"ok": False}

BOT_KEY = environ.get('NBT_ACCESS_TOKEN')
if not BOT_KEY:
print("Telegram access token not set, exiting.")
exit(1)
API_BASE = f'https://api.telegram.org/bot{BOT_KEY}'
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from states import States, log
from telegram import handle_incoming_messages

from one_time import create_tables

def get_last_updated():
try:
Expand All @@ -12,13 +12,14 @@ def get_last_updated():
f.close()
except FileNotFoundError:
last_updated = 0
log.debug('Last updated id: {0}'.format(last_updated))
log.debug(f'Last updated id: {last_updated}')
return last_updated

if __name__ == '__main__':

try:
log.debug('Starting up')
log.info('Starting newsbot')
create_tables()
States.last_updated = get_last_updated()
while True:
handle_incoming_messages(States.last_updated)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@

from models import *


def create_tables():
db.connect()
db.create_tables([Source, Request, Message], True)
db.close()

if __name__ == '__main__':
create_tables()
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,30 @@
__author__ = 'Sathyajith'


def summarize(url):
log.info('Not yet implemented!')
return url


def get_latest_news(sub_reddits):
log.debug('Fetching news from reddit')
r = praw.Reddit(user_agent='SubReddit Newsfetcher Bot',
r = praw.Reddit(user_agent='NewsBot',
client_id='ralalsYuEJXKDg',
client_secret="16DD-6O7VVaYVMlkUPZWLhdluhU")
r.read_only = True

# Can change the subreddit or add more.
sub_reddits = clean_up_subreddits(sub_reddits)
log.info('Fetching subreddits: {0}'.format(sub_reddits))
log.info('Fetching subreddits: {sub_reddits}')
submissions = r.subreddit(sub_reddits).hot(limit=5)
submission_content = ''
try:
for post in submissions:
submission_content += summarize(post.title + ' - ' + post.url) + '\n\n'
submission_content += post.title + ' - ' + post.url + '\n\n'
except praw.errors.Forbidden:
log.debug('subreddit {0} is private'.format(sub_reddits))
log.debug(f'subreddit {sub_reddits} is private')
submission_content = "Sorry couldn't fetch; subreddit is private"
except praw.errors.InvalidSubreddit:
log.debug('Subreddit {} is invalid or doesn''t exist.'.format(sub_reddits))
log.debug(f'Subreddit {sub_reddits} is invalid or doesn''t exist')
submission_content = "Sorry couldn't fetch; subreddit doesn't seem to exist"
return submission_content


def clean_up_subreddits(sub_reddits):
log.debug('Got subreddits to clean: {0}'.format(sub_reddits))
log.debug(f'Got subreddits to clean: {sub_reddits}')
return sub_reddits.strip().replace(" ", "").replace(',', '+')
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
praw
peewee==2.10.2
praw==7.4.0
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
class States(object):
last_updated_id = ''

logging.basicConfig(level=logging.DEBUG,
logging.basicConfig(level=logging.INFO,
format='%(levelname)s: %(asctime)s - %(funcName)s - %(message)s')

log = logging.getLogger('nbt')
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@


def get_updates(last_updated):
log.debug('Checking for requests, last updated passed is: {}'.format(last_updated))
log.debug('Checking for requests, last updated passed is: {last_updated}')
sleep(UPDATE_PERIOD)
response = requests.get(API_BASE + BOT_KEY + '/getUpdates', params={'offset': last_updated+1})
response = requests.get(f"{API_BASE}/getUpdates", params={'offset': last_updated+1})
json_response = FALSE_RESPONSE
if response.status_code != 200:
# wait for a bit, try again
Expand All @@ -26,65 +26,68 @@ def get_updates(last_updated):
except ValueError:
sleep(UPDATE_PERIOD*20)
get_updates(last_updated)
log.info('received response: {}'.format(json_response))
log.info(f"received response: {json_response}")
return json_response


def post_message(chat_id, text):
log.info('posting {} to {}'.format(text, chat_id))
log.debug(f"posting {text} to {chat_id}")
payload = {'chat_id': chat_id, 'text': text}
requests.post(API_BASE + BOT_KEY + '/sendMessage', data=payload)
requests.post(f"{API_BASE}/sendMessage", data=payload)


def handle_incoming_messages(last_updated):
r = get_updates(last_updated)
split_chat_text = []
if r['ok']:
for req in r['result']:
chat_sender_id = req['message']['chat']['id']
if 'message' in req:
chat_sender_id = req['message']['chat']['id']
else:
chat_sender_id = req['edited_message']['chat']['id']
try:
chat_text = req['message']['text']
split_chat_text = chat_text.split()
except KeyError:
chat_text = ''
split_chat_text.append(chat_text)
log.debug('Looks like no chat text was detected... moving on')
try:

if 'message' in req:
person_id = req['message']['from']['id']
except KeyError:
pass
else:
person_id = req['edited_message']['from']['id']

log.info('Chat text received: {0}'.format(chat_text))
log.info(f"Chat text received: {chat_text}")
r = re.search('(source+)(.*)', chat_text)

if (r is not None and r.group(1) == 'source'):
if r.group(2):
sources_dict[person_id] = r.group(2)
log.info('Sources set for {0} to {1}'.format(person_id, sources_dict[person_id]))
log.info(f'Sources set for {person_id} to {sources_dict[person_id]}')
with db.atomic() as txn:
try:
sources = Source.create(person_id=person_id, fetch_from=sources_dict[person_id])
log.info('Inserted row id: {0}'.format(sources.person_id))
log.debug(f'Inserted row id: {sources.person_id}')
except peewee.IntegrityError:
sources = Source.update(fetch_from=sources_dict[person_id]).where(person_id == person_id)
rows_updated = sources.execute()
log.info('Updated {0} rows, query obj {1}'.format(rows_updated, sources))
log.info(f'Updated {rows_updated} rows')
txn.commit()

post_message(person_id, 'Sources set as {0}!'.format(r.group(2)))
else:
post_message(person_id, 'We need a comma separated list of subreddits! No subreddit, no news :-(')
if chat_text == '/stop':
log.debug('Added {0} to skip list'.format(chat_sender_id))
log.debug(f'Added {chat_sender_id} to skip list')
skip_list.append(chat_sender_id)
post_message(chat_sender_id, "Ok, we won't send you any more messages.")

if chat_text in ('/start', '/help'):
helptext = '''
Hi! This is a News Bot which fetches news from subreddits. Use "/source" to select a subreddit source.
Example "/source programming,games" fetches news from r/programming, r/games.
Use "/fetch for the bot to go ahead and fetch the news. At the moment, bot will fetch total of 5 posts from all sub reddits
I will have this configurable soon.
Use "/fetch for the bot to go ahead and fetch the news. At the moment, bot will fetch total of 5 posts from the selected subreddit.
'''
post_message(chat_sender_id, helptext)

Expand All @@ -102,5 +105,5 @@ def handle_incoming_messages(last_updated):
f.write(str(last_updated))
States.last_updated = last_updated
log.debug(
'Updated last_updated to {0}'.format(last_updated))
f'Updated last_updated to {last_updated}')
f.close()