From e972291993ff9a4e73b611a33a00eaef59d042a9 Mon Sep 17 00:00:00 2001
From: thezero
Date: Sun, 6 Dec 2020 19:59:31 +0100
Subject: [PATCH] web service
---
docker-compose.yml | 11 +++
requirements.txt | 3 +-
src/download_utils.py | 14 +--
src/static/main.css | 194 +++++++++++++++++++++++++++++++++++++++
src/static/marx.min.css | 2 +
src/templates/error.html | 17 ++++
src/templates/home.html | 21 +++++
src/web.py | 93 +++++++++++++++++++
8 files changed, 348 insertions(+), 7 deletions(-)
create mode 100644 src/static/main.css
create mode 100644 src/static/marx.min.css
create mode 100644 src/templates/error.html
create mode 100644 src/templates/home.html
create mode 100644 src/web.py
diff --git a/docker-compose.yml b/docker-compose.yml
index cdb7593..1b65f68 100644
--- a/docker-compose.yml
+++ b/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"
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 41884de..c071ebe 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
gplaycli==3.29
-python-telegram-bot==13.0
\ No newline at end of file
+python-telegram-bot==13.0
+Flask==1.1.2
\ No newline at end of file
diff --git a/src/download_utils.py b/src/download_utils.py
index 9ba09ab..8c21721 100644
--- a/src/download_utils.py
+++ b/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)
diff --git a/src/static/main.css b/src/static/main.css
new file mode 100644
index 0000000..2647288
--- /dev/null
+++ b/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;
+}
\ No newline at end of file
diff --git a/src/static/marx.min.css b/src/static/marx.min.css
new file mode 100644
index 0000000..19a2f65
--- /dev/null
+++ b/src/static/marx.min.css
@@ -0,0 +1,2 @@
+*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box}:after,:before{text-decoration:inherit;vertical-align:inherit}html{cursor:default;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;word-break:break-word}body{margin:0}h1{font-size:2em;margin:.67em 0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{margin:0}nav ol,nav ul{list-style:none;padding:0}pre{font-family:monospace,monospace;font-size:1em}abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}audio,canvas,iframe,img,svg,video{vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}iframe,img{border-style:none}svg:not([fill]){fill:currentColor}svg:not(:root){overflow:hidden}table{border-collapse:collapse}button,input,select{margin:0}button{overflow:visible;text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}fieldset{border:1px solid #a0a0a0;padding:.35em .75em .625em}input{overflow:visible}legend{color:inherit;display:table;max-width:100%;white-space:normal}progress{display:inline-block;vertical-align:baseline}select{text-transform:none}textarea{margin:0}[type=checkbox],[type=radio]{padding:0}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}details,dialog{display:block}dialog{background-color:#fff;border:solid;color:#000;height:-moz-fit-content;height:-webkit-fit-content;height:fit-content;left:0;margin:auto;padding:1em;position:absolute;right:0;width:-moz-fit-content;width:-webkit-fit-content;width:fit-content}dialog:not([open]){display:none}summary{display:list-item}canvas{display:inline-block}template{display:none}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}[hidden]{display:none}[aria-busy=true]{cursor:progress}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);position:absolute}/*! Marx v3.0.6 - The classless CSS reset (perfect for Communists) | MIT License | https://github.com/mblode/marx */article,aside,details,footer,header,main,section,summary{margin:0 auto 16px;width:100%}main{display:block;margin:0 auto;max-width:768px;padding:0 16px 16px}footer{border-top:1px solid rgba(0,0,0,.12);padding:16px 0;text-align:center}footer p{margin-bottom:0}hr{border:0;border-top:1px solid rgba(0,0,0,.12);display:block;margin-top:16px;margin-bottom:16px;width:100%;-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}img{height:auto;max-width:100%;vertical-align:baseline}@media screen and (max-width:400px){article,aside,section{clear:both;display:block;max-width:100%}img{margin-right:16px}}embed,iframe,video{border:0}body{color:rgba(0,0,0,.8);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:16px;line-height:1.5}p{margin:0 0 16px}h1,h2,h3,h4,h5,h6{color:inherit;font-family:inherit;line-height:1.2;font-weight:500}h1{font-size:40px}h1,h2{margin:20px 0 16px}h2{font-size:32px}h3{font-size:28px}h3,h4{margin:16px 0 4px}h4{font-size:24px}h5{font-size:20px}h5,h6{margin:16px 0 4px}h6{font-size:16px}small{color:rgba(0,0,0,.54);vertical-align:bottom}pre{background:#f7f7f9;display:block;margin:16px 0;padding:16px;white-space:pre-wrap;overflow-wrap:break-word}code,pre{color:rgba(0,0,0,.8);font-family:Menlo,Monaco,Consolas,Courier New,monospace;font-size:16px}code{line-height:inherit;margin:0;padding:0;vertical-align:baseline;word-break:break-all;word-wrap:break-word}a{color:#007bff;text-decoration:none;background-color:rgba(0,0,0,0)}a:focus,a:hover{color:#0062cc;text-decoration:underline}dl{margin-bottom:16px}dd{margin-left:40px}ol,ul{margin-bottom:8px;padding-left:40px;vertical-align:baseline}blockquote{border-left:2px solid rgba(0,0,0,.8);font-style:italic;margin:16px 0;padding-left:16px}blockquote,figcaption{font-family:Georgia,Times,Times New Roman,serif}u{text-decoration:underline}s{text-decoration:line-through}sup{vertical-align:super}sub,sup{font-size:14px}sub{vertical-align:sub}mark{background:#ffeb3b}input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{background:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.12);border-radius:4px;color:rgba(0,0,0,.8);display:block;width:100%;font-size:1rem;padding:8px 16px;line-height:1.5;-webkit-transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}input[type=color]{background:#fff;border:1px solid rgba(0,0,0,.12);border-radius:4px;display:inline-block;vertical-align:middle}input:not([type]){-webkit-appearance:none;background:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.12);border-radius:4px;color:rgba(0,0,0,.8);display:block;width:100%;padding:8px 16px;line-height:1.5;-webkit-transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;text-align:left}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{background-color:#fff;border-color:#80bdff;outline:0;-webkit-box-shadow:0 0 0 .2rem rgba(0,123,255,.25);box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}input:not([type]):focus{background-color:#fff;border-color:#febf01;outline:0;-webkit-box-shadow:0 0 0 .2rem rgba(0,123,255,.25);box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:1px thin rgba(0,0,0,.12)}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled],select[disabled],textarea[disabled]{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.54);cursor:not-allowed;opacity:1}input:not([type])[disabled]{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.54);cursor:not-allowed;opacity:1}input[readonly],select[readonly],textarea[readonly]{border-color:rgba(0,0,0,.12);color:rgba(0,0,0,.54)}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{border-color:#ea1c0d;color:#f44336}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#f44336}select{border:1px solid rgba(0,0,0,.12);vertical-align:sub}select:not([size]):not([multiple]){height:-webkit-calc(2.25rem + 2px);height:calc(2.25rem + 2px)}select[multiple]{height:auto}label{display:inline-block;line-height:2}fieldset{border:0;margin:0;padding:8px 0}legend{border-bottom:1px solid rgba(0,0,0,.12);color:rgba(0,0,0,.8);display:block;margin-bottom:8px;padding:8px 0;width:100%}textarea{overflow:auto;resize:vertical}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0;display:inline}button,input[type=button],input[type=reset],input[type=submit]{background-color:#007bff;border:#007bff;border-radius:4px;color:#fff;padding:8px 16px;display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid rgba(0,0,0,0);font-size:1rem;line-height:1.5;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out}button::-moz-focus-inner,input[type=button]::-moz-focus-inner,input[type=reset]::-moz-focus-inner,input[type=submit]::-moz-focus-inner{padding:0}button:hover,input[type=button]:hover,input[type=reset]:hover,input[type=submit]:hover{background-color:#0069d9;border-color:#0062cc;color:#fff}button:not(:disabled):active,input[type=button]:not(:disabled):active,input[type=reset]:not(:disabled):active,input[type=submit]:not(:disabled):active{background-color:#0062cc;border-color:#005cbf;color:#fff}button:focus,input[type=button]:focus,input[type=reset]:focus,input[type=submit]:focus{outline:0;-webkit-box-shadow:0 0 0 .2rem rgba(0,123,255,.5);box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}button:disabled,input[type=button]:disabled,input[type=reset]:disabled,input[type=submit]:disabled{opacity:.65;cursor:not-allowed;background-color:#007bff;border-color:#007bff;color:#fff}table{border-top:1px solid rgba(0,0,0,.12);margin-bottom:16px}caption{padding:8px 0}thead th{border:0;border-bottom:2px solid rgba(0,0,0,.12);text-align:left}tr{margin-bottom:8px}td,th{border-bottom:1px solid rgba(0,0,0,.12);padding:16px;vertical-align:inherit}tfoot tr{text-align:left}tfoot td{color:rgba(0,0,0,.54);font-size:8px;font-style:italic;padding:16px 4px}
+/*# sourceMappingURL=marx.min.css.map */
diff --git a/src/templates/error.html b/src/templates/error.html
new file mode 100644
index 0000000..dedda39
--- /dev/null
+++ b/src/templates/error.html
@@ -0,0 +1,17 @@
+
+
+
+Error
+
+
+
+
+
+ apk-downloader
+ Error {{code}}
+ {{error}}
+
+ Return to the homepage or go back
+
+
+
\ No newline at end of file
diff --git a/src/templates/home.html b/src/templates/home.html
new file mode 100644
index 0000000..5db0ceb
--- /dev/null
+++ b/src/templates/home.html
@@ -0,0 +1,21 @@
+
+
+
+apk-downloader
+
+
+
+
+
+ apk-downloader
+
+ Download apk from the PlayStore
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/web.py b/src/web.py
new file mode 100644
index 0000000..3ce0441
--- /dev/null
+++ b/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/", 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)