1 Commits

Author SHA1 Message Date
41a3d7a981 chore(deps): lock file maintenance
Some checks failed
Enforce Conventional Commit PR Title / Validate PR Title (pull_request_target) Successful in 6s
CI / ci (pull_request) Failing after 50s
2025-06-06 17:38:55 +00:00
9 changed files with 41 additions and 85 deletions

View File

@ -41,14 +41,10 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: uv sync run: uv sync
# - name: Lint
# run: |
# uv run pylint --fail-under=8 --recursive=yes --output-format=parseable --output=lintreport.txt app/ tests/
# cat lintreport.txt
- name: Lint - name: Lint
run: | run: |
uv run pylint --fail-under=8 --recursive=yes --output-format=parseable app/ tests/ uv run pylint --fail-under=8 --recursive=yes --output-format=parseable app/ tests/ # --output=lintreport.txt
cat lintreport.txt
- name: Unit Test - name: Unit Test
run: | run: |

View File

@ -1,13 +1,8 @@
"""Command module for handling the 'exit' command in the Webex meme bot."""
from webex_bot.models.command import Command from webex_bot.models.command import Command
class ExitCommand(Command): class ExitCommand(Command):
"""Command to handle the 'exit' command in the Webex meme bot."""
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the ExitCommand with command keyword and help message."""
super().__init__( super().__init__(
command_keyword="exit", command_keyword="exit",
help_message="Exit", help_message="Exit",
@ -15,14 +10,11 @@ class ExitCommand(Command):
) )
self.sender: str = "" self.sender: str = ""
def pre_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def pre_execute(self, message, attachment_actions, activity) -> None:
"""Pre-execution logic for the exit command."""
return return
def execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def execute(self, message, attachment_actions, activity) -> None:
"""Execute the exit command."""
return return
def post_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def post_execute(self, message, attachment_actions, activity) -> None:
"""Post-execution logic for the exit command."""
return return

View File

@ -1,5 +1,3 @@
"""Generates meme images using the memegen.link API."""
import requests import requests
CHAR_REPLACEMENTS: list = [ CHAR_REPLACEMENTS: list = [
@ -19,11 +17,6 @@ CHAR_REPLACEMENTS: list = [
def get_templates() -> list[dict]: def get_templates() -> list[dict]:
"""Fetches available meme templates from the memegen.link API.
Returns:
list[dict]: A list of dictionaries containing meme template information.
"""
url: str = "https://api.memegen.link/templates" url: str = "https://api.memegen.link/templates"
req: requests.Response = requests.get(url=url, timeout=10) req: requests.Response = requests.get(url=url, timeout=10)
req.raise_for_status() req.raise_for_status()
@ -47,14 +40,6 @@ def get_templates() -> list[dict]:
def format_meme_string(input_string: str) -> str: def format_meme_string(input_string: str) -> str:
"""Formats a string for use in a meme image URL.
Args:
input_string (str): The string to format.
Returns:
str: The formatted string suitable for meme image URLs.
"""
# https://memegen.link/#special-characters # https://memegen.link/#special-characters
out_string: str = input_string out_string: str = input_string
for char_replacement in CHAR_REPLACEMENTS: for char_replacement in CHAR_REPLACEMENTS:
@ -63,16 +48,6 @@ def format_meme_string(input_string: str) -> str:
def generate_api_url(template: str, top_str: str, btm_str: str) -> str: def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
"""Generates a meme image URL using the memegen.link API.
Args:
template (str): The template identifier in the format "name.ext".
top_str (str): The text for the top line of the meme.
btm_str (str): The text for the bottom line of the meme.
Returns:
str: The complete URL for the meme image.
"""
tmpl_name: str tmpl_name: str
tmpl_ext: str tmpl_ext: str
tmpl_name, tmpl_ext = template.split(".") tmpl_name, tmpl_ext = template.split(".")
@ -80,5 +55,7 @@ def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
top_str = format_meme_string(top_str) top_str = format_meme_string(top_str)
btm_str = format_meme_string(btm_str) btm_str = format_meme_string(btm_str)
url: str = f"https://api.memegen.link/images/{tmpl_name}/{top_str}/{btm_str}.{tmpl_ext}" url: str = (
f"https://api.memegen.link/images/{tmpl_name}/{top_str}/{btm_str}.{tmpl_ext}"
)
return url return url

View File

@ -1,7 +1,5 @@
#!/usr/local/bin/python3 #!/usr/local/bin/python3
"""Main entry point for the Webex Bot application."""
from webex_bot.webex_bot import WebexBot from webex_bot.webex_bot import WebexBot
from app import close, meme from app import close, meme
@ -20,7 +18,6 @@ def create_bot() -> WebexBot:
def main() -> None: def main() -> None:
"""Main function to run the Webex Bot."""
bot: WebexBot = create_bot() bot: WebexBot = create_bot()
bot.add_command(meme.MakeMemeCommand()) bot.add_command(meme.MakeMemeCommand())
bot.add_command(close.ExitCommand()) bot.add_command(close.ExitCommand())

View File

@ -1,11 +1,9 @@
"""Generates meme images using the memegen.link API."""
from webex_bot.models.command import Command from webex_bot.models.command import Command
from webex_bot.models.response import Response, response_from_adaptive_card from webex_bot.models.response import Response, response_from_adaptive_card
from webexpythonsdk.models.cards import ( from webexteamssdk.models.cards import (
AdaptiveCard, AdaptiveCard,
Choice, Choice,
ChoiceSet, Choices,
Column, Column,
ColumnSet, ColumnSet,
FontSize, FontSize,
@ -13,7 +11,7 @@ from webexpythonsdk.models.cards import (
Text, Text,
TextBlock, TextBlock,
) )
from webexpythonsdk.models.cards.actions import OpenUrl, Submit from webexteamssdk.models.cards.actions import OpenUrl, Submit
from app import img from app import img
@ -24,7 +22,6 @@ class MakeMemeCommand(Command):
"""Class for initial Webex interactive card.""" """Class for initial Webex interactive card."""
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the MakeMemeCommand with command keyword and help message."""
super().__init__( super().__init__(
command_keyword="/meme", command_keyword="/meme",
help_message="Make a Meme", help_message="Make a Meme",
@ -32,12 +29,10 @@ class MakeMemeCommand(Command):
delete_previous_message=True, delete_previous_message=True,
) )
def pre_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def pre_execute(self, message, attachment_actions, activity) -> None:
"""Pre-execution logic for the MakeMemeCommand."""
return return
def execute(self, message, attachment_actions, activity) -> Response: # pylint: disable=unused-argument def execute(self, message, attachment_actions, activity) -> Response:
"""Execute the MakeMemeCommand and return an adaptive card."""
card_body: list = [ card_body: list = [
ColumnSet( ColumnSet(
columns=[ columns=[
@ -50,13 +45,13 @@ class MakeMemeCommand(Command):
size=FontSize.MEDIUM, size=FontSize.MEDIUM,
), ),
TextBlock( TextBlock(
"This bot uses memegen.link to generate memes. Click 'View Templates' to view available templates.", # pylint: disable=line-too-long "This bot uses memegen.link to generate memes. Click 'View Templates' to view available templates.",
weight=FontWeight.LIGHTER, weight=FontWeight.LIGHTER,
size=FontSize.SMALL, size=FontSize.SMALL,
wrap=True, wrap=True,
), ),
TextBlock( TextBlock(
"Both fields are required. If you do not want to specify a value, please type a space.", # pylint: disable=line-too-long "Both fields are required. If you do not want to specify a value, please type a space.",
weight=FontWeight.LIGHTER, weight=FontWeight.LIGHTER,
size=FontSize.SMALL, size=FontSize.SMALL,
wrap=True, wrap=True,
@ -70,10 +65,13 @@ class MakeMemeCommand(Command):
Column( Column(
width=1, width=1,
items=[ items=[
ChoiceSet( Choices(
id="meme_type", id="meme_type",
isMultiSelect=False, isMultiSelect=False,
choices=[Choice(title=x["name"], value=x["choiceval"]) for x in TEMPLATES], choices=[
Choice(title=x["name"], value=x["choiceval"])
for x in TEMPLATES
],
), ),
Text(id="text_top", placeholder="Top Text", maxLength=100), Text(id="text_top", placeholder="Top Text", maxLength=100),
Text( Text(
@ -105,7 +103,6 @@ class MakeMemeCallback(Command):
"""Class to process user data and return meme.""" """Class to process user data and return meme."""
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the MakeMemeCallback with command keyword and help message."""
super().__init__( super().__init__(
card_callback_keyword="make_meme_callback_rbamzfyx", card_callback_keyword="make_meme_callback_rbamzfyx",
delete_previous_message=True, delete_previous_message=True,
@ -116,8 +113,7 @@ class MakeMemeCallback(Command):
self.meme: str = "" self.meme: str = ""
self.meme_filename: str = "" self.meme_filename: str = ""
def pre_execute(self, message, attachment_actions, activity) -> str: # pylint: disable=unused-argument def pre_execute(self, message, attachment_actions, activity) -> str:
"""Pre-execution logic for the MakeMemeCallback."""
self.meme: str = attachment_actions.inputs.get("meme_type") self.meme: str = attachment_actions.inputs.get("meme_type")
self.text_top: str = attachment_actions.inputs.get("text_top") self.text_top: str = attachment_actions.inputs.get("text_top")
self.text_bottom: str = attachment_actions.inputs.get("text_bottom") self.text_bottom: str = attachment_actions.inputs.get("text_bottom")
@ -131,12 +127,13 @@ class MakeMemeCallback(Command):
return "Generating your meme..." return "Generating your meme..."
def execute(self, message, attachment_actions, activity) -> Response | None: # pylint: disable=unused-argument def execute(self, message, attachment_actions, activity) -> Response | None:
"""Execute the MakeMemeCallback and return a response with the meme image."""
if self.error: if self.error:
return None return None
self.meme_filename: str = img.generate_api_url(self.meme, self.text_top, self.text_bottom) self.meme_filename: str = img.generate_api_url(
self.meme, self.text_top, self.text_bottom
)
msg: Response = Response( msg: Response = Response(
attributes={ attributes={
"roomId": activity["target"]["globalId"], "roomId": activity["target"]["globalId"],
@ -146,6 +143,5 @@ class MakeMemeCallback(Command):
) )
return msg return msg
def post_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def post_execute(self, message, attachment_actions, activity) -> None:
"""Post-execution logic for the MakeMemeCallback."""
return return

View File

@ -8,7 +8,7 @@ authors = [
] ]
requires-python = ">=3.11.2" requires-python = ">=3.11.2"
dependencies = [ dependencies = [
"webex-bot<1.1.0,>=1.0.3", "webex-bot<1.0.0,>=0.5.2",
"pillow<12.0.0,>=11.0.0", "pillow<12.0.0,>=11.0.0",
"astroid<=3.3.10", "astroid<=3.3.10",
] ]
@ -32,6 +32,3 @@ includes = []
[build-system] [build-system]
requires = ["pdm-backend"] requires = ["pdm-backend"]
build-backend = "pdm.backend" build-backend = "pdm.backend"
[tool.black]
line-length = 120

View File

@ -2,22 +2,19 @@
import os import os
env_vars: dict = { vars: dict = {
"APP_VERSION": "dev", "APP_VERSION": "dev",
"WEBEX_API_KEY": "testing", "WEBEX_API_KEY": "testing",
} }
for var, value in env_vars.items(): for var, value in vars.items():
os.environ[var] = value os.environ[var] = value
# needs to be imported AFTER environment variables are set # needs to be imported AFTER environment variables are set
from app.config import ( from app.config import config # pragma: no cover # noqa: E402
config,
) # pylint: disable=wrong-import-position # pragma: no cover # noqa: E402
def test_config() -> None: def test_config() -> None:
"""Test the configuration settings.""" assert config.webex_token == vars["WEBEX_API_KEY"]
assert config.webex_token == env_vars["WEBEX_API_KEY"] assert config.version == vars["APP_VERSION"]
assert config.version == env_vars["APP_VERSION"]

View File

@ -29,4 +29,8 @@ def test_error_false() -> None:
callback.text_top = "TEST" callback.text_top = "TEST"
callback.text_bottom = "TEST" callback.text_bottom = "TEST"
result: Response = callback.execute(None, None, {"target": {"globalId": "TEST"}}) result: Response = callback.execute(None, None, {"target": {"globalId": "TEST"}})
assert isinstance(result, Response) and result.roomId == "TEST" and result.files[0] == callback.meme_filename assert (
isinstance(result, Response)
and result.roomId == "TEST"
and result.files[0] == callback.meme_filename
)

8
uv.lock generated
View File

@ -584,7 +584,7 @@ wheels = [
[[package]] [[package]]
name = "webex-bot" name = "webex-bot"
version = "1.0.3" version = "0.6.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "backoff" }, { name = "backoff" },
@ -592,9 +592,9 @@ dependencies = [
{ name = "webexpythonsdk" }, { name = "webexpythonsdk" },
{ name = "websockets" }, { name = "websockets" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/35/a5/beae32cfe2f42fc23d6beb850314fbab232d8b3c12b3f4752dfe61fe2709/webex_bot-1.0.3.tar.gz", hash = "sha256:5813be91563200953aea6ee52da8a1a4d5a0369cb5b9f96bf71c592378eb9600", size = 30000, upload-time = "2025-06-04T14:33:28.964Z" } sdist = { url = "https://files.pythonhosted.org/packages/8c/44/9c19b6cdabc1fff00a6f01a0b5d9e04c178dc4ab6a2d8a24491918c336ee/webex_bot-0.6.2.tar.gz", hash = "sha256:93146eacfad0678322062e39f101a1ed9d6e3a1b0d69afee341d429d775e6542", size = 29677, upload-time = "2025-05-23T10:25:37.379Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/b4/ad4ce0bbd01248f949281a9831862e73ec20eef0b1b4c697a4b60802e6cf/webex_bot-1.0.3-py2.py3-none-any.whl", hash = "sha256:b9a326dabedda6a9bdee308ecb70f01277583a6bc92b583efea51e9211521567", size = 22352, upload-time = "2025-06-04T14:33:26.345Z" }, { url = "https://files.pythonhosted.org/packages/60/16/9cdd96229121cac4bd359db90f15ba34902f5557d0903d555335d517aa30/webex_bot-0.6.2-py2.py3-none-any.whl", hash = "sha256:b2813b3a349513876116c17362b75f609a69907cf137c6839cbb36b43a76474e", size = 22081, upload-time = "2025-05-23T10:25:35.582Z" },
] ]
[[package]] [[package]]
@ -622,7 +622,7 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "astroid", specifier = "<=3.3.10" }, { name = "astroid", specifier = "<=3.3.10" },
{ name = "pillow", specifier = ">=11.0.0,<12.0.0" }, { name = "pillow", specifier = ">=11.0.0,<12.0.0" },
{ name = "webex-bot", specifier = ">=1.0.3,<1.1.0" }, { name = "webex-bot", specifier = ">=0.5.2,<1.0.0" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]