Skip to content
Merged
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
68 changes: 57 additions & 11 deletions .github/workflows/HostnameRedaction.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
EVENT_NAME: ${{ github.event_name }}
REPO: ${{ github.repository }}
SENDER: ${{ github.event.sender.login }}
GITHUB_ACTOR: ${{ github.actor }}
# Issue fields
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_TITLE: ${{ github.event.issue.title }}
Expand All @@ -43,13 +44,15 @@ jobs:
import os
import re
import subprocess
import time
import urllib.request

# Load environment
HOSTNAMES_URL = os.environ.get("HOSTNAMES_URL", "")
EVENT_NAME = os.environ.get("EVENT_NAME", "")
REPO = os.environ.get("REPO", "")
SENDER = os.environ.get("SENDER", "")
GITHUB_ACTOR = os.environ.get("GITHUB_ACTOR", "")

ISSUE_BODY = os.environ.get("ISSUE_BODY") or ""
ISSUE_TITLE = os.environ.get("ISSUE_TITLE") or ""
Expand All @@ -75,9 +78,16 @@ jobs:
else:
NUMBER = ISSUE_NUMBER

# Prevent infinite loop when the action itself edits
if SENDER.endswith("[bot]"):
print(f"Edit by bot ({SENDER}), skipping")
# Prevent self-trigger loops when actions edit/comment.
sender_lower = (SENDER or "").lower().strip()
actor_lower = (GITHUB_ACTOR or "").lower().strip()
if (
sender_lower.endswith("[bot]")
or sender_lower == "github-actions"
or actor_lower.endswith("[bot]")
or actor_lower == "github-actions"
):
print(f"Event by bot (sender={SENDER}, actor={GITHUB_ACTOR}), skipping")
exit(0)

if not HOSTNAMES_URL:
Expand All @@ -88,14 +98,25 @@ jobs:
print("Could not determine issue/PR number, skipping")
exit(0)

# Fetch hostname list
try:
req = urllib.request.Request(HOSTNAMES_URL, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
hostnames_data = resp.read().decode("utf-8")
except Exception as e:
print(f"Failed to fetch hostnames: {e}")
exit(1)
# Fetch hostname list (retry to avoid transient network timeouts)
hostnames_data = ""
fetch_error = None
for attempt in range(1, 4):
try:
req = urllib.request.Request(HOSTNAMES_URL, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=10) as resp:
hostnames_data = resp.read().decode("utf-8")
fetch_error = None
break
except Exception as e:
fetch_error = e
print(f"Failed to fetch hostnames (attempt {attempt}/3): {e}")
if attempt < 3:
time.sleep(attempt * 2)

if not hostnames_data:
print(f"Failed to fetch hostnames after retries, skipping: {fetch_error}")
exit(0)

# Parse hostname list into domain_base -> alias mapping
domain_to_alias = {}
Expand Down Expand Up @@ -133,6 +154,31 @@ jobs:

def post_warning(count, target_type):
"""Post a warning comment about redacted hostnames."""
warning_marker = f"automatically redacted from this {target_type}"

# Avoid duplicate warning comments if this workflow is retriggered.
try:
existing_comments_raw = subprocess.check_output(
["gh", "api", f"/repos/{REPO}/issues/{NUMBER}/comments"],
text=True,
)
existing_comments = json.loads(existing_comments_raw)
except Exception as e:
print(f"Could not load existing comments for deduplication: {e}")
existing_comments = []

for comment in existing_comments:
comment_body = (comment.get("body") or "").lower()
user_login = ((comment.get("user") or {}).get("login") or "").lower()
if warning_marker in comment_body and (
user_login == "github-actions[bot]"
or user_login == "github-actions"
):
print(
f"Warning already posted for this {target_type}; skipping duplicate comment"
)
return

if count == 1:
msg = f"⚠️ **1 hostname was automatically redacted from this {target_type}.**"
else:
Expand Down
17 changes: 6 additions & 11 deletions .github/workflows/PullRequests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:

version:
needs: [ quality-check ]
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.event_name == 'pull_request' && github.head_ref == 'dev' && github.base_ref == 'main') || ((github.event_name == 'workflow_dispatch' || github.event_name == 'push') && github.ref_name == 'dev'))
runs-on: ubuntu-latest
timeout-minutes: 1
outputs:
Expand All @@ -78,7 +78,7 @@ jobs:

build-wheel:
needs: [ quality-check, version ]
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.event_name == 'pull_request' && github.head_ref == 'dev' && github.base_ref == 'main') || ((github.event_name == 'workflow_dispatch' || github.event_name == 'push') && github.ref_name == 'dev'))
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
Expand Down Expand Up @@ -109,7 +109,7 @@ jobs:

build-exe:
needs: [ quality-check, version ]
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.event_name == 'pull_request' && github.head_ref == 'dev' && github.base_ref == 'main') || ((github.event_name == 'workflow_dispatch' || github.event_name == 'push') && github.ref_name == 'dev'))
runs-on: windows-latest
timeout-minutes: 3
env:
Expand Down Expand Up @@ -208,7 +208,7 @@ jobs:

build-docker-amd64:
needs: [ quality-check, version, build-wheel ]
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.event_name == 'pull_request' && github.head_ref == 'dev' && github.base_ref == 'main') || ((github.event_name == 'workflow_dispatch' || github.event_name == 'push') && github.ref_name == 'dev'))
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
Expand Down Expand Up @@ -241,7 +241,7 @@ jobs:

build-docker-arm64:
needs: [ quality-check, version, build-wheel ]
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.event_name == 'pull_request' && github.head_ref == 'dev' && github.base_ref == 'main') || ((github.event_name == 'workflow_dispatch' || github.event_name == 'push') && github.ref_name == 'dev'))
runs-on: ubuntu-24.04-arm
timeout-minutes: 3
steps:
Expand Down Expand Up @@ -300,7 +300,7 @@ jobs:
${{ env.GHCR_ENDPOINT }}:${TAG_ARM64}

notify:
name: Notify Discord & PR
name: Notify PR
needs: [ quality-check, version, beta-release, merge-docker-manifest ]
if: needs.quality-check.outputs.changes_pushed != 'true' && needs.beta-release.result == 'success'
runs-on: ubuntu-latest
Expand All @@ -313,17 +313,12 @@ jobs:
- name: Send Notifications
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
VERSION: ${{ needs.version.outputs.version }}
EPOCH: ${{ needs.version.outputs.epoch }}
REPO: ${{ github.repository }}
run: |
echo "📢 Notifying stakeholders..."
RELEASE_BODY=$(gh release view beta --json body --jq .body)
if [ -n "$DISCORD_WEBHOOK" ]; then
jq -n --arg title "🚀 New Beta Build: $VERSION ($EPOCH)" --arg url "https://github.com/$REPO/releases/tag/beta" '{content: null, flags: 4096, embeds: [{title: $title, url: $url, color: 5814783}]}' > discord_payload.json
curl -H "Content-Type: application/json" -d @discord_payload.json "$DISCORD_WEBHOOK"
fi
PR_NUMBER=$(gh pr list --search "${{ github.sha }}" --state merged --json number --jq '.[0].number')
if [ -n "$PR_NUMBER" ]; then
echo "## 🚀 Beta Release $VERSION is Live!" > pr_comment.md
Expand Down
10 changes: 0 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,6 @@ docker run -d \
-e 'QUASARR_API_KEY'='your_quasarr_api_key_here' \
-e 'FLARESOLVERR_URL'='http://10.10.0.1:8191/v1' \
-e 'APIKEY_2CAPTCHA'='your_2captcha_api_key_here' \
-e 'DEATHBYCAPTCHA_TOKEN'='your_deathbycaptcha_token_here' \
-e 'TZ'='Europe/Berlin' \
ghcr.io/rix1337/sponsors-helper:latest
```
Expand All @@ -327,13 +326,4 @@ docker run -d \
| `QUASARR_API_KEY` | Your Quasarr API key (found in Quasarr web UI under "API Settings") |
| `FLARESOLVERR_URL` | Local URL of [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) |
| `APIKEY_2CAPTCHA` | [2Captcha](https://2captcha.com/?from=27506687) account API key |
| `DEATHBYCAPTCHA_TOKEN` | [DeathByCaptcha](https://deathbycaptcha.com/register?refid=6184288242b) account token |
| `TZ` | Optional. Timezone for SponsorsHelper (e.g., `Europe/Berlin`) |

| Volume | Purpose |
|--------|---------|
| `/config` | Persistent SponsorsHelper state (including cached GitHub auth token) |

> - [2Captcha](https://2captcha.com/?from=27506687) is the recommended CAPTCHA solving service.
> - [DeathByCaptcha](https://deathbycaptcha.com/register?refid=6184288242b) can serve as a fallback or work on its own.
> - If you set both `APIKEY_2CAPTCHA` and `DEATHBYCAPTCHA_TOKEN` both services will be used alternately.
1 change: 0 additions & 1 deletion docker/dev-services-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ services:
# - QUASARR_URL=http://192.168.0.1:8080
# - QUASARR_API_KEY=your_quasarr_api_key_here
# - APIKEY_2CAPTCHA=your_2captcha_api_key_here
# - DEATHBYCAPTCHA_TOKEN=your_deathbycaptcha_token_here
# - FLARESOLVERR_URL=http://10.10.0.1:8191/v1
# volumes:
# - ${CONFIG_VOLUMES}/sponsorshelper:/config
Expand Down
1 change: 0 additions & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ services:
- 'QUASARR_API_KEY=your_quasarr_api_key_here'
- 'FLARESOLVERR_URL=http://10.10.0.1:8191/v1'
- 'APIKEY_2CAPTCHA=your_2captcha_api_key_here'
- 'DEATHBYCAPTCHA_TOKEN=your_deathbycaptcha_token_here'
- 'TZ=Europe/Berlin'
22 changes: 14 additions & 8 deletions quasarr/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ def render_notification_toggle_rows(provider):
</p>

<div class="timeout-slow-mode-section">
<h4>Timeouts</h4>
<label class="timeout-slow-mode-heading">Timeouts</label>
<p class="api-hint">
By default, Quasarr uses strict request timeouts so manual searches stay within a reasonable timeframe.
Enable slow mode only if you are willing to wait longer on slow sites.
Expand All @@ -336,7 +336,7 @@ def render_notification_toggle_rows(provider):
{timeout_slow_mode_rows}
</div>
<div id="timeout-slow-mode-status" class="notification-status"></div>
<p>{render_button("Save Slow Mode Settings", "primary", {"onclick": "saveTimeoutSlowModeSettings()", "type": "button", "id": "timeoutSlowModeSaveBtn"})}</p>
<p class="timeout-slow-mode-actions">{render_button("Save Timeout Settings", "primary", {"onclick": "saveTimeoutSlowModeSettings()", "type": "button", "id": "timeoutSlowModeSaveBtn"})}</p>
</div>
</div>
</details>
Expand Down Expand Up @@ -619,9 +619,15 @@ def render_notification_toggle_rows(provider):
border-top: 1px solid var(--card-border, #dee2e6);
text-align: left;
}}
.timeout-slow-mode-section h4 {{
margin: 0 0 8px 0;
font-size: 1em;
.timeout-slow-mode-heading {{
display: block;
font-weight: 500;
margin-bottom: 6px;
font-size: 0.95em;
text-align: left;
}}
.timeout-slow-mode-actions {{
text-align: center;
}}
.timeout-slow-mode-value {{
margin-top: 2px;
Expand Down Expand Up @@ -1148,7 +1154,7 @@ def render_notification_toggle_rows(provider):

async function saveTimeoutSlowModeSettings() {{
var saveButton = document.getElementById('timeoutSlowModeSaveBtn');
setNotificationStatus('timeout-slow-mode-status', 'Saving slow mode settings...', true);
setNotificationStatus('timeout-slow-mode-status', 'Saving timeout settings...', true);

if (saveButton) {{
saveButton.disabled = true;
Expand All @@ -1163,7 +1169,7 @@ def render_notification_toggle_rows(provider):
}});
var data = await response.json();
if (!response.ok || !data.success) {{
throw new Error(data.message || 'Failed to save slow mode settings');
throw new Error(data.message || 'Failed to save timeout settings');
}}

if (data.settings && typeof data.settings === 'object') {{
Expand All @@ -1184,7 +1190,7 @@ def render_notification_toggle_rows(provider):
}} finally {{
if (saveButton) {{
saveButton.disabled = false;
saveButton.textContent = 'Save Slow Mode Settings';
saveButton.textContent = 'Save Timeout Settings';
}}
}}
}}
Expand Down
Loading
Loading