Compare commits
1 Commits
v0.40.6
...
a2fec2a4af
| Author | SHA1 | Date | |
|---|---|---|---|
| a2fec2a4af |
@@ -12,12 +12,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository code
|
- name: Check out repository code
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Run Hadolint
|
- name: Run Hadolint
|
||||||
uses: hadolint/hadolint-action@v3.3.0
|
uses: hadolint/hadolint-action@v3.1.0
|
||||||
with:
|
with:
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
output-file: hadolint.out
|
output-file: hadolint.out
|
||||||
@@ -25,9 +25,9 @@ jobs:
|
|||||||
no-fail: true
|
no-fail: true
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.14"
|
python-version: "3.13"
|
||||||
|
|
||||||
- name: uv cache
|
- name: uv cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -40,15 +40,11 @@ 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 --output=lintreport.txt app/ tests/
|
||||||
|
cat lintreport.txt
|
||||||
|
|
||||||
- name: Unit Test
|
- name: Unit Test
|
||||||
run: |
|
run: |
|
||||||
@@ -58,38 +54,18 @@ jobs:
|
|||||||
|
|
||||||
- name: Minimize uv cache
|
- name: Minimize uv cache
|
||||||
run: uv cache prune --ci
|
run: uv cache prune --ci
|
||||||
|
|
||||||
- name: Set up environment for Snyk
|
|
||||||
run: |
|
|
||||||
uv pip freeze > requirements.txt
|
|
||||||
mv pyproject.toml pyproject.toml.bak
|
|
||||||
mv uv.lock uv.lock.bak
|
|
||||||
|
|
||||||
- name: Snyk SAST Scan
|
- name: SonarQube Scan
|
||||||
|
uses: SonarSource/sonarqube-scan-action@v4.2.1
|
||||||
|
env:
|
||||||
|
SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
|
||||||
|
|
||||||
|
- name: Snyk Vulnerability Scan
|
||||||
uses: snyk/actions/python@master
|
uses: snyk/actions/python@master
|
||||||
|
continue-on-error: true # Sometimes vulns aren't immediately fixable
|
||||||
env:
|
env:
|
||||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
with:
|
with:
|
||||||
# command: snyk
|
command: snyk
|
||||||
args: snyk code test #--all-projects --exclude=.archive
|
args: test --all-projects
|
||||||
|
|
||||||
# - name: SonarQube Scan
|
|
||||||
# uses: SonarSource/sonarqube-scan-action@v5.2.0
|
|
||||||
# env:
|
|
||||||
# SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
|
|
||||||
# SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
|
|
||||||
|
|
||||||
# - name: Snyk Vulnerability Scan
|
|
||||||
# uses: snyk/actions/python@master
|
|
||||||
# continue-on-error: true # Sometimes vulns aren't immediately fixable
|
|
||||||
# env:
|
|
||||||
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
|
||||||
# with:
|
|
||||||
# command: snyk
|
|
||||||
# args: test --all-projects
|
|
||||||
|
|
||||||
- name: Reverse set up environment for Snyk
|
|
||||||
run: |
|
|
||||||
rm -f requirements.txt
|
|
||||||
mv pyproject.toml.bak pyproject.toml
|
|
||||||
mv uv.lock.bak uv.lock
|
|
||||||
|
|||||||
@@ -1,48 +1,104 @@
|
|||||||
name: Release
|
name: Release
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 9 * * 0'
|
- cron: "0 9 * * 0"
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# test:
|
manual_trigger:
|
||||||
# name: Test
|
name: Manual Trigger Cleanup
|
||||||
# uses: https://git.tainton.uk/${{ gitea.repository }}/.gitea/workflows/ci.yml@main
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ gitea.event_name == 'issue_comment' }}
|
||||||
|
steps:
|
||||||
|
- name: Log event metadata
|
||||||
|
run: |
|
||||||
|
echo "Issue: ${{ gitea.event.issue.number }}"
|
||||||
|
echo "Comment: ${{ gitea.event.comment.body }}"
|
||||||
|
echo "User: ${{ gitea.event.comment.user.login }}"
|
||||||
|
|
||||||
tag:
|
- name: Stop workflow if required conditions are not met
|
||||||
name: Tag release
|
if: ${{ !contains(gitea.event.issue.number, '436') || !contains(gitea.event.comment.body, '/trigger-release') || !contains(gitea.event.comment.user.login, 'luke') }}
|
||||||
uses: https://git.tainton.uk/actions/gha-workflows/.gitea/workflows/release-with-tag.yaml@main
|
run: exit 1
|
||||||
|
|
||||||
|
- name: Delete issue comment
|
||||||
|
run: |
|
||||||
|
curl -X DELETE \
|
||||||
|
-H "Authorization: token ${{ gitea.token }}" \
|
||||||
|
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/issues/comments/${{ gitea.event.comment.id }}"
|
||||||
|
|
||||||
|
# test:
|
||||||
|
# name: Unit Test
|
||||||
|
# uses: https://git.tainton.uk/public/webexmemebot/.gitea/workflows/ci.yml@main
|
||||||
|
# continue-on-error: true
|
||||||
|
|
||||||
create_release:
|
create_release:
|
||||||
name: Create Release
|
name: Create Release
|
||||||
needs: tag
|
|
||||||
uses: https://git.tainton.uk/actions/gha-workflows/.gitea/workflows/create-release-preexisting-tag.yaml@main
|
|
||||||
with:
|
|
||||||
tag: ${{ needs.tag.outputs.tag_name }}
|
|
||||||
body: ${{ needs.tag.outputs.changelog }}
|
|
||||||
secrets:
|
|
||||||
ACTIONS_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
|
|
||||||
|
|
||||||
# get_release_id:
|
|
||||||
# name: Get Release ID
|
|
||||||
# runs-on: ubuntu-latest
|
|
||||||
# needs: create_release
|
|
||||||
# outputs:
|
|
||||||
# releaseid: ${{ steps.getid.outputs.releaseid }}
|
|
||||||
# steps:
|
|
||||||
# - name: Get Release ID
|
|
||||||
# id: getid
|
|
||||||
# run: |
|
|
||||||
# rid=$(curl -s -X 'GET' \
|
|
||||||
# -H 'accept: application/json' \
|
|
||||||
# '${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/latest' | jq -r '.id')
|
|
||||||
# echo "releaseid=$rid" >> "$GITEA_OUTPUT"
|
|
||||||
# echo "$rid"
|
|
||||||
|
|
||||||
create_docker:
|
|
||||||
name: Publish Docker Images
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [tag, create_release]
|
# needs: test
|
||||||
|
outputs:
|
||||||
|
release_name: ${{ steps.get_next_version.outputs.tag }}
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Changes since last tag
|
||||||
|
id: changes
|
||||||
|
run: |
|
||||||
|
rm -f .changes
|
||||||
|
git log $(git describe --tags --abbrev=0)..HEAD --no-merges --oneline >> .changes
|
||||||
|
cat .changes
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
run: |
|
||||||
|
if [[ -z $(grep '[^[:space:]]' .changes) ]] ; then
|
||||||
|
echo "changes=false"
|
||||||
|
echo "changes=false" >> "$GITEA_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "changes=true"
|
||||||
|
echo "changes=true" >> "$GITEA_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Cancel if no changes
|
||||||
|
if: steps.changes.outputs.changes == 'false'
|
||||||
|
run: exit 1
|
||||||
|
|
||||||
|
- name: Set server URL
|
||||||
|
id: set_srvurl
|
||||||
|
run: |
|
||||||
|
SRVURL=$(echo "${{ gitea.server_url }}" | sed 's/https:\/\/\(.*\)/\1/')
|
||||||
|
echo "srvurl=$SRVURL" >> "$GITEA_OUTPUT"
|
||||||
|
|
||||||
|
- name: Get next version
|
||||||
|
uses: TriPSs/conventional-changelog-action@v6
|
||||||
|
id: get_next_version
|
||||||
|
with:
|
||||||
|
git-url: ${{ steps.set_srvurl.outputs.srvurl }}
|
||||||
|
github-token: ${{ gitea.token }}
|
||||||
|
preset: "conventionalcommits"
|
||||||
|
# preset: "angular" # This is the default
|
||||||
|
skip-commit: true
|
||||||
|
release-count: 1
|
||||||
|
output-file: false
|
||||||
|
create-summary: true
|
||||||
|
skip-on-empty: true
|
||||||
|
skip-version-file: true
|
||||||
|
skip-tag: true
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
run: |
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.ACTIONS_TOKEN }}" \
|
||||||
|
-H "accept: application/json" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\": \"${{ steps.get_next_version.outputs.tag }}\", \"name\": \"${{ steps.get_next_version.outputs.tag }}\", \"body\": \"${{ steps.get_next_version.outputs.changelog }}\"}" \
|
||||||
|
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases"
|
||||||
|
|
||||||
|
build_docker:
|
||||||
|
name: Build Docker Images
|
||||||
|
needs: create_release
|
||||||
steps:
|
steps:
|
||||||
- name: Update Docker configuration
|
- name: Update Docker configuration
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -54,17 +110,11 @@ jobs:
|
|||||||
echo "DOCKER_OPTS=\"--insecure-registry ${{ vars.PACKAGES_REGISTRY_URL }}\"" >> /etc/default/docker
|
echo "DOCKER_OPTS=\"--insecure-registry ${{ vars.PACKAGES_REGISTRY_URL }}\"" >> /etc/default/docker
|
||||||
echo "{\"insecure-registries\": [\"${{ vars.PACKAGES_REGISTRY_URL }}\"]}" > /etc/docker/daemon.json
|
echo "{\"insecure-registries\": [\"${{ vars.PACKAGES_REGISTRY_URL }}\"]}" > /etc/docker/daemon.json
|
||||||
|
|
||||||
- name: Get repo name
|
|
||||||
id: split
|
|
||||||
run: echo "repo=${REPO##*/}" >> "$GITEA_OUTPUT"
|
|
||||||
env:
|
|
||||||
REPO: ${{ gitea.repository }}
|
|
||||||
|
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ needs.tag.outputs.tag_name }}
|
ref: ${{ needs.create_release.outputs.release_name }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -87,10 +137,10 @@ jobs:
|
|||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
tags: type=semver,pattern=v{{version}},value=${{ needs.tag.outputs.tag_name }}
|
|
||||||
images: |
|
images: |
|
||||||
ghcr.io/${{ vars.GHCR_USERNAME }}/${{ steps.split.outputs.repo }}
|
ghcr.io/${{ vars.GHCR_USERNAME }}/webexmemebot
|
||||||
${{ vars.PACKAGES_REGISTRY_URL }}/${{ gitea.repository }}
|
${{ vars.PACKAGES_REGISTRY_URL }}/${{ gitea.repository }}
|
||||||
|
tags: type=semver,pattern=v{{version}},value=${{ needs.create_release.outputs.release_name }}
|
||||||
|
|
||||||
- name: Print metadata
|
- name: Print metadata
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -9,25 +9,23 @@ on:
|
|||||||
- cron: "@daily"
|
- cron: "@daily"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# sonarqube:
|
sonarqube:
|
||||||
# name: SonarQube
|
|
||||||
# runs-on: ubuntu-latest
|
|
||||||
# steps:
|
|
||||||
# - name: Checkout repo
|
|
||||||
# uses: actions/checkout@v4.2.2
|
|
||||||
|
|
||||||
# - name: SonarQube Scan
|
|
||||||
# uses: SonarSource/sonarqube-scan-action@v5.2.0
|
|
||||||
# env:
|
|
||||||
# SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
|
|
||||||
# SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
|
|
||||||
|
|
||||||
snyk:
|
|
||||||
name: Snyk
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
|
- name: SonarQube Scan
|
||||||
|
uses: SonarSource/sonarqube-scan-action@v4.2.1
|
||||||
|
env:
|
||||||
|
SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
|
||||||
|
|
||||||
|
snyk:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Snyk
|
- name: Snyk
|
||||||
uses: snyk/actions/python@master
|
uses: snyk/actions/python@master
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.14-slim
|
FROM python:3.13-slim
|
||||||
LABEL maintainer="Luke Tainton <luke@tainton.uk>"
|
LABEL maintainer="Luke Tainton <luke@tainton.uk>"
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
|
|||||||
14
app/close.py
14
app/close.py
@@ -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
|
||||||
|
|||||||
29
app/img.py
29
app/img.py
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
40
app/meme.py
40
app/meme.py
@@ -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
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ 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.1,>=12.0.0",
|
"pillow<12.0.0,>=11.0.0",
|
||||||
"astroid<=4.0.1",
|
"astroid<=3.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[tool.uv]
|
||||||
dev = [
|
dev-dependencies = [
|
||||||
"black<25.9.1,>=25.9.0",
|
"black<25.2.0,>=25.1.0",
|
||||||
"coverage<8.0.0,>=7.6.10",
|
"coverage<8.0.0,>=7.6.10",
|
||||||
"isort<7.0.1,>=7.0.0",
|
"isort<6.1.0,>=6.0.0",
|
||||||
"pylint<4.1.0,>=4.0.0",
|
"pylint<4.0.0,>=3.3.2",
|
||||||
"pylint-exit<2.0.0,>=1.2.0",
|
"pylint-exit<2.0.0,>=1.2.0",
|
||||||
"pytest<9.0.0,>=8.3.4",
|
"pytest<9.0.0,>=8.3.4",
|
||||||
"pre-commit<5.0.0,>=4.0.1",
|
"pre-commit<5.0.0,>=4.0.1",
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"assignAutomerge": false,
|
"assignAutomerge": true,
|
||||||
"assigneesFromCodeOwners": false,
|
"assigneesFromCodeOwners": true,
|
||||||
"dependencyDashboardAutoclose": true,
|
"dependencyDashboardAutoclose": true,
|
||||||
"extends": ["config:recommended"],
|
"extends": ["config:recommended"],
|
||||||
"ignorePaths": ["**/.archive/**"],
|
"ignorePaths": ["**/.archive/**"],
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user