Initial commit
Based on JMusicBot by jagrosh (https://github.com/jagrosh/MusicBot), via the maintained fork by arif-banai (https://github.com/arif-banai/MusicBot). See NOTICE for attribution. Licensed under Apache 2.0. YouTube-side changes had broken playback entirely. Fixes: - Apply the remote signature cipher independently of OAuth. Deciphering and authentication are unrelated, but the cipher was only configured when OAuth was enabled, so the default install fell back to the bundled extractor and failed with "Must find sig function from script: /s/player/<id>/...". Now always applied, and configurable via playback.youtube.remoteCipher.url. - Add the ANDROID client and order the client list by reliability. ANDROID is currently the only client YouTube reliably streams to anonymously. WEB moves last, as YouTube answers it with SABR-only responses carrying no playable format ("No supported audio streams available, available types:"). - Detect sign-in failures across the whole exception graph. The existing hint could never fire: it matched the aggregate AllClientsFailedException message rather than the per-client reasons, and compared against an ASCII apostrophe while YouTube sends U+2019. - Track youtube-source past 1.18.2, which predates the TV client User-Agent fix and the contentLength recovery fix. Measured on previously failing tracks: 5/5 playing, stable across repeat runs (was 1/5). No OAuth required. Adds 17 tests; 676 unit + 48 integration pass.
This commit is contained in:
commit
0941987234
295 files changed
+58207
No files matched your search
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at j@gro.sh. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: arif-banai
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Bug Report
|
||||
description: Report a bug or unexpected behavior
|
||||
title: "[Bug Report] Title Here "
|
||||
labels: []
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: Please provide a clear and concise description of what the bug is.
|
||||
placeholder: The bug is...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Provide steps you can take to reliably reproduce this bug
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Result
|
||||
description: What did you expect to happen when this bug occurred?
|
||||
placeholder: I expected...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: debug
|
||||
attributes:
|
||||
label: Debug Output
|
||||
description: Please run the `debug` command and paste it here or upload it as an attachment. If you cannot run the command, please provide information about your system such as operating system version, JMusicBot version, etc.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-info
|
||||
attributes:
|
||||
label: Additional Info
|
||||
description: Any additional info that you think might be useful or relevant to this bug
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Before submitting your issue, please affirm the following
|
||||
options:
|
||||
- label: I have looked for information about this [within the documentation](https://jmusicbot.com/)
|
||||
required: true
|
||||
- label: I have searched for similar issues [on the issues page](https://github.com/arif-banai/MusicBot/issues?q=is%3Aissue)
|
||||
required: true
|
||||
- label: I am using an official release from [the releases page](https://github.com/arif-banai/MusicBot/releases)
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: JMusicBot Version
|
||||
description: Which version of JMusicBot are you running?
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://github.com/arif-banai/MusicBot/discussions/categories/general
|
||||
about: For general questions, please check the Discussions
|
||||
- name: Troubleshooting
|
||||
url: https://github.com/arif-banai/MusicBot/discussions/categories/help
|
||||
about: For troubleshooting help, please check the Discussions
|
||||
@@ -0,0 +1,52 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature Request] Title Here"
|
||||
labels: []
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: Please provide a clear and concise description of what the problem is.
|
||||
placeholder: A common problem users face is...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: What is your ideal solution to the problem?
|
||||
description: Describe a clear and concise description of what you want to happen.
|
||||
placeholder: My solution to the above problem is...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: procedure
|
||||
attributes:
|
||||
label: How would this feature be used?
|
||||
description: Describe the basic flow/steps of using this feature.
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-info
|
||||
attributes:
|
||||
label: Additional Info
|
||||
description: Any additional info that you think might be useful or relevant to this feature request
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Before submitting your issue, please affirm the following
|
||||
options:
|
||||
- label: I have checked [the documentation](https://jmusicbot.com/) to make sure this feature doesn't already exist
|
||||
required: true
|
||||
- label: I have searched for [similar feature requests](https://github.com/arif-banai/MusicBot/labels/enhancement)
|
||||
required: true
|
||||
- label: I have confirmed that my requests is **not** on the [list of things that will not be added](https://jmusicbot.com/things-that-wont-be-added)
|
||||
required: true
|
||||
- label: "I am running the latest version of the bot: "
|
||||
required: true
|
||||
@@ -0,0 +1,14 @@
|
||||
### This pull request...
|
||||
- [ ] Fixes a bug
|
||||
- [ ] Introduces a new feature
|
||||
- [ ] Improves an existing feature
|
||||
- [ ] Boosts code quality or performance
|
||||
|
||||
### Description
|
||||
Replace this with a description of the pull request
|
||||
|
||||
### Purpose
|
||||
Replace this with a description of the problem or use-case
|
||||
|
||||
### Relevant Issue(s)
|
||||
This PR closes issue #...
|
||||
@@ -0,0 +1,71 @@
|
||||
name: 'Docker Build and Push'
|
||||
description: 'Builds and pushes a multi-arch Docker image to GHCR with caching'
|
||||
|
||||
inputs:
|
||||
tags:
|
||||
description: 'Docker image tags (from metadata-action)'
|
||||
required: true
|
||||
annotations:
|
||||
description: 'OCI annotations from metadata-action (for multi-arch images)'
|
||||
required: true
|
||||
timestamp:
|
||||
description: 'Build timestamp in ISO 8601 format'
|
||||
required: true
|
||||
registry:
|
||||
description: 'Container registry (e.g., ghcr.io)'
|
||||
required: false
|
||||
default: 'ghcr.io'
|
||||
image-name:
|
||||
description: 'Image name (e.g., arif-banai/musicbot)'
|
||||
required: false
|
||||
default: 'arif-banai/musicbot'
|
||||
token:
|
||||
description: 'GitHub token for authentication'
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
digest:
|
||||
description: 'Docker image digest'
|
||||
value: ${{ steps.build.outputs.digest }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver-opts: |
|
||||
network=host
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ inputs.registry }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ inputs.token }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ inputs.tags }}
|
||||
annotations: ${{ inputs.annotations }}
|
||||
cache-from: |
|
||||
type=gha
|
||||
type=registry,ref=${{ inputs.registry }}/${{ inputs.image-name }}:buildcache
|
||||
cache-to: |
|
||||
type=gha,mode=max
|
||||
type=registry,ref=${{ inputs.registry }}/${{ inputs.image-name }}:buildcache,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
BUILD_TIMESTAMP=${{ inputs.timestamp }}
|
||||
|
||||
- name: Image digest
|
||||
shell: bash
|
||||
run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}"
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Build and Test
|
||||
|
||||
# Build and test on PRs and master pushes
|
||||
# This workflow does NOT publish Docker images - that's handled by docker-publish.yml
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master # PRs targeting trunk
|
||||
push:
|
||||
branches:
|
||||
- master # Direct pushes to master
|
||||
paths-ignore:
|
||||
- '.github/**' # Ignore GitHub config changes (workflows, actions, templates, etc.)
|
||||
- 'README.md'
|
||||
- 'LICENSE'
|
||||
- 'CNAME'
|
||||
- 'docs/**'
|
||||
- '*.example.yml'
|
||||
- '*.example.yaml'
|
||||
- '.circleci/**' # If not using CircleCI, ignore its config
|
||||
|
||||
# Cancel in-progress runs for the same workflow and branch
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read # Only need to checkout repository
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Java Environment
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
|
||||
- name: Build and Test with Coverage
|
||||
run: |
|
||||
COMMIT_TIME=$(git log -1 --format=%cI)
|
||||
mvn --batch-mode verify -Pcoverage -Dproject.build.outputTimestamp=$COMMIT_TIME
|
||||
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
report_type: coverage
|
||||
files: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results to Codecov
|
||||
if: ${{ !cancelled() }}
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
report_type: test_results
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -0,0 +1,120 @@
|
||||
name: Publish Docker Image
|
||||
|
||||
# Docker publishing workflow - gated by successful build-and-test completion
|
||||
# This ensures images are only published after tests pass on master
|
||||
on:
|
||||
# Triggered after successful build-and-test on master
|
||||
# Note: The branches filter checks workflow_run.head_branch, which is the source branch
|
||||
# of the triggering workflow. For PRs targeting master, head_branch is the PR source
|
||||
# branch (not master), so this filter already excludes PR-triggered builds. Only direct
|
||||
# pushes to master will have head_branch == 'master' and trigger this workflow.
|
||||
workflow_run:
|
||||
workflows: ["Build and Test"]
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- master
|
||||
# Also trigger on version tags (v*) for releases
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
# Cancel in-progress runs for the same workflow and ref
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: arif-banai/musicbot
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
# Only run if the triggering workflow succeeded (for workflow_run events)
|
||||
# For tag pushes, always run
|
||||
if: |
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') ||
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # Required for pushing to GHCR
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
# For workflow_run events, checkout the commit that triggered the original workflow
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
|
||||
fetch-depth: 0 # Full history needed for git commands
|
||||
|
||||
- name: Extract metadata
|
||||
id: metadata
|
||||
run: |
|
||||
# Extract project version from pom.xml
|
||||
POM_VERSION=$(grep -m 1 '<version>' pom.xml | sed 's/.*<version>\([^<]*\)<\/version>.*/\1/' | tr -d ' ')
|
||||
SHA_SHORT=$(git rev-parse --short HEAD)
|
||||
TIMESTAMP=$(git log -1 --format=%cI)
|
||||
|
||||
# Determine if this is a tag build or master branch build
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref_type }}" == "tag" ]]; then
|
||||
# For tag builds, use the git tag version (strip 'v' prefix)
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
IS_TAG_BUILD="true"
|
||||
else
|
||||
# For master builds, use pom.xml version
|
||||
VERSION="$POM_VERSION"
|
||||
IS_TAG_BUILD="false"
|
||||
fi
|
||||
|
||||
# Check if version is a SNAPSHOT
|
||||
if [[ "$VERSION" =~ -SNAPSHOT$ ]]; then
|
||||
IS_SNAPSHOT="true"
|
||||
else
|
||||
IS_SNAPSHOT="false"
|
||||
fi
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "pom_version=$POM_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$SHA_SHORT" >> "$GITHUB_OUTPUT"
|
||||
echo "timestamp=$TIMESTAMP" >> "$GITHUB_OUTPUT"
|
||||
echo "is_tag_build=$IS_TAG_BUILD" >> "$GITHUB_OUTPUT"
|
||||
echo "is_snapshot=$IS_SNAPSHOT" >> "$GITHUB_OUTPUT"
|
||||
echo "Extracted version: $VERSION (pom: $POM_VERSION), SHA: $SHA_SHORT, is_tag: $IS_TAG_BUILD, is_snapshot: $IS_SNAPSHOT"
|
||||
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
env:
|
||||
# For multi-arch images, annotations must be attached to the image index
|
||||
# GitHub Packages reads annotations from the index level for multi-arch images
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# Tag rules:
|
||||
# - For tag builds: version tag (from git tag) + sha tag
|
||||
# - For master builds: latest + sha + version (if not SNAPSHOT)
|
||||
tags: |
|
||||
type=raw,value=${{ steps.metadata.outputs.version }},enable=${{ steps.metadata.outputs.is_tag_build == 'true' }}
|
||||
type=raw,value=latest,enable=${{ steps.metadata.outputs.is_tag_build == 'false' }}
|
||||
type=raw,value=${{ steps.metadata.outputs.version }},enable=${{ steps.metadata.outputs.is_tag_build == 'false' && steps.metadata.outputs.is_snapshot == 'false' }}
|
||||
type=raw,value=sha-${{ steps.metadata.outputs.sha }}
|
||||
# Labels are automatically generated from git context by metadata-action
|
||||
# No need to specify them - metadata-action will create standard OCI labels
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build
|
||||
uses: ./.github/actions/docker-build
|
||||
with:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
timestamp: ${{ steps.metadata.outputs.timestamp }}
|
||||
registry: ${{ env.REGISTRY }}
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}"
|
||||
@@ -0,0 +1,153 @@
|
||||
name: Make Release
|
||||
|
||||
# Manual workflow to create a GitHub release
|
||||
# This sets the version, builds the JAR, and creates a release with tag
|
||||
# The tag will trigger docker-publish.yml to build and push the Docker image
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_number:
|
||||
description: 'Version Number (e.g., 0.6.3)'
|
||||
required: true
|
||||
type: string
|
||||
info:
|
||||
description: 'Description of this Release'
|
||||
required: true
|
||||
type: string
|
||||
is_prerelease:
|
||||
description: 'Mark this as a pre-release (for testing)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
ref:
|
||||
description: 'Branch, tag, or SHA to build from (defaults to the branch this workflow is run from)'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
# Prevent concurrent releases
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build_jar:
|
||||
name: Build JAR
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write # Required to push version commit
|
||||
outputs:
|
||||
release_sha: ${{ steps.set-release-sha.outputs.sha }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref || github.ref }}
|
||||
fetch-depth: 0 # Full history needed for git commands
|
||||
|
||||
- name: Validate branch for regular releases
|
||||
if: ${{ github.event.inputs.is_prerelease != 'true' }}
|
||||
run: |
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [ "$BRANCH" != "master" ]; then
|
||||
echo "❌ Error: Regular releases can only be created from the 'master' branch."
|
||||
echo " Current branch: $BRANCH"
|
||||
echo " To create a pre-release from this branch, check the 'Mark this as a pre-release' option."
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Branch validation passed: $BRANCH"
|
||||
|
||||
- name: Set up Java Environment
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
|
||||
- name: Get initial SHA (for pre-releases)
|
||||
id: get-initial-sha
|
||||
if: ${{ github.event.inputs.is_prerelease == 'true' }}
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set Version
|
||||
run: |
|
||||
mvn -B versions:set -DnewVersion=${{ github.event.inputs.version_number }}
|
||||
mvn -B versions:commit
|
||||
|
||||
- name: Commit version bump
|
||||
if: ${{ github.event.inputs.is_prerelease != 'true' }}
|
||||
id: commit-version
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Stage whatever the version plugin changed
|
||||
git add pom.xml
|
||||
|
||||
# If nothing is staged, don't fail the workflow
|
||||
if git diff --cached --quiet; then
|
||||
echo "No version changes to commit (pom.xml already at ${{ github.event.inputs.version_number }})."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "chore: bump version to ${{ github.event.inputs.version_number }}"
|
||||
git push
|
||||
|
||||
|
||||
- name: Set release SHA output
|
||||
id: set-release-sha
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.is_prerelease }}" == "true" ]; then
|
||||
echo "sha=${{ steps.get-initial-sha.outputs.sha }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build with Maven
|
||||
run: |
|
||||
COMMIT_TIME=$(git log -1 --format=%cI)
|
||||
mvn --batch-mode verify -Dproject.build.outputTimestamp=$COMMIT_TIME
|
||||
|
||||
- name: Rename JAR
|
||||
run: mv target/*-All.jar JMusicBot-${{ github.event.inputs.version_number }}.jar
|
||||
|
||||
- name: Upload JAR artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: jar
|
||||
path: JMusicBot-${{ github.event.inputs.version_number }}.jar
|
||||
if-no-files-found: error
|
||||
|
||||
create_release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
needs: build_jar
|
||||
permissions:
|
||||
contents: write # Required to create releases and tags
|
||||
steps:
|
||||
- name: Download JAR artifact
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: jar
|
||||
path: .
|
||||
|
||||
- name: Create Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: v${{ github.event.inputs.version_number }}
|
||||
commit: ${{ needs.build_jar.outputs.release_sha }}
|
||||
name: v${{ github.event.inputs.version_number }}
|
||||
draft: true
|
||||
prerelease: ${{ github.event.inputs.is_prerelease }}
|
||||
artifacts: "*.jar"
|
||||
body: |
|
||||
${{ github.event.inputs.info }}
|
||||
|
||||
---
|
||||
### Setup
|
||||
https://jmusicbot.com/setup
|
||||
https://jmusicbot.com/config
|
||||
# Download: [JMusicBot-${{ github.event.inputs.version_number }}.jar](https://github.com/arif-banai/MusicBot/releases/download/v${{ github.event.inputs.version_number }}/JMusicBot-${{ github.event.inputs.version_number }}.jar)
|
||||
@@ -0,0 +1,131 @@
|
||||
name: Publish Preview Image
|
||||
|
||||
# Optional workflow for manually publishing preview Docker images from any branch/tag/SHA
|
||||
# This is separate from the main CI/CD pipeline and allows explicit preview builds
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'Git ref to build (branch name, tag, or SHA)'
|
||||
required: true
|
||||
type: string
|
||||
tag:
|
||||
description: 'Optional tag suffix override (if empty, derived from ref)'
|
||||
required: false
|
||||
type: string
|
||||
run_tests:
|
||||
description: 'Run Maven tests before building Docker image'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
# Cancel in-progress runs for the same workflow and ref
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.inputs.ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: arif-banai/musicbot
|
||||
|
||||
jobs:
|
||||
publish-preview:
|
||||
name: Publish Preview Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # Required for pushing to GHCR
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
fetch-depth: 0 # Full history needed for git commands
|
||||
|
||||
- name: Extract metadata
|
||||
id: metadata
|
||||
run: |
|
||||
# Extract project version from pom.xml
|
||||
VERSION=$(grep -m 1 '<version>' pom.xml | sed 's/.*<version>\([^<]*\)<\/version>.*/\1/' | tr -d ' ')
|
||||
SHA_SHORT=$(git rev-parse --short HEAD)
|
||||
TIMESTAMP=$(git log -1 --format=%cI)
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$SHA_SHORT" >> "$GITHUB_OUTPUT"
|
||||
echo "timestamp=$TIMESTAMP" >> "$GITHUB_OUTPUT"
|
||||
echo "Extracted version: $VERSION, SHA: $SHA_SHORT"
|
||||
|
||||
- name: Set up Java Environment
|
||||
# Run if run_tests is true (default) or explicitly set to true
|
||||
if: ${{ github.event.inputs.run_tests != 'false' }}
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
|
||||
- name: Run Maven tests
|
||||
# Run if run_tests is true (default) or explicitly set to true
|
||||
if: ${{ github.event.inputs.run_tests != 'false' }}
|
||||
run: |
|
||||
COMMIT_TIME=$(git log -1 --format=%cI)
|
||||
mvn --batch-mode verify -Pdocker -Dproject.build.outputTimestamp=$COMMIT_TIME
|
||||
|
||||
- name: Sanitize tag suffix
|
||||
id: sanitize
|
||||
run: |
|
||||
# Determine the tag suffix: use input.tag if provided, otherwise derive from ref
|
||||
if [[ -n "${{ github.event.inputs.tag }}" ]]; then
|
||||
TAG_SUFFIX="${{ github.event.inputs.tag }}"
|
||||
else
|
||||
# Derive from ref: strip refs/heads/, refs/tags/, refs/ prefixes
|
||||
REF="${{ github.event.inputs.ref }}"
|
||||
REF="${REF#refs/heads/}"
|
||||
REF="${REF#refs/tags/}"
|
||||
REF="${REF#refs/}"
|
||||
TAG_SUFFIX="$REF"
|
||||
fi
|
||||
|
||||
# Sanitize: lowercase, replace '/' with '-', strip invalid chars to [a-z0-9._-]
|
||||
# Truncate to 50 characters to avoid tag length issues
|
||||
SANITIZED=$(echo "$TAG_SUFFIX" | tr '[:upper:]' '[:lower:]' | tr '/' '-' | tr -cd '[:alnum:]._-' | cut -c1-50)
|
||||
|
||||
echo "suffix=$SANITIZED" >> "$GITHUB_OUTPUT"
|
||||
echo "Sanitized tag suffix: $SANITIZED"
|
||||
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
env:
|
||||
# For multi-arch images, annotations must be attached to the image index
|
||||
# GitHub Packages reads annotations from the index level for multi-arch images
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# Tag rules for preview images:
|
||||
# - preview-<sanitized-ref> tag
|
||||
# - sha-<short> immutable tag
|
||||
tags: |
|
||||
type=raw,value=preview-${{ steps.sanitize.outputs.suffix }}
|
||||
type=raw,value=sha-${{ steps.metadata.outputs.sha }}
|
||||
# Labels are automatically generated from git context by metadata-action
|
||||
# No need to specify them - metadata-action will create standard OCI labels
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build
|
||||
uses: ./.github/actions/docker-build
|
||||
with:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
timestamp: ${{ steps.metadata.outputs.timestamp }}
|
||||
registry: ${{ env.REGISTRY }}
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Print final tags
|
||||
run: |
|
||||
echo "Successfully published preview Docker image"
|
||||
echo "Tags: ${{ steps.meta.outputs.tags }}"
|
||||
echo "Image digest: ${{ steps.build.outputs.digest }}"
|
||||
@@ -0,0 +1,69 @@
|
||||
name: Validate Branch Naming
|
||||
|
||||
# Validates branch naming conventions for feature/fix/chore/deps/release branches
|
||||
# Runs only on pull requests
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
# Cancel in-progress runs for the same PR
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
validate-branch-name:
|
||||
name: Validate Branch Naming Convention
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Validate branch name
|
||||
run: |
|
||||
# Extract branch name from pull request head ref
|
||||
BRANCH_NAME="${{ github.head_ref }}"
|
||||
|
||||
echo "Validating branch: $BRANCH_NAME"
|
||||
|
||||
# Check each allowed pattern
|
||||
VALID=false
|
||||
|
||||
# feature/<slug>
|
||||
if [[ "$BRANCH_NAME" =~ ^feature/[a-z0-9-]+$ ]]; then
|
||||
VALID=true
|
||||
echo "✓ Branch name matches pattern: feature/<slug>"
|
||||
# fix/<slug>
|
||||
elif [[ "$BRANCH_NAME" =~ ^fix/[a-z0-9-]+$ ]]; then
|
||||
VALID=true
|
||||
echo "✓ Branch name matches pattern: fix/<slug>"
|
||||
# chore/<slug>
|
||||
elif [[ "$BRANCH_NAME" =~ ^chore/[a-z0-9-]+$ ]]; then
|
||||
VALID=true
|
||||
echo "✓ Branch name matches pattern: chore/<slug>"
|
||||
# deps/<slug>
|
||||
elif [[ "$BRANCH_NAME" =~ ^deps/[a-z0-9-]+$ ]]; then
|
||||
VALID=true
|
||||
echo "✓ Branch name matches pattern: deps/<slug>"
|
||||
# release/<version> (e.g., release/0.6.3 or release/0.6.3-rc1)
|
||||
elif [[ "$BRANCH_NAME" =~ ^release/[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9-]+)?$ ]]; then
|
||||
VALID=true
|
||||
echo "✓ Branch name matches pattern: release/<version>"
|
||||
fi
|
||||
|
||||
if [[ "$VALID" == "false" ]]; then
|
||||
echo "❌ Branch name '$BRANCH_NAME' does not match allowed patterns:"
|
||||
echo ""
|
||||
echo "Allowed branch patterns:"
|
||||
echo " - feature/<slug> (e.g., feature/new-player-ui)"
|
||||
echo " - fix/<slug> (e.g., fix/youtube-oauth)"
|
||||
echo " - chore/<slug> (e.g., chore/update-dependencies)"
|
||||
echo " - deps/<slug> (e.g., deps/youtube-source-pr195)"
|
||||
echo " - release/<version> (e.g., release/0.6.3 or release/0.6.3-rc1)"
|
||||
echo ""
|
||||
echo "Branch names should:"
|
||||
echo " - Use lowercase letters, numbers, and hyphens only"
|
||||
echo " - Follow the pattern: <type>/<descriptive-slug>"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Branch name is valid!"
|
||||
Reference in new issue
Block a user