Compare commits

...

10 commits

Author SHA1 Message Date
Tim Abbott
1cbaa43db5 github: Add a zulip/zulip style pull request template.
Hopefully, this will help improve the quality of new-contirbutor pull
requests to this project.
2024-11-25 17:54:02 -08:00
Niloth P
1d37ed2217 bots: Rename BotHandler to AbstractBotHandler.
Fixes #690.
2024-11-21 11:33:05 -08:00
Niloth P
fb73220438 rss-bot: Handle feed entries that lack a title field.
Fixes #836.
2024-11-01 16:19:35 -07:00
Aman Agrawal
c04a17255b litellm: Add a tool to summarize a topic. 2024-10-30 13:44:04 -07:00
Anders Kaseorg
2675715ecb zulip: Replace deprecated distro.linux_distribution.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2024-10-24 08:44:01 -07:00
Aman Agrawal
48c6e404de
salesforce: Fix linting error. (#835) 2024-10-24 12:24:59 +05:30
Alex Vandiver
75bea9f96d zmirror: Drop empty zulip messages.
Zulip will reject sending these, so there is no need to construct them.
2024-08-13 12:05:57 -07:00
Alex Vandiver
43a4900e1f zephyr: Delete DMs after they are received. 2024-08-13 10:39:27 -07:00
Tim Abbott
e9d8ef3b27 mailmap: Canonicalize acrefoot. 2024-07-25 10:45:36 -07:00
Tim Abbott
3bd99978ec mailmap: Canonicalize several contributors. 2024-07-25 10:42:37 -07:00
51 changed files with 395 additions and 152 deletions

43
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,43 @@
<!-- Describe your pull request here.-->
Fixes: <!-- Issue link, or clear description.-->
<!-- If the PR changes output, always include screenshots or code blocks to demonstrate your changes. If it seems helpful, add a screen capture of the new functionality as well.
Tooling tips: https://zulip.readthedocs.io/en/latest/tutorials/screenshot-and-gif-software.html
-->
**Screenshots and how this was tested:**
<details>
<summary>Self-review checklist</summary>
<!-- Prior to submitting a PR, follow our step-by-step guide to review your own code:
https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code -->
<!-- Once you create the PR, check off all the steps below that you have completed.
If any of these steps are not relevant or you have not completed, leave them unchecked.-->
- [ ] [Self-reviewed](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code) the changes for clarity and maintainability
(variable names, code reuse, readability, etc.).
Communicate decisions, questions, and potential concerns.
- [ ] Explains differences from previous plans (e.g., issue description).
- [ ] Highlights technical choices and bugs encountered.
- [ ] Calls out remaining decisions and concerns.
- [ ] Automated tests verify logic where appropriate.
Individual commits are ready for review (see [commit discipline](https://zulip.readthedocs.io/en/latest/contributing/commit-discipline.html)).
- [ ] Each commit is a coherent idea.
- [ ] Commit message(s) explain reasoning and motivation for changes.
Completed manual review and testing of the following:
- [ ] Visual appearance of the changes.
- [ ] Responsiveness and internationalization.
- [ ] Strings and tooltips.
- [ ] End-to-end functionality of buttons, interactions and flows.
- [ ] Corner cases, error conditions, and easily imagined bugs.
</details>

View file

@ -1,16 +1,26 @@
acrefoot <acrefoot@zulip.com> <acrefoot@humbughq.com>
Aman Agrawal <f2016561@pilani.bits-pilani.ac.in>
Anders Kaseorg <anders@zulip.com> <anders@zulipchat.com>
Anders Kaseorg <anders@zulip.com> <andersk@mit.edu>
Jessica McKellar <jesstess@zulip.com> <jesstess@humbughq.com>
Jessica McKellar <jesstess@mit.edu> <jesstess@humbughq.com>
Jessica McKellar <jesstess@mit.edu> <jesstess@zulip.com>
Kevin Mehall <km@kevinmehall.net> <kevin@humbughq.com>
Kevin Mehall <km@kevinmehall.net> <kevin@zulip.com>
Luke Faraone <lfaraone@zulip.com> <lfaraone@humbughq.com>
Reid Barton <rwbarton@gmail.com> <rwbarton@humbughq.com>
Rein Zustand (rht) <rhtbot@protonmail.com>
Rishi Gupta <rishig@zulip.com> <rishig@zulipchat.com>
Scott Feeney <scott@oceanbase.org> <scott@humbughq.com>
Scott Feeney <scott@oceanbase.org> <scott@zulip.com>
Steve Howell <showell@zulip.com> <showell30@yahoo.com>
Steve Howell <showell@zulip.com> <showell@zulipchat.com>
Steve Howell <showell@zulip.com> <steve@zulip.com>
Tim Abbott <tabbott@zulip.com> <tabbott@humbughq.com>
Tim Abbott <tabbott@zulip.com> <tabbott@mit.edu>
Tim Abbott <tabbott@zulip.com> <tabbott@zulipchat.com>
umkay <ukhan@zulipchat.com> <umaimah.k@gmail.com>
Waseem Daher <wdaher@zulip.com> <wdaher@dropbox.com>
Waseem Daher <wdaher@zulip.com> <wdaher@humbughq.com>
Zev Benjamin <zev@zulip.com> <zev@dropbox.com>
Zev Benjamin <zev@zulip.com> <zev@humbughq.com>
Zev Benjamin <zev@zulip.com> <zev@mit.edu>

View file

@ -2,7 +2,7 @@
from typing import Any, Dict
import packaged_helloworld
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
__version__ = packaged_helloworld.__version__
@ -17,7 +17,7 @@ class HelloWorldHandler:
sophisticated, bots that can be installed separately.
"""
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
content = "beep boop"
bot_handler.send_reply(message, content)

View file

@ -0,0 +1,36 @@
# Summarize topic
Generate a short summary of the last 100 messages in the provided topic URL.
### API Keys
For testing you need access token from
https://huggingface.co/settings/tokens (or set the correct env
variable with the access token if using a different model)
In `~/.zuliprc` add a section named `litellm` and set the api key for
the model you are trying to use. For example:
```
[litellm]
HUGGINGFACE_API_KEY=YOUR_API_KEY
```
### Setup
```bash
$ pip install -r zulip/integrations/litellm/requirements.txt
```
Just run `zulip/integrations/litellm/summarize-topic` to generate
sample summary.
```bash
$ zulip/integrations/litellm/summarize-topic --help
usage: summarize-topic [-h] [--url URL] [--model MODEL]
options:
-h, --help show this help message and exit
--url URL The URL to fetch content from
--model MODEL The model name to use for summarization
```

View file

@ -0,0 +1,2 @@
zulip
litellm

View file

@ -0,0 +1,130 @@
#!/usr/bin/env python3
import argparse
import os
import sys
import urllib.parse
from configparser import ConfigParser
from litellm import completion # type: ignore[import-not-found]
import zulip
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--url",
type=str,
help="The URL to fetch content from",
default="https://chat.zulip.org/#narrow/stream/101-design/topic/more.20user.20indicators",
)
parser.add_argument(
"--model",
type=str,
help="The model name to use for summarization",
default="huggingface/meta-llama/Meta-Llama-3-8B-Instruct",
)
parser.add_argument(
"--max-tokens",
type=int,
help="The maximum tokens permitted in the response",
default=100,
)
parser.add_argument(
"--max-messages",
type=int,
help="The maximum number of messages fetched from the server",
default=100,
)
parser.add_argument(
"--verbose",
type=bool,
help="Print verbose debugging output",
default=False,
)
args = parser.parse_args()
config_file = zulip.get_default_config_filename()
if not config_file:
print("Could not find the Zulip configuration file. Please read the provided README.")
sys.exit()
client = zulip.Client(config_file=config_file)
config = ConfigParser()
# Make config parser case sensitive otherwise API keys will be lowercased
# which is not supported by litellm.
# https://docs.python.org/3/library/configparser.html#configparser.ConfigParser.optionxform
config.optionxform = str # type: ignore[assignment, method-assign]
with open(config_file) as f:
config.read_file(f, config_file)
# Set all the keys in `litellm` as environment variables.
for key in config["litellm"]:
if args.verbose:
print("Setting key:", key)
os.environ[key] = config["litellm"][key]
url = args.url
model = args.model
base_url, narrow_hash = url.split("#")
narrow_hash_terms = narrow_hash.split("/")
channel = narrow_hash_terms[2].split("-")[1]
topic = narrow_hash_terms[4]
channel = urllib.parse.unquote(channel.replace(".", "%"))
topic = urllib.parse.unquote(topic.replace(".", "%"))
narrow = [
{"operator": "channel", "operand": channel},
{"operator": "topic", "operand": topic},
]
request = {
"anchor": "newest",
"num_before": args.max_messages,
"num_after": 0,
"narrow": narrow,
# Fetch raw Markdown, not HTML
"apply_markdown": False,
}
result = client.get_messages(request)
if result["result"] == "error":
print("Failed fetching message history", result)
sys.exit(1)
messages = result["messages"]
if len(messages) == 0:
print("No messages in conversation to summarize")
sys.exit(0)
formatted_messages = [
{"content": f"{message['sender_full_name']}: {message['content']}", "role": "user"}
for message in messages
]
# Provide a instruction if using an `Instruct` model.
if "Instruct" in model:
formatted_messages.append(
{
"content": """
Summarize the above content within 90 words.
""",
"role": "user",
}
)
# Send formatted messages to the LLM model for summarization
response = completion(
max_tokens=args.max_tokens,
model=model,
messages=formatted_messages,
)
print("Summarized conversation URL:", url)
print(
f"Used {response['usage']['total_tokens']} tokens to summarize {len(formatted_messages)} Zulip messages."
)
print()
print(response["choices"][0]["message"]["content"])

View file

@ -177,7 +177,8 @@ def send_zulip(entry: Any, feed_name: str) -> Dict[str, Any]:
if opts.unwrap:
body = unwrap_text(body)
content = f"**[{entry.title}]({entry.link})**\n{strip_tags(body)}\n{entry.link}"
title = f"**[{entry.title}]({entry.link})**\n" if hasattr(entry, "title") else ""
content = f"{title}{strip_tags(body)}\n{entry.link}"
if opts.math:
content = content.replace("$", "$$")

View file

@ -300,6 +300,11 @@ if "error" in res["result"]:
logging.error(res["msg"])
print_status_and_exit(1)
messages = [event["message"] for event in res["events"]]
for m in messages:
if m.get("stream_id") is None:
# Non-stream messages can't have a retention policy, so clean
# them up so they don't pile up
zulip_client.delete_message(m["id"])
logger.info("Finished receiving Zulip messages!")
receive_zephyrs()

View file

@ -554,6 +554,8 @@ def send_zulip_worker(zulip_queue: "Queue[ZephyrDict]", zulip_client: zulip.Clie
while True:
zeph = zulip_queue.get()
try:
if zeph["content"] == "":
continue
res = send_zulip(zulip_client, zeph)
if res.get("result") != "success":
logger.error("Error relaying zephyr:\n%s\n%s", zeph, res)

View file

@ -548,7 +548,8 @@ class Client:
pass
if vendor == "Linux":
vendor, vendor_version, dummy = distro.linux_distribution()
vendor = distro.name()
vendor_version = distro.version()
elif vendor == "Windows":
vendor_version = platform.win32_ver()[1]
elif vendor == "Darwin":

View file

@ -4,11 +4,11 @@ from typing import Any, Dict, List
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class BaremetricsHandler:
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("baremetrics")
self.api_key = self.config_info["api_key"]
@ -38,7 +38,7 @@ class BaremetricsHandler:
self.check_api_key(bot_handler)
def check_api_key(self, bot_handler: BotHandler) -> None:
def check_api_key(self, bot_handler: AbstractBotHandler) -> None:
url = "https://api.baremetrics.com/v1/account"
test_query_response = requests.get(url, headers=self.auth_header)
test_query_data = test_query_response.json()
@ -57,7 +57,7 @@ class BaremetricsHandler:
Version 1.0
"""
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
content = message["content"].strip().split()
if content == []:

View file

@ -4,7 +4,7 @@ from typing import Dict
import requests
from requests.exceptions import ConnectionError
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
help_message = """
You can add datapoints towards your beeminder goals \
@ -80,7 +80,7 @@ class BeeminderHandler:
towards their beeminder goals via zulip
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("beeminder")
# Check for valid auth_token
auth_token = self.config_info["auth_token"]
@ -96,7 +96,7 @@ class BeeminderHandler:
def usage(self) -> str:
return "This plugin allows users to add datapoints towards their Beeminder goals"
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
response = get_beeminder_response(message["content"], self.config_info)
bot_handler.send_reply(message, response)

View file

@ -5,7 +5,7 @@ from typing import Dict, Optional
import chess
import chess.engine
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
START_REGEX = re.compile("start with other user$")
START_COMPUTER_REGEX = re.compile("start as (?P<user_color>white|black) with computer")
@ -24,7 +24,7 @@ class ChessHandler:
"Stockfish program on this computer."
)
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("chess")
try:
@ -36,7 +36,7 @@ class ChessHandler:
# runner is testing or knows they won't be using an engine.
print("That Stockfish doesn't exist. Continuing.")
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
content = message["content"]
if content == "":
@ -76,7 +76,7 @@ class ChessHandler:
elif resign_regex_match:
self.resign(message, bot_handler, last_fen)
def start(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def start(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
"""Starts a game with another user, with the current user as white.
Replies to the bot handler.
@ -93,7 +93,7 @@ class ChessHandler:
bot_handler.storage.put("last_fen", new_board.fen())
def start_computer(
self, message: Dict[str, str], bot_handler: BotHandler, is_white_user: bool
self, message: Dict[str, str], bot_handler: AbstractBotHandler, is_white_user: bool
) -> None:
"""Starts a game with the computer. Replies to the bot handler.
@ -123,7 +123,7 @@ class ChessHandler:
)
def validate_board(
self, message: Dict[str, str], bot_handler: BotHandler, fen: str
self, message: Dict[str, str], bot_handler: AbstractBotHandler, fen: str
) -> Optional[chess.Board]:
"""Validates a board based on its FEN string. Replies to the bot
handler if there is an error with the board.
@ -147,7 +147,7 @@ class ChessHandler:
def validate_move(
self,
message: Dict[str, str],
bot_handler: BotHandler,
bot_handler: AbstractBotHandler,
last_board: chess.Board,
move_san: str,
is_computer: object,
@ -180,7 +180,7 @@ class ChessHandler:
return move
def check_game_over(
self, message: Dict[str, str], bot_handler: BotHandler, new_board: chess.Board
self, message: Dict[str, str], bot_handler: AbstractBotHandler, new_board: chess.Board
) -> bool:
"""Checks if a game is over due to
- checkmate,
@ -224,7 +224,7 @@ class ChessHandler:
return False
def move(
self, message: Dict[str, str], bot_handler: BotHandler, last_fen: str, move_san: str
self, message: Dict[str, str], bot_handler: AbstractBotHandler, last_fen: str, move_san: str
) -> None:
"""Makes a move for a user in a game with another user. Replies to
the bot handler.
@ -256,7 +256,7 @@ class ChessHandler:
bot_handler.storage.put("last_fen", new_board.fen())
def move_computer(
self, message: Dict[str, str], bot_handler: BotHandler, last_fen: str, move_san: str
self, message: Dict[str, str], bot_handler: AbstractBotHandler, last_fen: str, move_san: str
) -> None:
"""Preforms a move for a user in a game with the computer and then
makes the computer's move. Replies to the bot handler. Unlike `move`,
@ -306,7 +306,7 @@ class ChessHandler:
bot_handler.storage.put("last_fen", new_board_after_computer_move.fen())
def move_computer_first(
self, message: Dict[str, str], bot_handler: BotHandler, last_fen: str
self, message: Dict[str, str], bot_handler: AbstractBotHandler, last_fen: str
) -> None:
"""Preforms a move for the computer without having the user go first in
a game with the computer. Replies to the bot handler. Like
@ -345,7 +345,9 @@ class ChessHandler:
# `bot_handler`'s `storage` only accepts `str` values.
bot_handler.storage.put("is_with_computer", str(True))
def resign(self, message: Dict[str, str], bot_handler: BotHandler, last_fen: str) -> None:
def resign(
self, message: Dict[str, str], bot_handler: AbstractBotHandler, last_fen: str
) -> None:
"""Resigns the game for the current player.
Parameters:

View file

@ -5,7 +5,7 @@ from math import floor, log10
from typing import Any, Dict, List
from zulip_bots.bots.converter import utils
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
def is_float(value: Any) -> bool:
@ -49,12 +49,12 @@ class ConverterHandler:
all supported units.
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
bot_response = get_bot_converter_response(message, bot_handler)
bot_handler.send_reply(message, bot_response)
def get_bot_converter_response(message: Dict[str, str], bot_handler: BotHandler) -> str:
def get_bot_converter_response(message: Dict[str, str], bot_handler: AbstractBotHandler) -> str:
content = message["content"]
words = content.lower().split()

View file

@ -6,7 +6,7 @@ from typing import Dict
import html2text
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class DefineHandler:
@ -27,7 +27,7 @@ class DefineHandler:
messages with @mention-bot.
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
original_content = message["content"].strip()
bot_response = self.get_bot_define_response(original_content)

View file

@ -5,7 +5,7 @@ from typing import Dict
import apiai
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
help_message = """DialogFlow bot
This bot will interact with dialogflow bots.
@ -47,7 +47,7 @@ class DialogFlowHandler:
DialogFlow bots to zulip
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("dialogflow")
def usage(self) -> str:
@ -56,7 +56,7 @@ class DialogFlowHandler:
DialogFlow bots to zulip
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
result = get_bot_result(message["content"], self.config_info, message["sender_id"])
bot_handler.send_reply(message, result)

View file

@ -3,7 +3,7 @@ from typing import Any, Dict, List, Tuple
from dropbox import Dropbox
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
URL = "[{name}](https://www.dropbox.com/home{path})"
@ -14,7 +14,7 @@ class DropboxHandler:
between zulip and your dropbox account.
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("dropbox_share")
self.ACCESS_TOKEN = self.config_info.get("access_token")
self.client = Dropbox(self.ACCESS_TOKEN)
@ -22,7 +22,7 @@ class DropboxHandler:
def usage(self) -> str:
return get_help()
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
command = message["content"]
if command == "":
command = "help"

View file

@ -1,6 +1,6 @@
from typing import Dict
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
def encrypt(text: str) -> str:
@ -34,7 +34,7 @@ class EncryptHandler:
Feeding encrypted messages into the bot decrypts them.
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
bot_response = self.get_bot_encrypt_response(message)
bot_handler.send_reply(message, bot_response)

View file

@ -2,7 +2,7 @@ import os
from pathlib import Path
from typing import Dict
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class FileUploaderHandler:
@ -13,7 +13,7 @@ class FileUploaderHandler:
"\n- @uploader help : Display help message"
)
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
help_str = (
"Use this bot with any of the following commands:"
"\n* `@uploader <local_file_path>` : Upload a file, where `<local_file_path>` is the path to the file"

View file

@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple
import requests
from requests.exceptions import ConnectionError
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
USERS_LIST_URL = "https://api.flock.co/v1/roster.listContacts"
SEND_MESSAGE_URL = "https://api.flock.co/v1/chat.sendMessage"
@ -97,14 +97,14 @@ class FlockHandler:
flock user without having to leave Zulip.
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("flock")
def usage(self) -> str:
return """Hello from Flock Bot. You can send messages to any Flock user
right from Zulip."""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
response = get_flock_bot_response(message["content"], self.config_info)
bot_handler.send_reply(message, response)

View file

@ -1,7 +1,7 @@
# See readme.md for instructions on running this code.
from typing import Dict
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class FollowupHandler:
@ -26,11 +26,11 @@ class FollowupHandler:
called "followup" that your API user can send to.
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("followup", optional=False)
self.stream = self.config_info.get("stream", "followup")
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
if message["content"] == "":
bot_response = (
"Please specify the message you want to send to followup stream after @mention-bot"

View file

@ -3,7 +3,7 @@ from typing import Any, Dict
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class FrontHandler:
@ -24,7 +24,7 @@ class FrontHandler:
Front Bot, `front.conf` must be set up. See `doc.md` for more details.
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
config = bot_handler.get_config_info("front")
api_key = config.get("api_key")
if not api_key:
@ -32,14 +32,14 @@ class FrontHandler:
self.auth = "Bearer " + api_key
def help(self, bot_handler: BotHandler) -> str:
def help(self, bot_handler: AbstractBotHandler) -> str:
response = ""
for command, description in self.COMMANDS:
response += f"`{command}` {description}\n"
return response
def archive(self, bot_handler: BotHandler) -> str:
def archive(self, bot_handler: AbstractBotHandler) -> str:
response = requests.patch(
self.FRONT_API.format(self.conversation_id),
headers={"Authorization": self.auth},
@ -51,7 +51,7 @@ class FrontHandler:
return "Conversation was archived."
def delete(self, bot_handler: BotHandler) -> str:
def delete(self, bot_handler: AbstractBotHandler) -> str:
response = requests.patch(
self.FRONT_API.format(self.conversation_id),
headers={"Authorization": self.auth},
@ -63,7 +63,7 @@ class FrontHandler:
return "Conversation was deleted."
def spam(self, bot_handler: BotHandler) -> str:
def spam(self, bot_handler: AbstractBotHandler) -> str:
response = requests.patch(
self.FRONT_API.format(self.conversation_id),
headers={"Authorization": self.auth},
@ -75,7 +75,7 @@ class FrontHandler:
return "Conversation was marked as spam."
def restore(self, bot_handler: BotHandler) -> str:
def restore(self, bot_handler: AbstractBotHandler) -> str:
response = requests.patch(
self.FRONT_API.format(self.conversation_id),
headers={"Authorization": self.auth},
@ -87,7 +87,7 @@ class FrontHandler:
return "Conversation was restored."
def comment(self, bot_handler: BotHandler, **kwargs: Any) -> str:
def comment(self, bot_handler: AbstractBotHandler, **kwargs: Any) -> str:
response = requests.post(
self.FRONT_API.format(self.conversation_id) + "/comments",
headers={"Authorization": self.auth},
@ -99,7 +99,7 @@ class FrontHandler:
return "Comment was sent."
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
command = message["content"]
result = re.search(self.CNV_ID_REGEXP, message["subject"])

View file

@ -5,7 +5,7 @@ import requests
from requests.exceptions import ConnectionError, HTTPError
from zulip_bots.custom_exceptions import ConfigValidationError
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
GIPHY_TRANSLATE_API = "http://api.giphy.com/v1/gifs/translate"
GIPHY_RANDOM_API = "http://api.giphy.com/v1/gifs/random"
@ -44,10 +44,10 @@ class GiphyHandler:
)
raise ConfigValidationError(error_message) from e
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("giphy")
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
bot_response = get_bot_giphy_response(message, bot_handler, self.config_info)
bot_handler.send_reply(message, bot_response)
@ -82,7 +82,7 @@ def get_url_gif_giphy(keyword: str, api_key: str) -> Union[int, str]:
def get_bot_giphy_response(
message: Dict[str, str], bot_handler: BotHandler, config_info: Dict[str, str]
message: Dict[str, str], bot_handler: AbstractBotHandler, config_info: Dict[str, str]
) -> str:
# Each exception has a specific reply should "gif_url" return a number.
# The bot will post the appropriate message for the error.

View file

@ -4,7 +4,7 @@ from typing import Any, Dict, Tuple, Union
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class GithubHandler:
@ -16,7 +16,7 @@ class GithubHandler:
GITHUB_ISSUE_URL_TEMPLATE = "https://api.github.com/repos/{owner}/{repo}/issues/{id}"
HANDLE_MESSAGE_REGEX = re.compile(r"(?:([\w-]+)\/)?([\w-]+)?#(\d+)")
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("github_detail", optional=True)
self.owner = self.config_info.get("owner", False)
self.repo = self.config_info.get("repo", False)
@ -73,7 +73,7 @@ class GithubHandler:
repo = self.repo
return (owner, repo)
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
# Send help message
if message["content"] == "help":
bot_handler.send_reply(message, self.usage())

View file

@ -5,7 +5,7 @@ from typing import Dict, List
import requests
from bs4 import BeautifulSoup, Tag
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
def google_search(keywords: str) -> List[Dict[str, str]]:
@ -83,7 +83,7 @@ class GoogleSearchHandler:
@mentioned-bot.
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
original_content = message["content"]
result = get_google_result(original_content)
bot_handler.send_reply(message, result)

View file

@ -2,7 +2,7 @@
from typing import Any, Dict
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class HelloWorldHandler:
@ -15,7 +15,7 @@ class HelloWorldHandler:
sophisticated, bots.
"""
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
content = "beep boop"
bot_handler.send_reply(message, content)

View file

@ -1,7 +1,7 @@
# See readme.md for instructions on running this code.
from typing import Dict
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class HelpHandler:
@ -15,7 +15,7 @@ class HelpHandler:
your Zulip instance.
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
help_content = "Info on Zulip can be found here:\nhttps://github.com/zulip/zulip"
bot_handler.send_reply(message, help_content)

View file

@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
API_BASE_URL = "https://beta.idonethis.com/api/v2"
@ -147,7 +147,7 @@ More information in my help"""
class IDoneThisHandler:
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
global api_key, default_team # noqa: PLW0603
self.config_info = bot_handler.get_config_info("idonethis")
if "api_key" in self.config_info:
@ -207,7 +207,7 @@ Below are some of the commands you can use, and what they do.
+ default_team_message
)
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
bot_handler.send_reply(message, self.get_response(message))
def get_response(self, message: Dict[str, Any]) -> str:

View file

@ -2,7 +2,7 @@ import json
import re
from typing import Any, Dict, Tuple
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
QUESTION = "How should we handle this?"
@ -28,7 +28,7 @@ class IncidentHandler:
glue code here should be pretty portable.
"""
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
query = message["content"]
if query.startswith("new "):
start_new_incident(query, message, bot_handler)
@ -46,7 +46,9 @@ class IncidentHandler:
bot_handler.send_reply(message, bot_response)
def start_new_incident(query: str, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def start_new_incident(
query: str, message: Dict[str, Any], bot_handler: AbstractBotHandler
) -> None:
# Here is where we would enter the incident in some sort of backend
# system. We just simulate everything by having an incident id that
# we generate here.

View file

@ -2,7 +2,7 @@
from typing import Dict, Final
from zulip_bots.lib import BotHandler, use_storage
from zulip_bots.lib import AbstractBotHandler, use_storage
class IncrementorHandler:
@ -19,13 +19,13 @@ class IncrementorHandler:
is @-mentioned, this number will be incremented in the same message.
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
storage = bot_handler.storage
if not storage.contains("number") or not storage.contains("message_id"):
storage.put("number", 0)
storage.put("message_id", None)
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
with use_storage(bot_handler.storage, ["number"]) as storage:
num = storage.get("number")

View file

@ -4,7 +4,7 @@ from typing import Any, Dict, Optional
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
GET_REGEX = re.compile('get "(?P<issue_key>.+)"$')
CREATE_REGEX = re.compile(
@ -153,7 +153,7 @@ class JiraHandler:
Jira Bot, `jira.conf` must be set up. See `doc.md` for more details.
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
config = bot_handler.get_config_info("jira")
username = config.get("username")
@ -205,7 +205,7 @@ class JiraHandler:
return response
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
content = message.get("content")
response = ""

View file

@ -3,7 +3,7 @@ from typing import Any, Dict
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class LinkShortenerHandler:
@ -18,11 +18,11 @@ class LinkShortenerHandler:
"`key` must be set in `link_shortener.conf`."
)
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("link_shortener")
self.check_api_key(bot_handler)
def check_api_key(self, bot_handler: BotHandler) -> None:
def check_api_key(self, bot_handler: AbstractBotHandler) -> None:
test_request_data: Any = self.call_link_shorten_service("www.youtube.com/watch")
try:
if self.is_invalid_token_error(test_request_data):
@ -38,7 +38,7 @@ class LinkShortenerHandler:
and response_json["status_txt"] == "INVALID_ARG_ACCESS_TOKEN"
)
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
regex_str = (
r"("
r"(?:http|https):\/\/" # This allows for the HTTP or HTTPS

View file

@ -4,18 +4,18 @@ from typing import Any, Dict, List
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class MentionHandler:
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("mention")
self.access_token = self.config_info["access_token"]
self.account_id = ""
self.check_access_token(bot_handler)
def check_access_token(self, bot_handler: BotHandler) -> None:
def check_access_token(self, bot_handler: AbstractBotHandler) -> None:
test_query_header = {
"Authorization": "Bearer " + self.access_token,
"Accept-Version": "1.15",
@ -43,7 +43,7 @@ class MentionHandler:
Version 1.00
"""
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
message["content"] = message["content"].strip()
if message["content"].lower() == "help":

View file

@ -2,7 +2,7 @@ import logging
from typing import Dict
from zulip_bots.bots.monkeytestit.lib import parse
from zulip_bots.lib import BotHandler, NoBotConfigError
from zulip_bots.lib import AbstractBotHandler, NoBotConfigError
class MonkeyTestitBot:
@ -17,7 +17,7 @@ class MonkeyTestitBot:
"Check doc.md for more options and setup instructions."
)
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
try:
self.config = bot_handler.get_config_info("monkeytestit")
except NoBotConfigError:
@ -47,7 +47,7 @@ class MonkeyTestitBot:
" your api_key value and try again."
)
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
content = message["content"]
response = parse.execute(content, self.api_key)

View file

@ -4,10 +4,13 @@ import logging
import re
from typing import Any, Collection, Dict, List
import simple_salesforce
# Upstream issue with simple_salesforce
# https://github.com/simple-salesforce/simple-salesforce/issues/723
from simple_salesforce import Salesforce # type: ignore[attr-defined]
from simple_salesforce.exceptions import SalesforceAuthenticationFailed
from zulip_bots.bots.salesforce.utils import commands, default_query, link_query, object_types
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
base_help_text = """Salesforce bot
This bot can do simple salesforce query requests
@ -73,9 +76,7 @@ def format_result(
return output
def query_salesforce(
arg: str, salesforce: simple_salesforce.Salesforce, command: Dict[str, Any]
) -> str:
def query_salesforce(arg: str, salesforce: Salesforce, command: Dict[str, Any]) -> str:
arg = arg.strip()
qarg = arg.split(" -", 1)[0]
split_args: List[str] = []
@ -161,18 +162,18 @@ class SalesforceHandler:
return "Usage: {} [arguments]".format(command["template"])
return get_help_text()
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("salesforce")
try:
self.sf = simple_salesforce.Salesforce(
self.sf = Salesforce(
username=self.config_info["username"],
password=self.config_info["password"],
security_token=self.config_info["security_token"],
)
except simple_salesforce.exceptions.SalesforceAuthenticationFailed as err:
except SalesforceAuthenticationFailed as err:
bot_handler.quit(f"Failed to log in to Salesforce. {err.code} {err.message}")
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
try:
bot_response = self.get_salesforce_response(message["content"])
bot_handler.send_reply(message, bot_response)

View file

@ -3,7 +3,7 @@ from typing import Dict, Final, Optional
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
# See readme.md for instructions on running this code.
@ -31,12 +31,12 @@ class StackOverflowHandler:
should preface query with "@mention-bot".
@mention-bot <search query>"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
bot_response = self.get_bot_stackoverflow_response(message, bot_handler)
bot_handler.send_reply(message, bot_response)
def get_bot_stackoverflow_response(
self, message: Dict[str, str], bot_handler: BotHandler
self, message: Dict[str, str], bot_handler: AbstractBotHandler
) -> Optional[str]:
"""This function returns the URLs of the requested topic."""

View file

@ -2,7 +2,7 @@ from typing import Dict
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class SusiHandler:
@ -38,7 +38,7 @@ class SusiHandler:
```
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
msg = message["content"]
if msg in ("help", ""):
bot_handler.send_reply(message, self.usage())

View file

@ -2,7 +2,7 @@ from typing import Any, Dict, List
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
supported_commands = [
("help", "Get the bot usage information."),
@ -18,7 +18,7 @@ RESPONSE_ERROR_MESSAGE = "Invalid Response. Please check configuration and param
class TrelloHandler:
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("trello")
self.api_key = self.config_info["api_key"]
self.access_token = self.config_info["access_token"]
@ -28,7 +28,7 @@ class TrelloHandler:
self.check_access_token(bot_handler)
def check_access_token(self, bot_handler: BotHandler) -> None:
def check_access_token(self, bot_handler: AbstractBotHandler) -> None:
test_query_response = requests.get(
f"https://api.trello.com/1/members/{self.user_name}/", params=self.auth_params
)
@ -43,7 +43,7 @@ class TrelloHandler:
Use `list-commands` to get information about the supported commands.
"""
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
content = message["content"].strip().split()
if content == []:

View file

@ -6,7 +6,7 @@ from typing import Any, Dict, Tuple
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class NotAvailableError(Exception):
@ -23,7 +23,7 @@ class TriviaQuizHandler:
This plugin will give users a trivia question from
the open trivia database at opentdb.com."""
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
query = message["content"]
if query == "new":
try:
@ -59,11 +59,11 @@ class TriviaQuizHandler:
bot_handler.send_reply(message, bot_response)
def get_quiz_from_id(quiz_id: str, bot_handler: BotHandler) -> str:
def get_quiz_from_id(quiz_id: str, bot_handler: AbstractBotHandler) -> str:
return bot_handler.storage.get(quiz_id)
def start_new_quiz(message: Dict[str, Any], bot_handler: BotHandler) -> None:
def start_new_quiz(message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
quiz = get_trivia_quiz()
quiz_id = generate_quiz_id(bot_handler.storage)
bot_response = format_quiz_for_markdown(quiz_id, quiz)
@ -188,7 +188,7 @@ Q: {question}
return content
def update_quiz(quiz: Dict[str, Any], quiz_id: str, bot_handler: BotHandler) -> None:
def update_quiz(quiz: Dict[str, Any], quiz_id: str, bot_handler: AbstractBotHandler) -> None:
bot_handler.storage.put(quiz_id, json.dumps(quiz))
@ -203,7 +203,11 @@ def build_response(is_correct: bool, num_answers: int) -> str:
def handle_answer(
quiz: Dict[str, Any], option: str, quiz_id: str, bot_handler: BotHandler, sender_name: str
quiz: Dict[str, Any],
option: str,
quiz_id: str,
bot_handler: AbstractBotHandler,
sender_name: str,
) -> Tuple[bool, str]:
answer = quiz["answers"][quiz["correct_letter"]]
is_new_answer = option not in quiz["answered_options"]

View file

@ -2,7 +2,7 @@ from typing import Dict
import tweepy
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class TwitpostBot:
@ -21,7 +21,7 @@ class TwitpostBot:
" * @twitpost tweet hey batman\n"
)
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("twitter")
auth = tweepy.OAuthHandler(
self.config_info["consumer_key"], self.config_info["consumer_secret"]
@ -31,7 +31,7 @@ class TwitpostBot:
)
self.api = tweepy.API(auth, parser=tweepy.parsers.JSONParser())
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
content = message["content"]
if content.strip() == "":

View file

@ -4,7 +4,7 @@ import os
import re
from typing import Any, Dict, Final, List, Set, Tuple, Union
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class VirtualFsHandler:
@ -16,7 +16,7 @@ class VirtualFsHandler:
def usage(self) -> str:
return get_help()
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
command = message["content"]
if command == "":
command = "help"

View file

@ -3,18 +3,18 @@ from typing import Any, Dict
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
api_url = "http://api.openweathermap.org/data/2.5/weather"
class WeatherHandler:
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.api_key = bot_handler.get_config_info("weather")["key"]
self.response_pattern = "Weather in {}, {}:\n{:.2f} F / {:.2f} C\n{}"
self.check_api_key(bot_handler)
def check_api_key(self, bot_handler: BotHandler) -> None:
def check_api_key(self, bot_handler: AbstractBotHandler) -> None:
api_params = dict(q="nyc", APPID=self.api_key)
test_response = requests.get(api_url, params=api_params)
try:
@ -29,7 +29,7 @@ class WeatherHandler:
This plugin will give info about weather in a specified city
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
help_content = """
This bot returns weather info for specified city.
You specify city in the following format:

View file

@ -3,7 +3,7 @@ from typing import Dict, Final
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
# See readme.md for instructions on running this code.
@ -33,11 +33,13 @@ class WikipediaHandler:
should preface searches with "@mention-bot".
@mention-bot <name of article>"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
bot_response = self.get_bot_wiki_response(message, bot_handler)
bot_handler.send_reply(message, bot_response)
def get_bot_wiki_response(self, message: Dict[str, str], bot_handler: BotHandler) -> str:
def get_bot_wiki_response(
self, message: Dict[str, str], bot_handler: AbstractBotHandler
) -> str:
"""This function returns the URLs of the requested topic."""
help_text = "Please enter your search term after {}"

View file

@ -6,7 +6,7 @@ from typing import Any, Callable, Dict, Optional
import wit
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class WitaiHandler:
@ -16,7 +16,7 @@ class WitaiHandler:
Wit.ai bot, `witai.conf` must be set up. See `doc.md` for more details.
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
config = bot_handler.get_config_info("witai")
token = config.get("token")
@ -37,7 +37,7 @@ class WitaiHandler:
self.client = wit.Wit(token)
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
if message["content"] == "" or message["content"] == "help":
bot_handler.send_reply(message, self.help_message)
return

View file

@ -4,7 +4,7 @@ from typing import Dict, Final, Optional
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
XKCD_TEMPLATE_URL = "https://xkcd.com/%s/info.0.json"
LATEST_XKCD_URL = "https://xkcd.com/info.0.json"
@ -36,7 +36,7 @@ class XkcdHandler:
`<comic_id>`, e.g `@mention-bot 1234`.
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
quoted_name = bot_handler.identity().mention
xkcd_bot_response = get_xkcd_bot_response(message, quoted_name)
bot_handler.send_reply(message, xkcd_bot_response)

View file

@ -5,7 +5,7 @@ from typing import Dict
import requests
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
HELP_MESSAGE = """
This bot allows users to translate a sentence into
@ -36,7 +36,7 @@ class YodaSpeakHandler:
It looks for messages starting with '@mention-bot'.
"""
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.api_key = bot_handler.get_config_info("yoda")["api_key"]
def usage(self) -> str:
@ -53,7 +53,7 @@ class YodaSpeakHandler:
@mention-bot You will learn how to speak like me someday.
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
self.handle_input(message, bot_handler)
def send_to_yoda_api(self, sentence: str) -> str:
@ -89,7 +89,7 @@ class YodaSpeakHandler:
sentence = message_content.replace(" ", "+")
return sentence
def handle_input(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_input(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
original_content = message["content"]
if self.is_help(original_content) or original_content == "":
@ -116,7 +116,7 @@ class YodaSpeakHandler:
bot_handler.send_reply(message, reply_message)
def send_message(
self, bot_handler: BotHandler, message: str, stream: str, subject: str
self, bot_handler: AbstractBotHandler, message: str, stream: str, subject: str
) -> None:
# function for sending a message
bot_handler.send_message(dict(type="stream", to=stream, subject=subject, content=message))

View file

@ -4,7 +4,7 @@ from typing import Dict, List, Optional, Tuple, Union
import requests
from requests.exceptions import ConnectionError, HTTPError
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
commands_list = ("list", "top", "help")
@ -28,7 +28,7 @@ class YoutubeHandler:
" * @mention-bot list funny dogs"
)
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.config_info = bot_handler.get_config_info("youtube")
# Check if API key is valid. If it is not valid, don't run the bot.
try:
@ -44,7 +44,7 @@ class YoutubeHandler:
except ConnectionError:
logging.warning("Bad connection")
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
if message["content"] == "" or message["content"] == "help":
bot_handler.send_reply(message, self.help_content)
else:

View file

@ -8,7 +8,7 @@ from typing import Any, Dict, Iterable, List, Sequence, Tuple
from typing_extensions import override
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
class BadMoveError(Exception):
@ -204,13 +204,13 @@ class GameAdapter:
"""
)
def initialize(self, bot_handler: BotHandler) -> None:
def initialize(self, bot_handler: AbstractBotHandler) -> None:
self.bot_handler = bot_handler
self.get_user_cache()
self.email = self.bot_handler.email
self.full_name = self.bot_handler.full_name
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, Any], bot_handler: AbstractBotHandler) -> None:
try:
self.bot_handler = bot_handler
content = message["content"].strip()

View file

@ -92,7 +92,7 @@ class BotStorage(Protocol):
class CachedStorage:
def __init__(self, parent_storage: BotStorage, init_data: Dict[str, Any]) -> None:
# CachedStorage is implemented solely for the context manager of any BotHandler.
# CachedStorage is implemented solely for the context manager of any AbstractBotHandler.
# It has a parent_storage that is responsible of communicating with the database
# 1. when certain data is not cached;
# 2. when the data need to be flushed to the database.
@ -176,7 +176,7 @@ def use_storage(storage: BotStorage, keys: List[str]) -> Iterator[BotStorage]:
cache.flush()
class BotHandler(Protocol):
class AbstractBotHandler(Protocol):
user_id: int
email: str
full_name: str
@ -378,7 +378,9 @@ class ExternalBotHandler:
sys.exit(message)
def extract_query_without_mention(message: Dict[str, Any], client: BotHandler) -> Optional[str]:
def extract_query_without_mention(
message: Dict[str, Any], client: AbstractBotHandler
) -> Optional[str]:
"""
If the bot is the first @mention in the message, then this function returns
the stripped message with the bot's @mention removed. Otherwise, it returns None.
@ -398,7 +400,7 @@ def extract_query_without_mention(message: Dict[str, Any], client: BotHandler) -
def is_private_message_but_not_group_pm(
message_dict: Dict[str, Any], current_user: BotHandler
message_dict: Dict[str, Any], current_user: AbstractBotHandler
) -> bool:
"""
Checks whether a message dict represents a PM from another user.
@ -422,7 +424,7 @@ def display_config_file_errors(error_msg: str, config_file: str) -> None:
print(f"\nMore details here:\n\n{error_msg}\n")
def prepare_message_handler(bot: str, bot_handler: BotHandler, bot_lib_module: Any) -> Any:
def prepare_message_handler(bot: str, bot_handler: AbstractBotHandler, bot_lib_module: Any) -> Any:
message_handler = bot_lib_module.handler_class()
if hasattr(message_handler, "validate_config"):
config_data = bot_handler.get_config_info(bot)

View file

@ -5,7 +5,7 @@ from unittest.mock import ANY, MagicMock, create_autospec, patch
from zulip import Client
from zulip_bots.lib import (
BotHandler,
AbstractBotHandler,
ExternalBotHandler,
StateHandler,
extract_query_without_mention,
@ -53,10 +53,10 @@ class FakeBotHandler:
def usage(self) -> str:
return """
This is a fake bot handler that is used
to spec BotHandler mocks.
to spec AbstractBotHandler mocks.
"""
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
pass

View file

@ -10,7 +10,7 @@ from unittest import mock
import importlib_metadata as metadata
from typing_extensions import override
from zulip_bots.lib import BotHandler
from zulip_bots.lib import AbstractBotHandler
from zulip_botserver import server
from zulip_botserver.input_parameters import parse_args
@ -19,7 +19,7 @@ from .server_test_lib import BotServerTestCase
class BotServerTests(BotServerTestCase):
class MockMessageHandler:
def handle_message(self, message: Dict[str, str], bot_handler: BotHandler) -> None:
def handle_message(self, message: Dict[str, str], bot_handler: AbstractBotHandler) -> None:
assert message == {"key": "test message"}
class MockLibModule: