From aaf587480fb43279f59a55371935cdc574136e09 Mon Sep 17 00:00:00 2001 From: prajeeta pal Date: Wed, 10 Dec 2025 15:04:24 +0530 Subject: [PATCH 1/3] feat: add issue reminder bot Signed-off-by: prajeeta pal --- .github/scripts/issue_reminder_no_pr.sh | 140 ++++++++++++++++++ .../workflows/bot-issue-reminder-no-pr.yml | 31 ++++ CHANGELOG.md | 32 +++- 3 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 .github/scripts/issue_reminder_no_pr.sh create mode 100644 .github/workflows/bot-issue-reminder-no-pr.yml diff --git a/.github/scripts/issue_reminder_no_pr.sh b/.github/scripts/issue_reminder_no_pr.sh new file mode 100644 index 000000000..075cd1609 --- /dev/null +++ b/.github/scripts/issue_reminder_no_pr.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Env: +# GH_TOKEN - provided by GitHub Actions +# REPO - owner/repo (fallback to GITHUB_REPOSITORY) +# DAYS - reminder threshold in days (default 7) + +REPO="${REPO:-${GITHUB_REPOSITORY:-}}" +DAYS="${DAYS:-7}" + +if [ -z "$REPO" ]; then + echo "ERROR: REPO environment variable not set." + exit 1 +fi + +echo "------------------------------------------------------------" +echo " Issue Reminder Bot (No PR)" +echo " Repo: $REPO" +echo " Threshold: $DAYS days" +echo "------------------------------------------------------------" +echo + +NOW_TS=$(date +%s) + +# Cross-platform timestamp parsing (Linux + macOS/BSD) +parse_ts() { + local ts="$1" + if date --version >/dev/null 2>&1; then + date -d "$ts" +%s # GNU date (Linux) + else + date -j -f "%Y-%m-%dT%H:%M:%SZ" "$ts" +"%s" # macOS/BSD + fi +} + +# Fetch open ISSUES (not PRs) that have assignees +ISSUES=$(gh api "repos/$REPO/issues" \ + --paginate \ + --jq '.[] | select(.state=="open" and (.assignees | length > 0) and (.pull_request | not)) | .number') + +if [ -z "$ISSUES" ]; then + echo "No open issues with assignees found." + exit 0 +fi + +for ISSUE in $ISSUES; do + echo "============================================================" + echo " ISSUE #$ISSUE" + echo "============================================================" + + ISSUE_JSON=$(gh api "repos/$REPO/issues/$ISSUE") + ASSIGNEES=$(echo "$ISSUE_JSON" | jq -r '.assignees[].login') + + if [ -z "$ASSIGNEES" ]; then + echo "[INFO] No assignees? Skipping." + echo + continue + fi + + echo "[INFO] Assignees: $ASSIGNEES" + echo + + # Check if this issue already has a reminder comment from ReminderBot + EXISTING_COMMENT=$(gh api "repos/$REPO/issues/$ISSUE/comments" \ + --jq ".[] | select(.user.login == \"github-actions[bot]\") | select(.body | contains(\"ReminderBot\")) | .id" \ + | head -n1) + + if [ -n "$EXISTING_COMMENT" ]; then + echo "[INFO] Reminder comment already posted on this issue." + echo + continue + fi + + # Get assignment time (use the last assigned event or issue creation time) + ASSIGN_TS=$(gh api "repos/$REPO/issues/$ISSUE/events" \ + --jq ".[] | select(.event==\"assigned\") | .created_at" \ + | tail -n1) + + if [ -z "$ASSIGN_TS" ]; then + echo "[WARN] No assignment event found, falling back to issue creation." + ASSIGN_TS=$(echo "$ISSUE_JSON" | jq -r '.created_at') + fi + + ASSIGN_TS_SEC=$(parse_ts "$ASSIGN_TS") + DIFF_DAYS=$(( (NOW_TS - ASSIGN_TS_SEC) / 86400 )) + + echo "[INFO] Assigned/Created at: $ASSIGN_TS" + echo "[INFO] Days since assignment: $DIFF_DAYS" + + # Check if any open PRs are linked to this issue + PR_NUMBERS=$(gh api \ + -H "Accept: application/vnd.github.mockingbird-preview+json" \ + "repos/$REPO/issues/$ISSUE/timeline" \ + --jq ".[] + | select(.event == \"cross-referenced\") + | select(.source.issue.pull_request != null) + | .source.issue.number" 2>/dev/null || true) + + OPEN_PR_FOUND="" + if [ -n "$PR_NUMBERS" ]; then + for PR_NUM in $PR_NUMBERS; do + PR_STATE=$(gh pr view "$PR_NUM" --repo "$REPO" --json state --jq '.state' 2>/dev/null || true) + if [ "$PR_STATE" = "OPEN" ]; then + OPEN_PR_FOUND="$PR_NUM" + break + fi + done + fi + + if [ -n "$OPEN_PR_FOUND" ]; then + echo "[KEEP] An OPEN PR #$OPEN_PR_FOUND is linked to this issue → skip reminder." + echo + continue + fi + + echo "[RESULT] No OPEN PRs linked to this issue." + + # Check if threshold has been reached + if [ "$DIFF_DAYS" -lt "$DAYS" ]; then + echo "[WAIT] Only $DIFF_DAYS days (< $DAYS) → not yet time for reminder." + echo + continue + fi + + echo "[REMIND] Issue #$ISSUE assigned for $DIFF_DAYS days, posting reminder." + + # Post reminder comment + MESSAGE="Hi, this is ReminderBot. This issue has been assigned but has had no pull request created. Are you still planning on working on the issue? + +From the Python SDK Team" + + gh issue comment "$ISSUE" --repo "$REPO" --body "$MESSAGE" + + echo "[DONE] Posted reminder comment on issue #$ISSUE." + echo +done + +echo "------------------------------------------------------------" +echo " Issue Reminder Bot (No PR) complete." +echo "------------------------------------------------------------" diff --git a/.github/workflows/bot-issue-reminder-no-pr.yml b/.github/workflows/bot-issue-reminder-no-pr.yml new file mode 100644 index 000000000..63b0d3103 --- /dev/null +++ b/.github/workflows/bot-issue-reminder-no-pr.yml @@ -0,0 +1,31 @@ +name: bot-issue-reminder-no-pr + +on: + schedule: + - cron: "0 10 * * *" + workflow_dispatch: + +permissions: + contents: read + issues: write + pull-requests: read + +jobs: + reminder: + runs-on: ubuntu-latest + + steps: + - name: Harden the runner + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@11bd71901afe3a2e8f79ce2a49e69702a58361bd + + - name: Post reminder on assigned issues with no PRs + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + DAYS: 7 + run: bash .github/scripts/issue_reminder_no_pr.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a598c2f0..c4ca5b316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Changelog +# Changelog All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org). @@ -7,21 +7,46 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. ## [Unreleased] ### Added + +- Modularized `transfer_transaction_fungible` example by introducing `account_balance_query()` & `transfer_transaction()`.Renamed `transfer_tokens()` → `main()` +- Phase 2 of the inactivity-unassign bot:Automatically detects stale open pull requests (no commit activity for 21+ days), comments with a helpful InactivityBot message, closes the stale PR, and unassigns the contributor from the linked issue. +- Added **str**() to CustomFixedFee and updated examples and tests accordingly. +- Added a github template for good first issues +- Added `.github/workflows/bot-assignment-check.yml` to limit non-maintainers to 2 concurrent issue assignments. +- Added all missing fields to **str**() method and updated `test_tokem_info.py` - Add examples/tokens/token_create_transaction_pause_key.py example demonstrating token pause/unpause behavior and pause key usage (#833) - Added `docs/sdk_developers/training/transaction_lifecycle.md` to explain the typical lifecycle of executing a transaction using the Hedera Python SDK. - Add inactivity bot workflow to unassign stale issue assignees (#952) +- Made custom fraction fee end to end +- feat: AccountCreateTransaction now supports both PrivateKey and PublicKey [#939](https://github.com/hiero-ledger/hiero-sdk-python/issues/939) +- Added Acceptance Criteria section to Good First Issue template for better contributor guidance (#997) +- Added **str**() to CustomRoyaltyFee and updated examples and tests accordingly (#986) +- Restore bug and feature request issue templates (#996)(https://github.com/hiero-ledger/hiero-sdk-python/issues/996) +- Support selecting specific node account ID(s) for queries and transactions and added `Network._get_node()` with updated execution flow (#362) +- Add TLS support with two-stage control (`set_transport_security()` and `set_verify_certificates()`) for encrypted connections to Hedera networks. TLS is enabled by default for hosted networks (mainnet, testnet, previewnet) and disabled for local networks (solo, localhost) (#855) +- Add PR inactivity reminder bot for stale pull requests `.github/workflows/pr-inactivity-reminder-bot.yml` +- Added Issue Reminder (no-PR) bot, `.github/scripts/issue_reminder_no_pr.sh` and `.github/workflows/bot-issue-reminder-no-pr.yml` to automatically detect assigned issues with no linked pull requests for 7+ days and post a gentle ReminderBot comment.(#951) + ### Changed -- + +- Allow `PublicKey` for `TokenUpdateKeys` in `TokenUpdateTransaction`, enabling non-custodial workflows where operators can build transactions using only public keys (#934). +- Bump protobuf toml to protobuf==6.33.2 +- Added more tests to the CustomFee class for different functionalities (#991) ### Fixed -- + +- Fixed inactivity bot workflow not checking out repository before running (#964) +- Fixed the topic_message_query integarion test +- good first issue template yaml rendering ### Breaking Change + - ## [0.1.10] - 2025-12-03 ### Added + - Added docs/sdk_developers/training/workflow: a training for developers to learn the workflow to contribute to the python SDK. - Added Improved NFT allowance deletion flow with receipt-based status checks and strict `SPENDER_DOES_NOT_HAVE_ALLOWANCE` verification. - Add `max_automatic_token_associations`, `staked_account_id`, `staked_node_id` and `decline_staking_reward` fields to `AccountUpdateTransaction` (#801) @@ -41,7 +66,6 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. - feat: Add string representation method for `CustomFractionalFee` class and update `custom_fractional_fee.py` example. - Moved query examples to their respective domain folders to improve structure matching. - ### Fixed - fixed workflow: changelog check with improved sensitivity to deletions, additions, new releases From 6e2add89f3a56ddc9c537c43ba93086fd91fbc15 Mon Sep 17 00:00:00 2001 From: prajeeta pal Date: Fri, 12 Dec 2025 23:53:04 +0530 Subject: [PATCH 2/3] feat: add issue reminder bot Signed-off-by: prajeeta pal --- .github/scripts/issue_reminder_no_pr.sh | 21 ++++++++++++------- .../workflows/bot-issue-reminder-no-pr.yml | 9 +++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/scripts/issue_reminder_no_pr.sh b/.github/scripts/issue_reminder_no_pr.sh index 075cd1609..70c45cba8 100644 --- a/.github/scripts/issue_reminder_no_pr.sh +++ b/.github/scripts/issue_reminder_no_pr.sh @@ -5,9 +5,11 @@ set -euo pipefail # GH_TOKEN - provided by GitHub Actions # REPO - owner/repo (fallback to GITHUB_REPOSITORY) # DAYS - reminder threshold in days (default 7) +# DRY_RUN - if "true", only log actions without posting comments REPO="${REPO:-${GITHUB_REPOSITORY:-}}" DAYS="${DAYS:-7}" +DRY_RUN="${DRY_RUN:-false}" if [ -z "$REPO" ]; then echo "ERROR: REPO environment variable not set." @@ -18,6 +20,7 @@ echo "------------------------------------------------------------" echo " Issue Reminder Bot (No PR)" echo " Repo: $REPO" echo " Threshold: $DAYS days" +echo " Dry Run: $DRY_RUN" echo "------------------------------------------------------------" echo @@ -71,20 +74,20 @@ for ISSUE in $ISSUES; do continue fi - # Get assignment time (use the last assigned event or issue creation time) + # Get assignment time (use the last assigned event) ASSIGN_TS=$(gh api "repos/$REPO/issues/$ISSUE/events" \ --jq ".[] | select(.event==\"assigned\") | .created_at" \ | tail -n1) if [ -z "$ASSIGN_TS" ]; then - echo "[WARN] No assignment event found, falling back to issue creation." - ASSIGN_TS=$(echo "$ISSUE_JSON" | jq -r '.created_at') + echo "[WARN] No assignment event found. Skipping." + continue fi ASSIGN_TS_SEC=$(parse_ts "$ASSIGN_TS") DIFF_DAYS=$(( (NOW_TS - ASSIGN_TS_SEC) / 86400 )) - echo "[INFO] Assigned/Created at: $ASSIGN_TS" + echo "[INFO] Assigned at: $ASSIGN_TS" echo "[INFO] Days since assignment: $DIFF_DAYS" # Check if any open PRs are linked to this issue @@ -129,9 +132,13 @@ for ISSUE in $ISSUES; do From the Python SDK Team" - gh issue comment "$ISSUE" --repo "$REPO" --body "$MESSAGE" - - echo "[DONE] Posted reminder comment on issue #$ISSUE." + if [ "$DRY_RUN" = "true" ]; then + echo "[DRY RUN] Would post comment on issue #$ISSUE:" + echo "$MESSAGE" + else + gh issue comment "$ISSUE" --repo "$REPO" --body "$MESSAGE" + echo "[DONE] Posted reminder comment on issue #$ISSUE." + fi echo done diff --git a/.github/workflows/bot-issue-reminder-no-pr.yml b/.github/workflows/bot-issue-reminder-no-pr.yml index 63b0d3103..8d9de13a4 100644 --- a/.github/workflows/bot-issue-reminder-no-pr.yml +++ b/.github/workflows/bot-issue-reminder-no-pr.yml @@ -4,6 +4,12 @@ on: schedule: - cron: "0 10 * * *" workflow_dispatch: + inputs: + dry_run: + description: "Dry run (log only, do not post comments)" + required: false + default: "true" + type: boolean permissions: contents: read @@ -21,11 +27,12 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@11bd71901afe3a2e8f79ce2a49e69702a58361bd + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #6.0.1 - name: Post reminder on assigned issues with no PRs env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} DAYS: 7 + DRY_RUN: ${{ inputs.dry_run }} run: bash .github/scripts/issue_reminder_no_pr.sh From 34ba63c935bebc89c76963e7b0d76018bdf1488f Mon Sep 17 00:00:00 2001 From: prajeeta pal Date: Sat, 13 Dec 2025 14:58:42 +0530 Subject: [PATCH 3/3] feat: inactivity bot edit Signed-off-by: prajeeta pal --- .github/workflows/bot-issue-reminder-no-pr.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bot-issue-reminder-no-pr.yml b/.github/workflows/bot-issue-reminder-no-pr.yml index 8d9de13a4..c9eee5600 100644 --- a/.github/workflows/bot-issue-reminder-no-pr.yml +++ b/.github/workflows/bot-issue-reminder-no-pr.yml @@ -2,19 +2,19 @@ name: bot-issue-reminder-no-pr on: schedule: - - cron: "0 10 * * *" + - cron: "0 10 * * *" #runs daily at 10:00 UTC workflow_dispatch: inputs: dry_run: description: "Dry run (log only, do not post comments)" required: false - default: "true" + default: true #safe default for manual testing type: boolean permissions: contents: read - issues: write - pull-requests: read + issues: write #needed to comment on issues + pull-requests: read #needed to check PR state jobs: reminder: