minor fixes, added web serer
This commit is contained in:
		| @@ -48,7 +48,7 @@ class MasterService: | |||||||
|             ) |             ) | ||||||
|  |  | ||||||
|             result = await download_task |             result = await download_task | ||||||
|             await redis.del_task_from_tasks_and_add_to_task_done(task=video_params) |             await redis.del_task_from_tasks_and_add_to_task_done(task={"link": video_params["link"], "result": result}) | ||||||
|             # TODO process result |             # TODO process result | ||||||
|             self.queue.task_done() |             self.queue.task_done() | ||||||
|  |  | ||||||
| @@ -76,8 +76,8 @@ class MasterService: | |||||||
|     @staticmethod |     @staticmethod | ||||||
|     def video_processing_executor(video_params: dict): |     def video_processing_executor(video_params: dict): | ||||||
|         try: |         try: | ||||||
|             MasterService.video_download(video_params=video_params) |             result = MasterService.video_download(video_params=video_params) | ||||||
|             return Result(result_type=ResultTypeEnum.DONE) |             return result | ||||||
|         except Exception as ex: |         except Exception as ex: | ||||||
|             return Result(result_type=ResultTypeEnum.EXCEPTION, value=ex) |             return Result(result_type=ResultTypeEnum.EXCEPTION, value=ex) | ||||||
|         # TODO upload to server |         # TODO upload to server | ||||||
|   | |||||||
| @@ -18,8 +18,8 @@ class RedisClient: | |||||||
|  |  | ||||||
|     async def _set_task_done(self, task: dict) -> int: |     async def _set_task_done(self, task: dict) -> int: | ||||||
|         async with self.connection as connection: |         async with self.connection as connection: | ||||||
|             res = await connection.set( |             res = await connection.sadd( | ||||||
|                 f'{self.TASKS_DONE_NAME}:1:{task["link"]}', |                 f'{self.TASKS_DONE_NAME}:1', | ||||||
|                 json.dumps(task, indent=4).encode('utf-8') |                 json.dumps(task, indent=4).encode('utf-8') | ||||||
|                                        ) |                                        ) | ||||||
|         return res |         return res | ||||||
| @@ -53,3 +53,19 @@ class RedisClient: | |||||||
|         await self._del_task(task) |         await self._del_task(task) | ||||||
|         return await self._set_task_done(task) |         return await self._set_task_done(task) | ||||||
|  |  | ||||||
|  |     async def get_task_done_queue(self) -> set: | ||||||
|  |         async with self.connection as connection: | ||||||
|  |             res = await connection.smembers(self.TASKS_DONE_NAME + f":1") | ||||||
|  |         return res | ||||||
|  |  | ||||||
|  |     async def del_task_from_task_done_queue(self, task) -> int: | ||||||
|  |         async with self.connection as connection: | ||||||
|  |             res = await connection.srem(self.TASKS_DONE_NAME + f":1", json.dumps(task, indent=4).encode('utf-8')) | ||||||
|  |         return res | ||||||
|  |  | ||||||
|  |     async def get_tasks_queue(self) -> set: | ||||||
|  |         async with self.connection as connection: | ||||||
|  |             res = await connection.json().get(self.TASKS_NAME) | ||||||
|  |         return res | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ def main(): | |||||||
|     if not found: |     if not found: | ||||||
|         client.make_bucket("clean-internet-oculus-integration-dev") |         client.make_bucket("clean-internet-oculus-integration-dev") | ||||||
|     else: |     else: | ||||||
|         print("Bucket 'asiatrip' already exists") |         print("Bucket 'clean-internet-oculus-integration-dev' already exists") | ||||||
|  |  | ||||||
|     # Upload '/home/user/Photos/asiaphotos.zip' as object name |     # Upload '/home/user/Photos/asiaphotos.zip' as object name | ||||||
|     # 'asiaphotos-2015.zip' to bucket 'asiatrip'. |     # 'asiaphotos-2015.zip' to bucket 'asiatrip'. | ||||||
| @@ -23,8 +23,8 @@ def main(): | |||||||
|         "clean-internet-oculus-integration-dev", "4uv2GNc_ybc_1080p.mp4", "/Users/garickbadalov/PycharmProjects/video_downloader_service/downloads/Youtube/4uv2GNc_ybc_1080p.mp4", |         "clean-internet-oculus-integration-dev", "4uv2GNc_ybc_1080p.mp4", "/Users/garickbadalov/PycharmProjects/video_downloader_service/downloads/Youtube/4uv2GNc_ybc_1080p.mp4", | ||||||
|     ) |     ) | ||||||
|     print( |     print( | ||||||
|         "'/home/user/Photos/asiaphotos.zip' is successfully uploaded as " |         "'/Users/garickbadalov/PycharmProjects/video_downloader_service/downloads/Youtube/4uv2GNc_ybc_1080p.mp4' is successfully uploaded as " | ||||||
|         "object 'asiaphotos-2015.zip' to bucket 'asiatrip'." |         "object '4uv2GNc_ybc_1080p.mp4' to bucket 'clean-internet-oculus-integration-dev'." | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import os | import os | ||||||
|  | import uuid | ||||||
|  |  | ||||||
| import requests | import requests | ||||||
| from http.cookies import SimpleCookie |  | ||||||
|  |  | ||||||
| from playwright.sync_api import Playwright | from playwright.sync_api import Playwright | ||||||
| from playwright.sync_api import sync_playwright | from playwright.sync_api import sync_playwright | ||||||
| @@ -19,25 +19,23 @@ class MyMailParser(BaseParser): | |||||||
|         page = context.new_page() |         page = context.new_page() | ||||||
|         mobile_url = f"{self.params['link'][0:8]}m.{self.params['link'][8:]}" |         mobile_url = f"{self.params['link'][0:8]}m.{self.params['link'][8:]}" | ||||||
|         page.goto(url=mobile_url) |         page.goto(url=mobile_url) | ||||||
|  |         cc = context.cookies() | ||||||
|  |         cookies = {cookie["name"]: cookie["value"] for cookie in cc} | ||||||
|         link = page.get_attribute("xpath=//video", "src") |         link = page.get_attribute("xpath=//video", "src") | ||||||
|         link = "https:" + link |         link = "https:" + link | ||||||
|         title = page.locator("xpath=//div[@class='event-text__title']").text_content() |         title = cookies["video_key"] | ||||||
|         return link, title |         return link, title, cookies | ||||||
|  |  | ||||||
|     def video_download(self, link: str = None, title: str = None): |     def video_download(self, link: str = None, title: str = None): | ||||||
|         if not link and not title: |         if not link and not title: | ||||||
|             with sync_playwright() as playwright: |             with sync_playwright() as playwright: | ||||||
|                 link, title = self.get_video_link(playwright) |                 link, title, cookies = self.get_video_link(playwright) | ||||||
|  |  | ||||||
|         if os.path.exists(os.path.join(os.getcwd() + f"MyMailRu/{title}.mp4")): |         if os.path.exists(os.path.join(os.getcwd() + f"/downloads/MyMailRu/{title}.mp4")): | ||||||
|             return Result(result_type=ResultTypeEnum.EXIST) |             return f"MyMailRu/{title}.mp4"#Result(result_type=ResultTypeEnum.EXIST) | ||||||
|  |  | ||||||
|         rawdata = "searchuid=4515257701686610918; p=ki8AAAYkJdcA; act=064d11655c924c9f8f2aad0181a06a4b; o=:1763:AUAQ.m; oid=22SgCdFE5g2ZEFHy1FkYW; mrcu=5A5B64F228CC3AC265485FC5AE55; re_theme=system; re_theme_actual=dark; s=fver=0|rt=1|dpr=2|ww=1728|wh=963; ph_tp_horo-mail-ru=t=1|d=1693591768453; tmr_lvid=26ef811c203f1c0c0e5d1c8af1a4671b; tmr_lvidTS=1693591768481; _ym_uid=1693591769619458564; _ym_d=1693591769; ph_v_my-mail-ru=1; mrhc=CB75tAx8UrwCaiqE85YXWoCM2+CTT6/VsTcMdxv4iCM=; mr_my_b=1; _ga=GA1.2.2000378679.1694259228; mtrc=%7B%22mytrackerid%22%3A52867%2C%22tmr_lvid%22%3A%2226ef811c203f1c0c0e5d1c8af1a4671b%22%7D; c=FuoAZQEAsHsTAAAUAQgACQAAgLrElILY4CDYNvMTASDQrSUa; b=nUwAAJBoPmMDosXR5CCG5oQO4ltqxSBq54QOYpLl6yBiWW0VIvzl6zAOfNwNOtuHt6ADAAAIpgR06GAGp1YMpgB06AAA; i=AQAQDgNlCQATAAguDyABAYwDApADARgHAewHATwJAUMLARkUAXseAjAgAfUgAfYgAfcgAfggAfEiAZMCCHYnbgABAQIBAgIBBwIBCAIBCQIBDgIBDwIBEQIBEgIBFwIBGAIBUQUBVgUBaAUBdAUBdQUBoAUBoQUBpAUBpgUBqQUBegYBDgsBKQsBLgsBxQsBxwsByQsBzAsBzQsBcA0BdQ0BeA0BvQ0B6BAB6RAB6hABw2MB3AQIBAEBAAHhBAkBAeIECgQGB80HOgUIDQQqAgEACAELCAEeEAHWBggEAQEAAb0HCAQBoxUBiQ0FAgHz; video_key=192bed9054db7a4efa7943ad834c7a2e05a55237; VID=0eXAI6071-IK00000t1kP4oK:::0-0-a1b105a-9aaf457:CAASEL33YAsZEz357mCA71F8QJgacM9HfhwzMJ-j3X3e-iJIE0DIiLWfRhfTc3GgyUNfH8_EwadLkVinwp0LA-QyaRe9p0A_ZR0y1i9Hk8aVl8Q8ZB_Qd_hCZN_SfHmeOvHeoe6QBCvz5w2SHcI2iFuAXKJkJMvNuYwSeBLdWhCXvsK5M_M" |  | ||||||
|         cookie = SimpleCookie() |  | ||||||
|         cookie.load(rawdata) |  | ||||||
|         cookies = {k: v.value for k, v in cookie.items()} |  | ||||||
|  |  | ||||||
|         self.make_sure_path_exists() |         self.make_sure_path_exists() | ||||||
|         video_response = requests.get(link, cookies=cookies) |         video_response = requests.get(link, cookies=cookies) | ||||||
|         with open(self.BASE_DIR + f"/{title}.mp4", "wb") as output: |         with open(self.BASE_DIR + f"/{title}.mp4", "wb") as output: | ||||||
|             output.write(video_response.content) |             output.write(video_response.content) | ||||||
|  |         return f"MyMailRu/{title}.mp4" | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import os | import os | ||||||
|  | import uuid | ||||||
|  |  | ||||||
| import requests | import requests | ||||||
|  |  | ||||||
| @@ -17,17 +18,18 @@ class YappyParser(BaseParser): | |||||||
|         soup = BeautifulSoup(resp.text, 'lxml') |         soup = BeautifulSoup(resp.text, 'lxml') | ||||||
|  |  | ||||||
|         link = soup.find('video').get("src") |         link = soup.find('video').get("src") | ||||||
|         title = soup.find('title').get_text() |         title = soup.find('video').get("id") | ||||||
|         return link, title |         return link, title | ||||||
|  |  | ||||||
|     def video_download(self, link: str = None, title: str = None): |     def video_download(self, link: str = None, title: str = None): | ||||||
|         if not link and not title: |         if not link and not title: | ||||||
|             link, title = self.get_video_link() |             link, title = self.get_video_link() | ||||||
|  |  | ||||||
|         if os.path.exists(os.path.join(os.getcwd() + f"Yappy/{title}.mp4")): |         if os.path.exists(os.path.join(os.getcwd() + f"/downloads/Yappy/{title}.mp4")): | ||||||
|             return Result(result_type=ResultTypeEnum.EXIST) |             return f"Yappy/{title}.mp4" | ||||||
|  |  | ||||||
|         video_response = requests.get(link) |         video_response = requests.get(link) | ||||||
|         self.make_sure_path_exists() |         self.make_sure_path_exists() | ||||||
|         with open(self.BASE_DIR + f"/{title}.mp4", "wb") as output: |         with open(self.BASE_DIR + f"/{title}.mp4", "wb") as output: | ||||||
|             output.write(video_response.content) |             output.write(video_response.content) | ||||||
|  |         return f"Yappy/{title}.mp4" | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ class BaseParser: | |||||||
|         } |         } | ||||||
|         downloader = VideoDownloader(link=self.params["link"], ydl_opts=ydl_opts) |         downloader = VideoDownloader(link=self.params["link"], ydl_opts=ydl_opts) | ||||||
|         video_info = downloader.get_info() |         video_info = downloader.get_info() | ||||||
|  |         #TODO Добавить динамеческое имя директории сервиса для проверки дублирования | ||||||
|         if os.path.exists( |         if os.path.exists( | ||||||
|                 os.path.join(os.getcwd() + f"Youtube/{video_info['id']}_{video_info['width']}.{video_info['ext']}") |                 os.path.join(os.getcwd() + f"Youtube/{video_info['id']}_{video_info['width']}.{video_info['ext']}") | ||||||
|         ): |         ): | ||||||
| @@ -31,7 +32,7 @@ class BaseParser: | |||||||
|         try: |         try: | ||||||
|             downloader.ydl_opts["quiet"] = False |             downloader.ydl_opts["quiet"] = False | ||||||
|             result = downloader.download() |             result = downloader.download() | ||||||
|             return result |             return f"{video_info['extractor_key']}/{result['id']}_{result['width']}p.{result['ext']}" | ||||||
|         except SiteNotImplementedException as ex: |         except SiteNotImplementedException as ex: | ||||||
|             raise HTTPException( |             raise HTTPException( | ||||||
|                 status_code=400, |                 status_code=400, | ||||||
|   | |||||||
							
								
								
									
										97
									
								
								src/web/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/web/main.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | import asyncio | ||||||
|  | import json | ||||||
|  | import os | ||||||
|  | from ast import literal_eval | ||||||
|  |  | ||||||
|  | import uvicorn | ||||||
|  | from aio_pika import connect, Message, DeliveryMode | ||||||
|  | from fastapi import FastAPI, Request, Form, HTTPException | ||||||
|  | from starlette.middleware.cors import CORSMiddleware | ||||||
|  | from starlette.responses import JSONResponse, FileResponse, StreamingResponse | ||||||
|  | from starlette.templating import Jinja2Templates | ||||||
|  |  | ||||||
|  | from src.core.redis_client import RedisClient | ||||||
|  |  | ||||||
|  | app = FastAPI( | ||||||
|  |     title="video_downloader", openapi_url=f"/api/v1/openapi.json" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | templates = Jinja2Templates(directory="templates") | ||||||
|  |  | ||||||
|  | app.add_middleware( | ||||||
|  |     CORSMiddleware, | ||||||
|  |     allow_origins=["*"], | ||||||
|  |     allow_credentials=True, | ||||||
|  |     allow_methods=["*"], | ||||||
|  |     allow_headers=["*"], | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.get("/") | ||||||
|  | async def index(request: Request): | ||||||
|  |     return templates.TemplateResponse("index.html", {"request": request}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.post('/submit/') | ||||||
|  | async def get_url_for_download_video(request: Request, link: str = Form(...)): | ||||||
|  |     connection = await connect("amqp://guest:guest@localhost/") | ||||||
|  |  | ||||||
|  |     async with connection: | ||||||
|  |         # Creating a channel | ||||||
|  |         channel = await connection.channel() | ||||||
|  |         body = [ | ||||||
|  |  | ||||||
|  |             { | ||||||
|  |                 "link": link, | ||||||
|  |                 "parser": "base", | ||||||
|  |                 "format": "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best", | ||||||
|  |                 "merge_output_format": "mp4", | ||||||
|  |                 "outtmpl": f"downloads/%(extractor_key)s/%(id)s_%(width)sp.%(ext)s", | ||||||
|  |             }, ] | ||||||
|  |         # Sending the message | ||||||
|  |         for link in body: | ||||||
|  |             if "mail" in link["link"]: | ||||||
|  |                 link["parser"] = "MyMailRu" | ||||||
|  |             elif "yappy" in link["link"]: | ||||||
|  |                 link["parser"] = "Yappy" | ||||||
|  |             message = Message( | ||||||
|  |                 json.dumps(link, indent=4).encode('utf-8'), delivery_mode=DeliveryMode.PERSISTENT, | ||||||
|  |             ) | ||||||
|  |             await channel.default_exchange.publish( | ||||||
|  |                 message, | ||||||
|  |                 routing_key='hello', | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         print(f" [x] Sent '{link}'") | ||||||
|  |         red = RedisClient() | ||||||
|  |  | ||||||
|  |         while True: | ||||||
|  |             try: | ||||||
|  |                 mes = await red.get_task_done_queue() | ||||||
|  |                 task = literal_eval(list(mes)[0].decode('utf-8')) | ||||||
|  |                 if task["link"] == link["link"]: | ||||||
|  |                     await red.del_task_from_task_done_queue(task) | ||||||
|  |                     break | ||||||
|  |                 await asyncio.sleep(5) | ||||||
|  |             except (AttributeError, IndexError): | ||||||
|  |                 await asyncio.sleep(5) | ||||||
|  |                 continue | ||||||
|  |         link_to_download_video = str(request.base_url) + "get/?file_path=" + task["result"] | ||||||
|  |  | ||||||
|  |     return JSONResponse({"result": link_to_download_video}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.get('/get/', response_class=FileResponse, status_code=200) | ||||||
|  | async def download_video(file_path): | ||||||
|  |     base = os.path.dirname(os.path.dirname(os.path.abspath(file_path))) | ||||||
|  |     base_download_dir = os.path.join(base, os.pardir, os.pardir, "downloads") | ||||||
|  |  | ||||||
|  |     def iterfile(): | ||||||
|  |         with open(base_download_dir + f'/{file_path}', mode="rb") as file_like: | ||||||
|  |             yield from file_like | ||||||
|  |  | ||||||
|  |     return StreamingResponse(iterfile(), media_type="video/mp4") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     uvicorn.run("src.web.main:app", host="localhost", log_level="info") | ||||||
							
								
								
									
										115
									
								
								src/web/templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/web/templates/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <title>Video Downloading</title> | ||||||
|  | </head> | ||||||
|  | <style> | ||||||
|  |     .custom-btn { | ||||||
|  |       width: 145px; | ||||||
|  |       height: 60px; | ||||||
|  |       color: #fff; | ||||||
|  |       border-radius: 5px; | ||||||
|  |       padding: 10px 25px; | ||||||
|  |       font-family: 'Lato', sans-serif; | ||||||
|  |       font-weight: 500; | ||||||
|  |       background: transparent; | ||||||
|  |       cursor: pointer; | ||||||
|  |       transition: all 0.3s ease; | ||||||
|  |       position: relative; | ||||||
|  |       display: inline-block; | ||||||
|  |        box-shadow:inset 2px 2px 2px 0px rgba(255,255,255,.5), | ||||||
|  |        7px 7px 20px 0px rgba(0,0,0,.1), | ||||||
|  |        4px 4px 5px 0px rgba(0,0,0,.1); | ||||||
|  |       outline: none; | ||||||
|  |     } | ||||||
|  |     .btn-1 { | ||||||
|  |       background: rgb(6,14,131); | ||||||
|  |       background: linear-gradient(0deg, rgba(6,14,131,1) 0%, rgba(12,25,180,1) 100%); | ||||||
|  |       border: none; | ||||||
|  |     } | ||||||
|  |     .btn-1:hover { | ||||||
|  |        background: rgb(0,3,255); | ||||||
|  |     background: linear-gradient(0deg, rgba(0,3,255,1) 0%, rgba(2,126,251,1) 100%); | ||||||
|  |     } | ||||||
|  |     input { | ||||||
|  | 	width: 300px; | ||||||
|  | 	font-size: 13px; | ||||||
|  | 	padding: 6px 0 4px 10px; | ||||||
|  | 	border: 1px solid #cecece; | ||||||
|  | 	background: #F6F6f6; | ||||||
|  | 	border-radius: 8px; | ||||||
|  |     } | ||||||
|  |     .col { | ||||||
|  |     background: #f0f0f0; /* Цвет фона */ | ||||||
|  |     width: 1000px; /* Ширина блока */ | ||||||
|  |     padding: 10px; /* Поля */ | ||||||
|  |     font-size: 1.5em; /* Размер шрифта */ | ||||||
|  |     word-wrap: break-word; /* Перенос слов */ | ||||||
|  |    } | ||||||
|  |    @keyframes spinner-border { | ||||||
|  |     100% { | ||||||
|  |       transform: rotate(360deg); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .submit-spinner { | ||||||
|  |     display: inline-block; | ||||||
|  |     width: 1rem; | ||||||
|  |     height: 1rem; | ||||||
|  |     vertical-align: -0.125em; | ||||||
|  |     border: 0.2em solid currentColor; | ||||||
|  |     border-right-color: transparent; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     -webkit-animation: .75s linear infinite spinner-border; | ||||||
|  |     animation: .75s linear infinite spinner-border; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .submit-spinner_hide { | ||||||
|  |     display: none; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  | <body> | ||||||
|  |     <form method="post" action="/submit" id="download"> | ||||||
|  |          <input type="text" name="link"> | ||||||
|  |          <button type="submit" class="custom-btn btn-1"><span class="submit-spinner submit-spinner_hide"></span> Download</button> | ||||||
|  |     </form> | ||||||
|  |     <div class="col"> | ||||||
|  |         <p> Ссылка для скачивания:</p> | ||||||
|  |         <br> | ||||||
|  |         <a id="result" href="" download></a> | ||||||
|  |         <div class="loader"> | ||||||
|  |             <div class="loader_inner"></div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </body> | ||||||
|  | <script> | ||||||
|  |     function sendForm() { | ||||||
|  |     const xhr = new XMLHttpRequest(); | ||||||
|  |     xhr.open('POST', document.forms.download.action); | ||||||
|  |     xhr.responseType = 'json'; | ||||||
|  |     xhr.onload = () => { | ||||||
|  |       document.forms.download.querySelector('[type="submit"]').disabled = false; | ||||||
|  |       document.forms.download.querySelector('.submit-spinner').classList.add('submit-spinner_hide'); | ||||||
|  |       if (xhr.status !== 200) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       const response = xhr.response; | ||||||
|  |       result.innerHTML = xhr.response.result; | ||||||
|  |       result.href = xhr.response.result; | ||||||
|  |     } | ||||||
|  |     xhr.onerror = () => { | ||||||
|  |       document.forms.download.querySelector('[type="submit"]').disabled = false; | ||||||
|  |       document.forms.download.querySelector('.submit-spinner').classList.add('submit-spinner_hide'); | ||||||
|  |     }; | ||||||
|  |     document.forms.download.querySelector('[type="submit"]').disabled = true; | ||||||
|  |     document.forms.download.querySelector('.submit-spinner').classList.remove('submit-spinner_hide'); | ||||||
|  |     xhr.send(new FormData(document.forms.download)); | ||||||
|  |   } | ||||||
|  |   // при отправке формы | ||||||
|  |   document.forms.download.addEventListener('submit', (e) => { | ||||||
|  |     e.preventDefault(); | ||||||
|  |     sendForm(); | ||||||
|  |   }); | ||||||
|  | </script> | ||||||
|  | </html> | ||||||
		Reference in New Issue
	
	Block a user
	 nikili0n
						nikili0n