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