Browse Source

web service

thezero 10 months ago
parent
commit
e972291993
8 changed files with 346 additions and 7 deletions
  1. 11 0
      docker-compose.yml
  2. 2 1
      requirements.txt
  3. 8 6
      src/download_utils.py
  4. 194 0
      src/static/main.css
  5. 0 0
      src/static/marx.min.css
  6. 17 0
      src/templates/error.html
  7. 21 0
      src/templates/home.html
  8. 93 0
      src/web.py

+ 11 - 0
docker-compose.yml

@@ -9,3 +9,14 @@ services:
     environment:
       - CONF_FOLDER=/bot/conf/
     user: 1000:1000
+  web:
+    build: .
+    command: python3 /bot/src/web.py
+    volumes:
+      - ./out:/bot/out
+      - ./conf:/bot/conf
+    environment:
+      - CONF_FOLDER=/bot/conf/
+    user: 1000:1000
+    ports:
+      - "5000:5000"

+ 2 - 1
requirements.txt

@@ -1,2 +1,3 @@
 gplaycli==3.29
-python-telegram-bot==13.0
+python-telegram-bot==13.0
+Flask==1.1.2

+ 8 - 6
src/download_utils.py

@@ -53,15 +53,17 @@ class APK:
         if os.path.isfile(fname):
             self.file_name = fname
 
-    def check_dimension(self):
-        if os.path.getsize(self.file_name) > 50 * 1024 * 1023:
+    def check_dimension(self, split=True, remove=True):
+        if split and os.path.getsize(self.file_name) > 50 * 1024 * 1023:
             check_call(['split', '-b', '49M', self.file_name, self.file_name])
-            os.remove(self.file_name)
+            if remove:
+                os.remove(self.file_name)
         return glob(escape(self.file_name) + '*')
 
     @contextmanager
-    def send(self):
-        files = self.check_dimension()  # split if size >= 50MB
+    def send(self, split=True, remove=True):
+        files = self.check_dimension(split)  # split if size >= 50MB
         yield files
         for f in files:  # removing old files
-            os.remove(f)
+            if remove:
+                os.remove(f)

+ 194 - 0
src/static/main.css

@@ -0,0 +1,194 @@
+/**
+ * Custom CSS
+ */
+ a {
+    color: #0097A7;
+}
+.look-form{
+    margin-top:20px;
+    margin-top:2rem;
+    margin-bottom:20px;
+    margin-bottom:2rem;
+    display: flex
+}
+.look-form label {
+    display:block;
+    font-weight:bold
+}
+.look-form input[type="text"] {
+    width:100%;
+    display:block;
+    border-radius:3px 0 0 3px;
+    border-radius:.3rem 0 0 .3rem;
+    color:#424242;
+    background:#fff;
+    border:2px solid #E0E0E0;
+    border:.17rem solid #E0E0E0;
+    transition:border-color ease-in-out .15s;
+    padding:11px 10px;
+    padding:1.1rem 1rem;
+}
+.look-form input[type="text"]:focus {
+    border-color:#0097A7;
+    outline:0
+    box-shadow: 0 0 0 .2rem #0095A570;
+    -webkit-box-shadow: 0 0 0 .2rem #0095A570;
+}
+.look-form input[type="text"] {
+    font-family: monospace;
+}
+.look-form input[type="submit"] {
+    height: 4rem;
+    width: 7rem;
+    z-index: 2;
+    background-color: #0095A5;
+    color: #fff;
+    border-radius: 0 4px 4px 0;
+    border-radius: 0 .4rem .4rem 0;
+    border: 2px solid #14854f;
+    text-align: center;
+    -webkit-transition: opacity .2s ease;
+    transition: opacity .2s ease;
+    text-decoration: none;
+    cursor: pointer;
+}
+.look-form input[type="submit"]:hover {
+    background-color: #0095A5;
+    border-color: #14854f;
+    opacity:.85
+}
+.look-form input[type="submit"]:active {
+    background-color: #0095A5;
+    border-color: #14854f;
+    opacity:.75
+}
+.look-form.error input {
+    border-color:#F50057
+}
+.console {
+    font-family: monospace;
+}
+.highlight {
+    color: #0097A7;
+}
+.look-segment {
+    border: 1px solid #E0E0E0;
+    border-top-color: rgb(224, 224, 224);
+    border-right-color: rgb(224, 224, 224);
+    border-bottom-color: rgb(224, 224, 224);
+    border-left-color: rgb(224, 224, 224);
+    border-radius: 3px;
+    border-radius: .3rem;
+    margin-top: 20px;
+    margin-top: 2rem;
+    margin-bottom: 20px;
+    margin-bottom: 2rem;
+    overflow: hidden;
+}
+.look-segment > .header {
+    background: #f5f5f5;
+    padding: 10px 20px;
+    padding: 1rem 2rem;
+    box-shadow: inset 0 -1px 0 #E0E0E0;
+}
+.look-segment > .body {
+    padding: 19px 20px;
+    padding: 1.9rem 2rem;
+    word-wrap: break-word;
+}
+.look-segment > .body > :last-child, .look-segment > .body > :last-child > :last-child {
+    margin-bottom: 0;
+}
+.look-segment > .body > :first-child, .look-segment > .body > :first-child > :first-child {
+    margin-top: 0;
+}
+.look-segment.amber {
+    border-color: #FFC107;
+}
+.look-segment.amber > .header {
+    box-shadow: inset 0 -1px 0 #FFC107;
+    background: #ffeeba;
+    color: #000;
+}
+.look-segment.green {
+    border-color: #4CAF50;
+}
+.look-segment.green > .header {
+    box-shadow: inset 0 -1px 0 #4CAF50;
+    background: #c7e7c8;
+    color: #000;
+}
+.look-label {
+    display: inline-block;
+    background: #0097A7;
+    color: #fff;
+    font-weight: bold;
+    border: 0;
+    border-radius: 3px;
+    border-radius: .3rem;
+    padding: 1px 4px;
+    padding: .1rem .4rem;
+    font-size: 14px;
+    text-align: center;
+    vertical-align: middle;
+    white-space: nowrap;
+    line-height: inherit;
+}
+.look-table {
+    width: 100%;
+}
+.look-table td, .look-table th {
+    padding: 10px;
+}
+.look-table a > code {
+    color: #0097A7;
+}
+.literal {
+    background: rgba(0,0,0,.075);
+    color: #000;
+}
+.faq .q {
+    font-style: italic;
+    color: #0077DD;
+}
+.legend {
+    font-weight: bolder;
+    font-size: 12px;
+}
+.legend .username {
+    color: blue;
+}
+.legend .password {
+    color: orange;
+}
+.legend .rounds, .legend .time_cost {
+    color: purple;
+}
+.legend .salt {
+    color: red;
+}
+.legend .hash {
+    color: green;
+}
+pre .username {
+    border-bottom: 2px solid blue;
+    padding-bottom: 4px;
+    /*background-color: #00008030;*/
+}
+pre .password {
+    border-bottom: 2px solid orange;
+    padding-bottom: 4px;
+    /*background-color: #ffa50030;*/
+}
+pre .rounds, pre .time_cost {
+    border-bottom: 2px solid purple;
+    background-color: #80008030;
+}
+pre .salt {
+    border-bottom: 2px solid red;
+    background-color: #ff000030;
+}
+pre .hash {
+    border-bottom: 2px solid green;
+    background-color: #00800030;
+}

File diff suppressed because it is too large
+ 0 - 0
src/static/marx.min.css


+ 17 - 0
src/templates/error.html

@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+<title>Error</title>
+<link rel="stylesheet" type="text/css" href="/static/marx.min.css">
+<link rel="stylesheet" type="text/css" href="/static/main.css">
+</head>
+<body>
+<main>
+	<h1>apk-downloader</h1>
+	<h2>Error {{code}}</h2>
+	<p>{{error}}</p>
+	<hr/>
+	<p>Return to the <a href="/">homepage</a> or <a href="javascript:window.history.back()">go back</a></p>
+</main>
+</body>
+</html>

+ 21 - 0
src/templates/home.html

@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+<head>
+<title>apk-downloader</title>
+<link rel="stylesheet" type="text/css" href="/static/marx.min.css">
+<link rel="stylesheet" type="text/css" href="/static/main.css">
+</head>
+<body>
+<main>
+  <h1>apk-downloader</h1>
+  <p>
+    Download apk from the PlayStore<br/>
+    <br/>
+    <form class="look-form" action="/download/" method="POST">
+      <input type="text" id="package_name" name="package_name" placeholder="Placeholder...">
+      <input type="submit" class="button" value="Submit">
+    </form>
+  </p>
+</main>
+</body>
+</html>

+ 93 - 0
src/web.py

@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+
+import logging
+import os
+
+from flask import Flask, request, render_template, redirect, url_for, session, send_file
+
+from download_utils import APK, BadPackageNameException
+from subprocess import CalledProcessError
+
+if "LOG_LEVEL" in os.environ:
+    log_level = os.environ["LOG_LEVEL"]
+else:
+    log_level = logging.INFO
+
+# Logging configuration.
+logger = logging.getLogger(__name__)
+logging.basicConfig(
+    format="%(asctime)s> [%(levelname)s] %(message)s",
+    datefmt="%d/%m/%Y %H:%M:%S",
+    level=log_level,
+)
+logging.getLogger("werkzeug").disabled = True
+
+if os.environ.get('CONF_FOLDER') is None:
+    logger.error("No conf folder available")
+    exit(1)
+TOKEN = None
+with open(os.path.join(os.environ['CONF_FOLDER'], 'web.token')) as f:
+    TOKEN = f.read().strip()
+
+CONF = {
+    'yes': True,
+    'verbose': False,
+    'tokencachefile': os.path.join(os.environ['CONF_FOLDER'], 'token.cache'),
+}
+CONF_FILE = os.path.join(os.environ['CONF_FOLDER'], 'gplaycli.conf')
+
+app = Flask(__name__)
+app.secret_key = TOKEN
+
+@app.errorhandler(400)
+@app.errorhandler(404)
+@app.errorhandler(500)
+def application_error(error):
+    logger.error(error)
+    return render_template('error.html', code=error.code, error=str(error)), error.code
+
+@app.route("/return-file/")
+def return_file():
+    package_name = session.get("package_name")
+    filename = f"{package_name}.apk"
+    location = session.get("filename", '')
+    if location != '':
+        location = os.path.join('/bot', location)
+    logging.info(f"sending {location}")
+    return send_file(location, attachment_filename=filename, as_attachment=True)
+
+@app.route("/download/<package_name>", methods=["GET"])
+def get_download(package_name):
+    return download(package_name)
+
+@app.route("/download/", methods=["POST"])
+def post_download():
+    package_name = request.form.get('package_name', '').strip()
+    return download(package_name)
+
+def download(package_name):
+    logger.info("downloading {} ...".format(package_name))
+
+    apk = APK(package_name, conf=CONF, conf_file=CONF_FILE)
+
+    try:
+        apk.download()
+        with apk.send(split=False, remove=False) as files:
+            for f in files:
+                session['package_name'] = package_name
+                session['filename'] = f
+    except BadPackageNameException as e:
+        logger.error("Bad package name: {}".format(e))
+        return render_template('error.html', code=500, error="Bad package name: {}".format(e)), 500
+    except CalledProcessError:
+        logger.error("Failed to send APK file")
+        return render_template('error.html', code=500, error="Failed to send APK file"), 500
+
+    return redirect(url_for("return_file"))
+
+@app.route("/", methods=["GET"], strict_slashes=False)
+def home():
+    return render_template('home.html')
+
+if __name__ == "__main__":
+    app.run(host="0.0.0.0", port=5000)

Some files were not shown because too many files changed in this diff