Skip to content

Commit

Permalink
Merge pull request #6 from rix1337/dev
Browse files Browse the repository at this point in the history
Discord Notifications
  • Loading branch information
rix1337 authored Aug 19, 2024
2 parents 1b956d5 + c9780f2 commit 8f2d6f2
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 31 deletions.
4 changes: 3 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ EXPOSE 8080
ENV PYTHONUNBUFFERED=1
ENV DOCKER="true"
ENV INTERNAL_ADDRESS=""
ENV EXTERNAL_ADDRESS=""
ENV DISCORD=""

ENTRYPOINT ["sh", "-c", "quasarr --port=8080 --internal_address=$INTERNAL_ADDRESS"]
ENTRYPOINT ["sh", "-c", "quasarr --port=8080 --internal_address=$INTERNAL_ADDRESS --external_address=$EXTERNAL_ADDRESS --discord=$DISCORD"]
2 changes: 2 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ services:
- '/path/to/config/:/config:rw'
environment:
- 'INTERNAL_ADDRESS=http://192.168.1.1:8080'
- 'EXTERNAL_ADDRESS=http://foo.bar/'
- 'DISCORD=https://discord.com/api/webhooks/1234567890/ABCDEFGHIJKLMN'
image: 'rix1337/docker-quasarr:latest'
28 changes: 24 additions & 4 deletions quasarr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import argparse
import multiprocessing
import os
import re
import socket
import sys
import tempfile
Expand All @@ -26,6 +27,8 @@ def run():
parser = argparse.ArgumentParser()
parser.add_argument("--port", help="Desired Port, defaults to 8080")
parser.add_argument("--internal_address", help="Must be provided when running in Docker")
parser.add_argument("--external_address", help="External address for CAPTCHA notifications")
parser.add_argument("--discord", help="Discord Webhook URL")
arguments = parser.parse_args()

sys.stdout = Unbuffered(sys.stdout)
Expand Down Expand Up @@ -53,8 +56,12 @@ def run():

if arguments.internal_address:
internal_address = arguments.internal_address
if arguments.external_address:
external_address = arguments.external_address
else:
external_address = internal_address

shared_state.set_connection_info(internal_address, port)
shared_state.set_connection_info(internal_address, external_address, port)

if not config_path:
config_path_file = "Quasarr.conf"
Expand Down Expand Up @@ -101,6 +108,19 @@ def run():
if not user or not password or not device:
jdownloader_config(shared_state)

discord_url = ""
if arguments.discord:
discord_webhook_pattern = r'^https://discord\.com/api/webhooks/\d+/[\w-]+$'
if re.match(discord_webhook_pattern, arguments.discord):
shared_state.update("webhook", arguments.discord)
print(f"Using Discord Webhook URL: {arguments.discord}")
discord_url = arguments.discord
else:
print(f"Invalid Discord Webhook URL provided: {arguments.discord}")
else:
print("No Discord Webhook URL provided")
shared_state.update("discord", discord_url)

jdownloader = multiprocessing.Process(target=jdownloader_connection,
args=(shared_state_dict, shared_state_lock))
jdownloader.start()
Expand All @@ -113,7 +133,7 @@ def run():
if protected:
package_count = len(protected)
print(f"\nCAPTCHA-Solution required for {package_count} package{'s' if package_count > 1 else ''} at "
f'{shared_state.values["internal_address"]}/captcha"!\n')
f'{shared_state.values["external_address"]}/captcha"!\n')

try:
api(shared_state_dict, shared_state_lock)
Expand All @@ -138,9 +158,9 @@ def jdownloader_connection(shared_state_dict, shared_state_lock):
if connection_established:
break

if connection_established:
try:
print(f'Connection to JDownloader successful. Device name: "{shared_state.get_device().name}"')
else:
except:
print('Error connecting to JDownloader! Stopping Quasarr!')
sys.exit(1)

Expand Down
8 changes: 7 additions & 1 deletion quasarr/arr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ def serve_captcha():
protected = shared_state.get_db("protected").retrieve_all_titles()
if not protected:
return render_centered_html('<h1>Quasarr</h1><p>No protected packages found! CAPTCHA not needed.</p>')
try:
device = shared_state.values["device"]
except KeyError:
device = None
if not device:
return render_centered_html('<h1>Quasarr</h1><p>JDownloader connection not established.</p>')
content = render_centered_html(r'''
<script type="text/javascript">
var api_key = "''' + captcha_values()["api_key"] + r'''";
var endpoint = '/captcha/' + api_key + '.html';
var endpoint = '/' + window.location.pathname.split('/')[1] + '/' + api_key + '.html';
function handleToken(token) {
document.getElementById("puzzle-captcha").remove();
document.getElementById("captcha-key").innerText = 'Using result "' + token + '" to decrypt links...';
Expand Down
4 changes: 3 additions & 1 deletion quasarr/downloads/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from quasarr.downloads.sources.nx import get_nx_download_links
from quasarr.providers.myjd_api import TokenExpiredException, RequestTimeoutException, MYJDException
from quasarr.providers.notifications import send_discord_captcha_alert


def get_first_matching_comment(package, package_links):
Expand Down Expand Up @@ -225,7 +226,8 @@ def download_package(shared_state, request_from, title, url, size_mb, password):
package_id = None

elif "filecrypt".lower() in url.lower():
print(f"CAPTCHA-Solution required for {title} at {shared_state.values["internal_address"]}/captcha")
print(f"CAPTCHA-Solution required for {title}{shared_state.values['external_address']}/captcha")
send_discord_captcha_alert(shared_state, title)
package_id = f"Quasarr_{category}_{str(hash(title + url)).replace('-', '')}"
blob = f"{title}|{url}|{size_mb}|{password}"
shared_state.values["database"]("protected").update_store(package_id, blob)
Expand Down
22 changes: 1 addition & 21 deletions quasarr/persistence/sqlite_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,6 @@
from quasarr.providers import shared_state


def get_first(iterable):
return iterable and list(iterable[:1]).pop() or None

cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
table_names = [row[0] for row in cursor.fetchall()]

tables_to_drop = set(table_names) - set(keep_tables)

for table in tables_to_drop:
cursor.execute(f"DROP TABLE IF EXISTS {table}")
print(f"Entferne überflüssige Tabelle '{table}' aus der Datenbank.")

conn.commit()
cursor.execute("VACUUM")
conn.close()


class DataBase(object):
def __init__(self, table):
try:
Expand All @@ -36,18 +19,15 @@ def __init__(self, table):
self._conn.commit()
except sqlite3.OperationalError as e:
try:
shared_state.logger.debug(
"Fehler bei Zugriff auf Quasarr.db: " + str(e) + " (neuer Versuch in 5 Sekunden).")
time.sleep(5)
self._conn = sqlite3.connect(shared_state.values["dbfile"], check_same_thread=False, timeout=10)
self._table = table
if not self._conn.execute(
f"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '{self._table}';").fetchall():
self._conn.execute(f"CREATE TABLE {self._table} (key, value)")
self._conn.commit()
shared_state.logger.debug("Zugriff auf Quasarr.db nach Wartezeit war erfolgreich.")
except sqlite3.OperationalError as e:
print("Fehler bei Zugriff auf Quasarr.db: ", str(e))
print(f"Error accessing Quasarr.db: {e}")

def retrieve(self, key):
query = f"SELECT value FROM {self._table} WHERE key=?"
Expand Down
36 changes: 36 additions & 0 deletions quasarr/providers/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Quasarr
# Project by https://github.com/rix1337

import json

import requests


def send_discord_captcha_alert(shared_state, title):
if not shared_state.values.get("discord"):
return False

data = {
'username': 'Quasarr',
# 'avatar_url': 'https://imgur.com/fooBar.png',
'embeds': [{
'title': title,
'description': 'Links are protected. Please solve the CAPTCHA to continue downloading.',
'fields': [
{
'name': '',
'value': f'[Solve the CAPTCHA here!]({f"{shared_state.values['external_address']}/captcha"})',
}

]
}]
}

response = requests.post(shared_state.values["discord"], data=json.dumps(data),
headers={"Content-Type": "application/json"})
if response.status_code != 204:
print(f"Failed to send message to Discord webhook. Status code: {response.status_code}")
return False

return True
4 changes: 2 additions & 2 deletions quasarr/providers/shared_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

values = {}
lock = None
logger = None


def set_state(manager_dict, manager_lock):
Expand All @@ -35,10 +34,11 @@ def set_sites():
update("sites", ["FX", "NX"])


def set_connection_info(internal_address, port):
def set_connection_info(internal_address, external_address, port):
if internal_address.count(":") < 2:
internal_address = f"{internal_address}:{port}"
update("internal_address", internal_address)
update("external_address", external_address)
update("port", port)


Expand Down
2 changes: 1 addition & 1 deletion quasarr/providers/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


def get_version():
return "0.1.0"
return "0.1.1"


def create_version_file():
Expand Down

0 comments on commit 8f2d6f2

Please sign in to comment.