web service
This commit is contained in:
parent
8fbe1a2bf4
commit
e972291993
@ -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"
|
@ -1,2 +1,3 @@
|
||||
gplaycli==3.29
|
||||
python-telegram-bot==13.0
|
||||
Flask==1.1.2
|
@ -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
src/static/main.css
Normal file
194
src/static/main.css
Normal file
@ -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;
|
||||
}
|
2
src/static/marx.min.css
vendored
Normal file
2
src/static/marx.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
17
src/templates/error.html
Normal file
17
src/templates/error.html
Normal file
@ -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
src/templates/home.html
Normal file
21
src/templates/home.html
Normal file
@ -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
src/web.py
Normal file
93
src/web.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user