Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ notes:
- `LXC` only works with `BUILD_TARGET=xaga`
- `TG_NOTIFY=true` needs `TG_BOT_TOKEN` and `TG_CHAT_ID`
- `GH_TOKEN` is optional, but helps when fetching latest release assets
- generic telegram success notifications upload one grouped message containing the AnyKernel3 zip plus a boot-image zip

## output

Expand All @@ -190,4 +191,3 @@ notes:
| build.log | build log |

where `<package>` has the format `${KERNEL_NAME}-${KERNEL_VERSION}-${VARIANT}` (with `-${KERNEL_COMMIT}` appended when `IS_RELEASE` is not `true`).

47 changes: 31 additions & 16 deletions ci/package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,17 @@ write_metadata() {
"$KERNEL_COMMIT"
}

notify_success() {
local final_package="$1"
local build_time="$2"
# For indicating package type (boot image, anykernel3)
local additional_tag="$3"

build_result_caption() {
local build_time="$1"
local additional_tag="$2"
local note="${3:-}"
local kernel_commit_url
kernel_commit_url="$(repo_spec "$KERNEL_REPO" github-commit-url "$KERNEL_COMMIT")"

local minutes=$((build_time / 60))
local seconds=$((build_time % 60))

local result_caption
result_caption=$(
cat << EOF
cat << EOF
✅ *$(escape_md_v2 "$KERNEL_NAME Build Successfully!")*

🏷️ \#$(escape_md_v2 "$BUILD_TAG") \#$(escape_md_v2 "$additional_tag")
Expand All @@ -131,9 +127,18 @@ $(tg_run_line)
*Commit:* [$(escape_md_v2 "$KERNEL_COMMIT")]($(escape_md_v2 "$kernel_commit_url"))
*Compiler:* $(escape_md_v2 "$COMPILER_STRING")
*Features:* KSU $(parse_bool "$KSU"), SuSFS $(is_true "$SUSFS" && escape_md_v2 "$SUSFS_VERSION" || echo "Disabled"), LXC $(parse_bool "$LXC"), Stock config $(parse_bool "$STOCK_CONFIG")
$(if [[ -n $note ]]; then printf '*Note:* %s\n' "$(escape_md_v2 "$note")"; fi)
EOF
)
}

notify_success() {
local final_package="$1"
local build_time="$2"
# For indicating package type (boot image, anykernel3)
local additional_tag="$3"

local result_caption
result_caption="$(build_result_caption "$build_time" "$additional_tag")"
telegram_upload_file "$final_package" "$result_caption"
}

Expand All @@ -143,16 +148,26 @@ telegram_notify() {

# AnyKernel3
local ak3_package="$OUT_DIR/$package_name-AnyKernel3.zip"
notify_success "$ak3_package" "$build_time" "anykernel3"

# Boot image
if [[ "$BUILD_TARGET" == "xaga" ]]; then
notify_success "$ak3_package" "$build_time" "anykernel3"
return
fi

local boot_package="$OUT_DIR/$package_name-boot.zip"
pushd "$OUT_DIR" > /dev/null
zip -9q -T "$package_name-boot.zip" "$package_name"-boot*.img
zip -9q -T "$(basename "$boot_package")" "$package_name"-boot*.img
popd > /dev/null

notify_success "$OUT_DIR/$package_name-boot.zip" "$build_time" "boot_image"
rm -f "$OUT_DIR/$package_name-boot.zip"
local result_caption
result_caption="$(
build_result_caption \
"$build_time" \
"generic" \
"Boot image zip contains raw, gzip, and lz4 variants."
)"
telegram_upload_gallery \
"$result_caption" \
"$ak3_package" \
"$boot_package"
rm -f "$boot_package"
}
6 changes: 6 additions & 0 deletions ci/utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ telegram_upload_file() {
printf '%s' "$*" | py_cli tg doc "$file"
}

telegram_upload_gallery() {
is_true "${TG_NOTIFY:-false}" || return 0

printf '%s' "$1" | py_cli tg gallery "${@:2}"
}

init_logging() {
# Clean logfile before writing
: > "$LOGFILE"
Expand Down
7 changes: 6 additions & 1 deletion py/src/tools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .common import escape_md_v2, stdin_text
from .github_release import asset_url_api, asset_url_json, next_tag
from .meta import write_metadata
from .tg import send_document, send_message
from .tg import send_document, send_document_gallery, send_message

app = typer.Typer(no_args_is_help=True)
release_app = typer.Typer(no_args_is_help=True)
Expand Down Expand Up @@ -76,6 +76,11 @@ def tg_doc(file: Path) -> None:
send_document(file, stdin_text())


@tg_app.command("gallery")
def tg_gallery(files: list[Path]) -> None:
send_document_gallery(files, stdin_text())


@util_app.command("escape-md-v2")
def util_escape_md_v2(text: Annotated[str, typer.Argument()]) -> None:
typer.echo(escape_md_v2(text), nl=False)
Expand Down
12 changes: 12 additions & 0 deletions py/src/tools/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,15 @@ class TelegramDocumentRequest(BaseModel):
parse_mode: str = "MarkdownV2"
disable_web_page_preview: str = "true"
caption: str


class TelegramMediaDocument(BaseModel):
type: str = "document"
media: str
caption: str | None = None
parse_mode: str | None = None


class TelegramMediaGroupRequest(BaseModel):
chat_id: str
media: list[TelegramMediaDocument]
61 changes: 60 additions & 1 deletion py/src/tools/tg.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import json
from io import BufferedReader
from pathlib import Path

import requests
from pydantic import ValidationError

from .common import die, env
from .models import TelegramDocumentRequest, TelegramMessageRequest, TelegramResponse
from .models import (
TelegramDocumentRequest,
TelegramMediaDocument,
TelegramMediaGroupRequest,
TelegramMessageRequest,
TelegramResponse,
)


def tg_api_url(method: str) -> str:
Expand Down Expand Up @@ -54,3 +62,54 @@ def send_document(file_path: Path, caption: str) -> None:
die(f"sendDocument returned invalid JSON: {e}")
if not data.ok:
die(f"sendDocument failed: {data.description or 'Unknown error'}")


def send_document_gallery(file_paths: list[Path], caption: str) -> None:
if not file_paths:
die("No files provided for document gallery upload")

missing_files = [str(file_path) for file_path in file_paths if not file_path.exists()]
if missing_files:
die(f"File not found: {', '.join(missing_files)}")

media = []
files: dict[str, tuple[str, BufferedReader]] = {}
handles: list[BufferedReader] = []
try:
for index, file_path in enumerate(file_paths):
attachment_name = f"file{index}"
media.append(
TelegramMediaDocument(
media=f"attach://{attachment_name}",
caption=caption if index == 0 else None,
parse_mode="MarkdownV2" if index == 0 else None,
)
)

handle = file_path.open("rb")
handles.append(handle)
files[attachment_name] = (file_path.name, handle)

payload = TelegramMediaGroupRequest(chat_id=env("TG_CHAT_ID"), media=media)
response = requests.post(
tg_api_url("sendMediaGroup"),
data={
"chat_id": payload.chat_id,
"media": json.dumps([item.model_dump(mode="json", exclude_none=True) for item in payload.media]),
},
files=files,
timeout=180,
)
response.raise_for_status()
except requests.RequestException as e:
die(f"sendMediaGroup request failed: {e}")
finally:
for handle in handles:
handle.close()

try:
data = TelegramResponse.model_validate_json(response.text)
except ValidationError as e:
die(f"sendMediaGroup returned invalid JSON: {e}")
if not data.ok:
die(f"sendMediaGroup failed: {data.description or 'Unknown error'}")