web service
This commit is contained in:
parent
8fbe1a2bf4
commit
e972291993
@ -9,3 +9,14 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- CONF_FOLDER=/bot/conf/
|
- CONF_FOLDER=/bot/conf/
|
||||||
user: 1000:1000
|
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
|
gplaycli==3.29
|
||||||
python-telegram-bot==13.0
|
python-telegram-bot==13.0
|
||||||
|
Flask==1.1.2
|
@ -53,15 +53,17 @@ class APK:
|
|||||||
if os.path.isfile(fname):
|
if os.path.isfile(fname):
|
||||||
self.file_name = fname
|
self.file_name = fname
|
||||||
|
|
||||||
def check_dimension(self):
|
def check_dimension(self, split=True, remove=True):
|
||||||
if os.path.getsize(self.file_name) > 50 * 1024 * 1023:
|
if split and os.path.getsize(self.file_name) > 50 * 1024 * 1023:
|
||||||
check_call(['split', '-b', '49M', self.file_name, self.file_name])
|
check_call(['split', '-b', '49M', self.file_name, self.file_name])
|
||||||
|
if remove:
|
||||||
os.remove(self.file_name)
|
os.remove(self.file_name)
|
||||||
return glob(escape(self.file_name) + '*')
|
return glob(escape(self.file_name) + '*')
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def send(self):
|
def send(self, split=True, remove=True):
|
||||||
files = self.check_dimension() # split if size >= 50MB
|
files = self.check_dimension(split) # split if size >= 50MB
|
||||||
yield files
|
yield files
|
||||||
for f in files: # removing old files
|
for f in files: # removing old files
|
||||||
|
if remove:
|
||||||
os.remove(f)
|
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