diff --git a/.github/workflows/notify-social.yml b/.github/workflows/notify-social.yml index 9511e9e9..24009dec 100644 --- a/.github/workflows/notify-social.yml +++ b/.github/workflows/notify-social.yml @@ -18,6 +18,10 @@ on: description: The release message type: string default: pyTermTk released + github-discussion-message: + description: The release message + type: string + default: pyTermTk released workflow_call: inputs: app: @@ -32,10 +36,14 @@ on: description: The release message type: string default: pyTermTk released + github-discussion-message: + description: The release message + type: string + default: pyTermTk released jobs: notify-discord: - # runs-on: ubuntu-latest + name: Notify Discord runs-on: self-hosted steps: - uses: actions/checkout@v4 @@ -54,5 +62,23 @@ jobs: run: | python tools/ci/social/notify_discord.py ${{ inputs.app }} ${{ inputs.version }} + notify-github-discussion: + name: Notify Github Discussion + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Deploy Github Discussion + env: + DISCUSSION_BODY: ${{ inputs.github-discussion-message}} + GH_DISCUSSION_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + run: | + export DISCUSSION_TITLE="${{ inputs.app }} ${{ inputs.version }} Released!!!" + python tools/ci/social/notify_github_discussion.py ${{ inputs.app }} ${{ inputs.version }} + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5172cde3..73936eec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -284,4 +284,26 @@ jobs: MESSAGE: ${{ matrix.release-notes }} DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} run: | - notify-discord ${{ matrix.name }} v${{ matrix.version }} \ No newline at end of file + notify-discord ${{ matrix.name }} v${{ matrix.version }} + - name: Notify ${{ matrix.name }} on Github Discussion + env: + RN: ${{ matrix.release-notes }} + MESSAGE: ${{ matrix.release-notes }} + GITHUB_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + GH_DISCUSSION_TOKEN: ${{ secrets.GH_DISCUSSION_TOKEN }} + run: | + notify-gh-discussion ${{ matrix.name }} v${{ matrix.version }} + - name: Notify ${{ matrix.name }} on Bluesky + env: + BLUESKY_APP_PWD: ${{ secrets.BLUESKY_APP_PWD }} + BLUESKY_APP_IDENTIFIER: ${{ secrets.BLUESKY_APP_IDENTIFIER }} + run: | + notify-bluesky ${{ matrix.name }} v${{ matrix.version }} + - name: Notify ${{ matrix.name }} on Twitter + env: + X_API_KEY: ${{ secrets.X_API_KEY }} + X_API_SECRET: ${{ secrets.X_API_SECRET }} + X_ACCESS_TOKEN: ${{ secrets.X_ACCESS_TOKEN }} + X_ACCESS_TOKEN_SECRET: ${{ secrets.X_ACCESS_TOKEN_SECRET }} + run: | + notify-twitter ${{ matrix.name }} ${{ matrix.version }} \ No newline at end of file diff --git a/tools/ci/ci_tools/social/notify_bluesky.py b/tools/ci/ci_tools/social/notify_bluesky.py index 28fe3ee6..fb5ee360 100644 --- a/tools/ci/ci_tools/social/notify_bluesky.py +++ b/tools/ci/ci_tools/social/notify_bluesky.py @@ -23,53 +23,114 @@ import os,sys import requests +import argparse from datetime import datetime +from pprint import pprint + +from typing import Dict current_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(current_dir) from social_common import get_social_data, SocialData, get_env_var -identifier=get_env_var('BLUESKY_APP_IDENTIFIER') -password=get_env_var('BLUESKY_APP_PWD') +import re +from typing import List, Dict + +def _get_facet(txt:str, slice:str, content:Dict ) -> Dict: + start = txt.index(slice) + end = start + len(slice) + return { + "index": { "byteStart": start, "byteEnd": end }, + "features": [ content ] + } + + +def main(): + parser = argparse.ArgumentParser(description="Send a Discord notification.") + parser.add_argument("app", type=str, help="The application name.") + parser.add_argument("version", type=str, help="The application version.") + args = parser.parse_args() + + data = get_social_data(args.app) + if not data: + raise ValueError(f"app: {args.app} is not recognised") + + identifier=get_env_var('BLUESKY_APP_IDENTIFIER') + password=get_env_var('BLUESKY_APP_PWD') -# Step 1: Authenticate and get access token -auth_response = requests.post( - "https://bsky.social/xrpc/com.atproto.server.createSession", + # Step 1: Authenticate and get access token + auth_response = requests.post( + "https://bsky.social/xrpc/com.atproto.server.createSession", + headers = { + "Content-Type": "application/json" + }, + json={ + "identifier": identifier, + "password": password + } + ) + + auth_data = auth_response.json() + # print(auth_data) + access_token = auth_data["accessJwt"] + did = auth_data["did"] + print(f"::add-mask::{did}") + + # Step 2: Post a message headers = { + "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" - }, - json={ - "identifier": identifier, - "password": password } -) - -auth_data = auth_response.json() -# print(auth_data) -access_token = auth_data["accessJwt"] -did = auth_data["did"] - -# Step 2: Post a message -headers = { - "Authorization": f"Bearer {access_token}", - "Content-Type": "application/json" -} - -post_data = { - "repo": did, - "collection": "app.bsky.feed.post", - "record": { - "$type": "app.bsky.feed.post", - "text": "Hello from my Python bot!", - "createdAt": datetime.now().isoformat() + "Z" + + text = f"{args.app} v{args.version} Released\n\n{data.link}\n\n#pyTermTk #TUI #Python #Linux #terminal" + + post_data = { + "repo": did, + "collection": "app.bsky.feed.post", + "record": { + "$type": "app.bsky.feed.post", + "text":text, + "facets": [ + _get_facet( + text, data.link, + { "uri": data.link , "$type": "app.bsky.richtext.facet#link" } + ), + _get_facet( + text, '#pyTermTk', + { "tag": 'pyTermTk' , "$type": "app.bsky.richtext.facet#tag" } + ), + _get_facet( + text, '#TUI', + { "tag": 'TUI' , "$type": "app.bsky.richtext.facet#tag" } + ), + _get_facet( + text, '#Python', + { "tag": 'Python' , "$type": "app.bsky.richtext.facet#tag" } + ), + _get_facet( + text, '#Linux', + { "tag": 'Linux' , "$type": "app.bsky.richtext.facet#tag" } + ), + _get_facet( + text, '#terminal', + { "tag": 'terminal' , "$type": "app.bsky.richtext.facet#tag" } + ), + ], + "createdAt": datetime.now().isoformat() + "Z" + } } -} -post_response = requests.post( - "https://bsky.social/xrpc/com.atproto.repo.createRecord", - headers=headers, - json=post_data -) + print('::group::Data') + pprint(post_data) + print('::endgroup::') + + post_response = requests.post( + "https://bsky.social/xrpc/com.atproto.repo.createRecord", + headers=headers, + json=post_data + ) -print(post_response.status_code) -print(post_response.json()) + print(post_response.status_code) + print(post_response.json()) +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/ci/ci_tools/social/notify_github_discussion.py b/tools/ci/ci_tools/social/notify_github_discussion.py index 0ac2d8a9..c46aa9bd 100644 --- a/tools/ci/ci_tools/social/notify_github_discussion.py +++ b/tools/ci/ci_tools/social/notify_github_discussion.py @@ -23,29 +23,21 @@ import os, sys import requests +import argparse current_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(current_dir) from social_common import get_social_data, SocialData, get_env_var - -# === CONFIGURATION === -GITHUB_TOKEN = get_env_var('GH_DISCUSSION_TOKEN') -REPO_OWNER = "ceccopierangiolieugenio" -REPO_NAME = "pyTermTk" -DICSUSSION_CATEGORY="announcements" -DISCUSSION_TITLE = "Your Announcement Title" -DISCUSSION_BODY = "This is the content of your announcement." - # === FUNCTIONS === def get_repo_id(owner, repo, token): query = f""" - query {{ - repository(owner: "{owner}", name: "{repo}") {{ - id + query {{ + repository(owner: "{owner}", name: "{repo}") {{ + id + }} }} - }} """ headers = { "Authorization": f"Bearer {token}", @@ -54,7 +46,7 @@ def get_repo_id(owner, repo, token): response = requests.post('https://api.github.com/graphql', json={'query': query}, headers=headers) return response.json()['data']['repository']['id'] -def get_category_id(repo_id, token): +def get_category_id(repo_id, token, discussion_category): query = f""" query {{ node(id: "{repo_id}") {{ @@ -76,7 +68,7 @@ def get_category_id(repo_id, token): response = requests.post('https://api.github.com/graphql', json={'query': query}, headers=headers) categories = response.json()['data']['node']['discussionCategories']['nodes'] for category in categories: - if category['name'].lower() == DICSUSSION_CATEGORY: + if category['name'].lower() == discussion_category: return category['id'] raise Exception("Announcements category not found") @@ -107,13 +99,41 @@ def create_discussion(repo_id, category_id, title, body, token): # === MAIN EXECUTION === -try: - repo_id = get_repo_id(REPO_OWNER, REPO_NAME, GITHUB_TOKEN) - print(f"{repo_id=}") - category_id = get_category_id(repo_id, GITHUB_TOKEN) - print(f"{category_id=}") - discussion_url = create_discussion(repo_id, category_id, DISCUSSION_TITLE, DISCUSSION_BODY, GITHUB_TOKEN) - print(f"{discussion_url=}") - print(f"✅ Discussion created successfully: {discussion_url}") -except Exception as e: - print(f"❌ Error: {e}") + +def main(): + parser = argparse.ArgumentParser(description="Send a Discord notification.") + parser.add_argument("app", type=str, help="The application name.") + parser.add_argument("version", type=str, help="The application version.") + args = parser.parse_args() + + # === CONFIGURATION === + GITHUB_TOKEN = get_env_var('GITHUB_TOKEN') + GH_DISCUSSION_TOKEN = get_env_var('GH_DISCUSSION_TOKEN') + REPO_OWNER = "ceccopierangiolieugenio" + REPO_NAME = "pyTermTk" + DICSUSSION_CATEGORY="announcements" + DISCUSSION_BODY = get_env_var('RN') + + data = get_social_data(args.app) + if not data: + raise ValueError(f"app: {args.app} is not recognised") + + try: + repo_id = get_repo_id(REPO_OWNER, REPO_NAME, GITHUB_TOKEN) + print(f"{repo_id=}") + category_id = get_category_id(repo_id, GITHUB_TOKEN, DICSUSSION_CATEGORY) + print(f"{category_id=}") + discussion_url = create_discussion( + repo_id, category_id, + f"{args.app} {args.version} Released!!!", + DISCUSSION_BODY, GH_DISCUSSION_TOKEN) + print(f"{discussion_url=}") + print(f"✅ Discussion created successfully: {discussion_url}") + except Exception as e: + print(f"❌ Error: {e}") + +if __name__ == "__main__": + main() + + + diff --git a/tools/ci/ci_tools/social/notify_twitter.py b/tools/ci/ci_tools/social/notify_twitter.py index d9df52b8..351b92c4 100644 --- a/tools/ci/ci_tools/social/notify_twitter.py +++ b/tools/ci/ci_tools/social/notify_twitter.py @@ -32,23 +32,21 @@ current_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(current_dir) from social_common import get_social_data, SocialData, get_env_var -def send_twitter_message(version: str, data:SocialData): +def send_twitter_message(version: str, data:SocialData, text:str): api_key = get_env_var("X_API_KEY") api_secret = get_env_var("X_API_SECRET") access_token = get_env_var("X_ACCESS_TOKEN") access_token_secret = get_env_var("X_ACCESS_TOKEN_SECRET") - message = get_env_var("MESSAGE") - twitter = OAuth1Session(api_key, api_secret, access_token, access_token_secret) - payload = {"text": "Hello from OAuth 1.0a!"} + payload = {"text": text} response = twitter.post("https://api.twitter.com/2/tweets", json=payload) print(response.status_code) print(response.json()) -if __name__ == "__main__": +def main(): parser = argparse.ArgumentParser(description="Send a Twitter/X notification.") parser.add_argument("app", type=str, help="The application name.") parser.add_argument("version", type=str, help="The application version.") @@ -58,4 +56,8 @@ if __name__ == "__main__": if not data: raise ValueError(f"app: {args.app} is not recognised") - send_twitter_message(args.version, data) \ No newline at end of file + text = f"{args.app} v{args.version} Released\n\n{data.link}\n\n#pyTermTk #TUI #Python #Linux #terminal" + send_twitter_message(args.version, data, text) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/ci/pyproject.toml b/tools/ci/pyproject.toml index aede014d..84c330bc 100644 --- a/tools/ci/pyproject.toml +++ b/tools/ci/pyproject.toml @@ -16,12 +16,17 @@ [project.optional-dependencies] social = [ - 'discord.py==2.5.2' + 'discord.py==2.5.2', + 'requests==2.32.5', + 'requests_oauthlib==2.0.0' ] [project.scripts] release-helper = "ci_tools.release_helper:main" notify-discord = "ci_tools.social.notify_discord:main" + notify-bluesky = "ci_tools.social.notify_bluesky:main" + notify-twitter = "ci_tools.social.notify_twitter:main" + notify-gh-discussion = "ci_tools.social.notify_github_discussion:main" [tool.setuptools] packages = ["ci_tools"] \ No newline at end of file diff --git a/tools/run.scratchpad.sh b/tools/run.scratchpad.sh new file mode 100755 index 00000000..493d2bd2 --- /dev/null +++ b/tools/run.scratchpad.sh @@ -0,0 +1,12 @@ +REPO="ceccopierangiolieugenio/pyTermTk" +WORKFLOW="_scratchpad.yml" +REF=$(git branch --show-current) +WORKFLOW_URL="https://github.com/$REPO/actions/workflows/$WORKFLOW" + +curl -s -X POST \ +-H "Accept: application/vnd.github+json" \ +-H "Authorization: Bearer $GITHUB_TOKEN" \ +"https://api.github.com/repos/$REPO/actions/workflows/$WORKFLOW/dispatches" \ +-d "{\"ref\":\"$REF\"}" + +echo "Scratchpad workflow triggered from branch $REF. You can monitor the progress here: $WORKFLOW_URL" \ No newline at end of file