Skip to content

bot: New headline fetching bot 'Newsboy' #760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions zulip_bots/zulip_bots/bots/newsboy/doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Newsboy

The Newsboy bot is a Zulip bot that fetches the top national news of particular country and sends them to the user.

To use the Newsboy bot, you can simply call it with `@<botname>` followed
by a command, like so:

```
@Newsboy help
```

## Setup

Before usage, you will need to configure the bot by putting the value of the `<api_key>` in the config file.
To do this, follow the given steps:

1. Go to [this]( https://newsdata.io/api-key) link after logging in at [newsdata.io]( https://newsdata.io).
2. Continue to get your `api_key`.
3. Open up `zulip_bots/bots/newsboy/newsboy.conf` in an editor and change the value of the `<api_key>` attributes to the corresponding noted values.

## Developer Notes

Be sure to add the additional commands and their descriptions to the `supported_commands`
list in `newsboy.py` so that they can be displayed with the other available commands using
`@<botname> list-commands`. Also modify the `test_list_commands_command` in
`test_newsboy.py`.

## Usage

`@newsboy list-commands` - This command gives a list of all available commands along with
short descriptions.

Example:
**`list-commands`**

![](assets/commands.png)

**`@bot_name help`**

![](assets/help.png)

**`get-top-news <country>`**

![](assets/news.png)
2 changes: 2 additions & 0 deletions zulip_bots/zulip_bots/bots/newsboy/newsboy.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[newsboy]
api_key = <api_key>
175 changes: 175 additions & 0 deletions zulip_bots/zulip_bots/bots/newsboy/newsboy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
from typing import Any, Dict, List, Optional

import requests

from zulip_bots.lib import BotHandler

INVALID_ARGUMENTS_ERROR_MESSAGE = "Invalid Arguments."
RESPONSE_ERROR_MESSAGE = "Invalid Response. Please check configuration and parameters."


class NewsboyHandler:
def initialize(self, bot_handler: BotHandler) -> None:
self.supported_commands = [
("help", "Get the bot usage information."),
("list-commands", "Get information about the commands supported by the bot."),
("get-top-news <country>", "Get top news of mention country e.g. (get-top-news us)."),
("list-countries", "Get the list of all supported countries."),
]
self.config_info = bot_handler.get_config_info("newsboy")
self.api_key = self.config_info["api_key"]

self.check_api_key(bot_handler)

def check_api_key(self, bot_handler: BotHandler) -> None:
test_query_response = requests.get(
"https://newsdata.io/api/1/news", params={"apikey": self.api_key}
)
try:
if test_query_response.json()["status"] == "error":
bot_handler.quit(
"Invalid Credentials. Please see doc.md to find out how to get them."
)
except AttributeError:
pass

def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:

content = message["content"].strip().split()

if content == []:
bot_handler.send_reply(message, "Empty Query")
return

content[0] = content[0].lower()

if content == ["help"]:
bot_reply = self.usage()
elif content == ["list-commands"]:
bot_reply = self.get_all_supported_commands()
elif content == ["list-countries"]:
bot_reply = self.get_all_supported_countries()
elif content[0] == "get-top-news":
bot_reply = self.get_news_response(content, self.api_key)
else:
bot_reply = "Command not supported"

bot_handler.send_reply(message, bot_reply)

def get_news_response(self, content: List[str], api_key: str) -> Optional[str]:
if len(content) < 2:
return INVALID_ARGUMENTS_ERROR_MESSAGE

base_url = "https://newsdata.io/api/1/news"
country = content[1]

params = {"apikey": api_key, "country": country, "q": "national", "language": "en"}
try:
response = requests.get(base_url, params=params).json()
data = response["results"] if response["status"] == "success" else None

for dt in data:
try:
title = dt["title"]
descr = dt["description"]
link = dt["link"]
image = dt["image_url"]
if (
title is not None
and descr is not None
and link is not None
and image is not None
):
return f"## [{title}]({link})\n\n > ***{descr}*** "
except Exception:
continue
except Exception as e:
return (RESPONSE_ERROR_MESSAGE, str(e))

def get_all_supported_countries(self) -> str:
countries = """
Argentina - ar \n
Australia - au \n
Austria - at \n
Belgium - be \n
Brazil - br \n
Bulgaria - bg \n
Canada - ca \n
China - cn \n
Colombia - co \n
Cuba - cu \n
Czech republic - cz \n
Egypt - eg \n
France - fr \n
Germany - de \n
Greece - gr \n
Hong kong - hk \n
Hungary - hu \n
India - in \n
Indonesia - id \n
Ireland - ie \n
Israel - il \n
Italy - it \n
Japan - jp \n
Kazakhstan - kz \n
Latvia - lv \n
Lebanon - lb \n
Lithuania - lt \n
Malaysia - my \n
Mexico - mx \n
Morocco - ma \n
Netherland - nl \n
New zealand - nz \n
Nigeria - ng \n
North korea - kp \n
Norway - no \n
Pakistan - pk \n
Peru - pe \n
Philippines - ph \n
Poland - pl \n
Portugal - pt \n
Romania - ro \n
Russia - ru \n
Saudi arabia - sa \n
Serbia - rs \n
Singapore - sg \n
Slovakia - sk \n
Slovenia - si \n
South africa - za \n
South korea - kr \n
Spain - es \n
Sweden - se \n
Switzerland - ch \n
Taiwan - tw \n
Thailand - th \n
Turkey - tr \n
Ukraine - ua \n
United arab emirates - ae \n
United kingdom - gb \n
United states of america - us \n
Venezuela - ve \n
"""
return countries

def get_all_supported_commands(self) -> str:
bot_response = "**Commands:** \n"
for index, (command, desc) in enumerate(self.supported_commands):
bot_response += f"{index + 1}. **{command}**: {desc}\n"

return bot_response

def usage(self) -> str:
help_content = """
## Newsboy
The Newsboy bot is a Zulip bot that fetches the top national news of particular country and
displays it to the user with headline and short description.

Use `list-commands` to get information about the supported commands.

Usage:
`get-top-news <country-abbreviation>` e.g. `get-top-news us`
"""
return help_content


handler_class = NewsboyHandler
59 changes: 59 additions & 0 deletions zulip_bots/zulip_bots/bots/newsboy/test_newsboy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import unittest
from unittest.mock import patch

from zulip_bots.test_lib import BotTestCase, DefaultTests

mock_config = {"api_key": "TEST"}


class Testnewsbot(BotTestCase, DefaultTests):
bot_name = "newsboy"

def test_bot_responds_to_empty_message(self) -> None:
with self.mock_config_info(mock_config), patch("requests.get"):
self.verify_reply("", "Empty Query")

def test_bot_usage(self) -> None:
with self.mock_config_info(mock_config), patch("requests.get"):
self.verify_reply(
"help",
"""
## Newsboy
The Newsboy bot is a Zulip bot that fetches the top national news of particular country and
displays it to the user with headline and short description.

Use `list-commands` to get information about the supported commands.

Usage:
`get-top-news <country-abbreviation>` e.g. `get-top-news us`
""",
)

def test_invalid_command(self) -> None:
with self.mock_config_info(mock_config), patch("requests.get"):
self.verify_reply("abcd", "Command not supported")

def test_list_commands_command(self) -> None:
expected_reply = (
"**Commands:** \n"
"1. **help**: Get the bot usage information.\n"
"2. **list-commands**: Get information about the commands supported by the bot.\n"
"3. **get-top-news <country>**: Get top news of mention country e.g. (get-top-news us).\n"
"4. **list-countries**: Get the list of all supported countries.\n"
)

with self.mock_config_info(mock_config), patch("requests.get"):
self.verify_reply("list-commands", expected_reply)

def test_command_invalid_arguments(self) -> None:
"""Add appropriate tests here for all additional commands with more than one arguments.
This ensures consistency."""

expected_error_response = "Invalid Arguments."

with self.mock_config_info(mock_config), patch("requests.get"):
self.verify_reply("get-top-news", expected_error_response)


if __name__ == "__main__":
unittest.main()