Skip to content

Commit

Permalink
new option to save downloads on path
Browse files Browse the repository at this point in the history
  • Loading branch information
programador51 committed May 3, 2024
1 parent 9cfe934 commit 7b0eed7
Show file tree
Hide file tree
Showing 21 changed files with 387 additions and 82 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "telegram_addon_downloader",
"private": true,
"version": "0.0.0",
"version": "1.4.0",
"author": {
"name": "José Luis Pérez Olguín"
},
"description": "Firefox extension to download content from private/non downloable groups or channels",
"description": "Firefox extension to download content from private/non downloable groups or channels. https://allmylinks.com/programador51",
"keywords": [
"react",
"download",
Expand Down
3 changes: 2 additions & 1 deletion public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"tabs",
"webRequest",
"webRequestBlocking",
"https://web.telegram.org/k"
"https://web.telegram.org/k",
"downloads"
],
"icons": {
"48": "48x48.png",
Expand Down
60 changes: 39 additions & 21 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import ui from "./styles.module.scss";
import useAddOn from "./customHooks/useAddOn";
import Header from "./molescules/Header";
import Gallery from "./structure/Gallery";
import { createContext } from "react";
import { ReturnUseAddOn } from "./customHooks/useAddOn/types";
import ClickMe from "./molescules/Clickme";

export const AddOnContext = createContext<ReturnUseAddOn | undefined>(
undefined
);

function App() {
const hook = useAddOn();
Expand All @@ -20,6 +27,11 @@ function App() {
<p className="mx-5">
Once you enter the link, close and open this popup
</p>

<div className={`mx-5 ${ui.pleaseClickMe}`}>
<p>Btw, if you like this extension check out my other works</p>
<ClickMe />
</div>
</main>
);

Expand All @@ -32,27 +44,33 @@ function App() {
);

return (
<main className={ui.addOn}>
<Header />

<Gallery items={hook.state} />

{hook.state.length >= 1 ? (
<footer>
<p className="m-0">
Pictures found
<span className="mx-2 badge bg-info">{hook.state.length}</span>{" "}
</p>

<button
className="btn btn-primary"
onClick={async () => await hook.handleDownloadAll()}
>
Download all
</button>
</footer>
) : null}
</main>
<AddOnContext.Provider value={hook}>
<main className={ui.addOn}>
<Header />

<Gallery items={hook.state} />

{hook.state.length >= 1 ? (
<footer>
<p className="m-0">
Pictures found
<span className="mx-2 badge bg-info">{hook.state.length}</span>
</p>

<div className={ui.footerInfo}>
<ClickMe />

<button
className="btn btn-primary btn-sm"
onClick={async () => await hook.handleDownloadAll()}
>
Download all
</button>
</div>
</footer>
) : null}
</main>
</AddOnContext.Provider>
);
}

Expand Down
37 changes: 35 additions & 2 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import browser from "webextension-polyfill";
import { AddOnMessage} from "./typesBackgroundScript";
import { MessageBrowserActions } from "./helpers/content_script/types";
import { decodeStreamChunks, fetchAndCombineStreams } from "./helpers/files";
import { DownloadImageMessage, MessageBrowserActions } from "./helpers/content_script/types";
import { base64toBlob, decodeStreamChunks, fetchAndCombineStreams, retrieveExtension } from "./helpers/files";
import { saveAs } from "file-saver";
import { retrieveAddOnConfiguration } from "./helpers/dom";

// Listen for messages from the content script
browser.runtime.onMessage.addListener(async (message: string) => {
Expand All @@ -28,6 +29,12 @@ browser.runtime.onMessage.addListener(async (message: string) => {
break;
}

case "downloadImage":{
const dto:MessageBrowserActions<"downloadImage"> = JSON.parse(message)
downloadImage(dto.message);
break;
}

default:
return;
}
Expand All @@ -36,6 +43,32 @@ browser.runtime.onMessage.addListener(async (message: string) => {
storeVideoStreamRequests();

////////////////////////////////////////////////////////////////////////////
function downloadImage(dto:DownloadImageMessage){
try {
const url = URL.createObjectURL(base64toBlob(dto.base64));

const { downloadPath } = retrieveAddOnConfiguration();

const extension = retrieveExtension(dto.base64);

let filename = "";

if(downloadPath!=="/") filename = `${downloadPath.substring(1,downloadPath.length)}/${window.crypto.randomUUID()}.${extension}`

if(downloadPath==="/"||downloadPath==="") filename = `${window.crypto.randomUUID()}.${extension}`;

console.log({filename , downloadPath})

browser.downloads.download({
url,
filename,
});
} catch (error) {
console.log({error});
alert(`There's has been an error to download the image, please report it on telegram group. Error code: 'f4853378-ff59-4fc6-ad26-5e2dc423eec6'`);
}
}


function downloadMediaVideo(urls:string[]){
fetchAndCombineStreams(urls)
Expand Down
46 changes: 43 additions & 3 deletions src/content_script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "./helpers/content_script";
import { MessageBrowserActions } from "./helpers/content_script/types";
import { CrawledImageDom } from "./typesContentScript";
import { checkIsVideDomTag, retrieveAddOnConfiguration } from "./helpers/dom";

showHiddenMediaBtns();
listenAddOn();
Expand Down Expand Up @@ -40,14 +41,27 @@ function observeMediaElementChanges() {
observer.observe(document.body, config);
}

async function downloadImage(dom: HTMLElement) {
/**
*
* @param targetElement - This can be an `img` tag or `video` tag
*/
async function downloadImage(dom: HTMLImageElement|HTMLElement) {

const isVideo = checkIsVideDomTag(dom);

// TODO: Implement the download for video as possible without dom interaction
if(dom instanceof HTMLImageElement && !isVideo){
await downloadImageDom(dom.src);
return;
}
dom.click();

const media = await getMediaControls();

const downloadBtn = media.childNodes[2] as HTMLElement;

downloadBtn.click();
downloadBtn.click();

const escapeKeyEvent = new KeyboardEvent("keydown", {
key: "Escape",
code: "Escape",
Expand All @@ -66,7 +80,6 @@ async function getMediaControls(): Promise<HTMLElement> {
const containerMedia = document.getElementsByClassName(
"media-viewer-buttons"
);
console.log({ containerMedia });
if (containerMedia.length > 0) {
resolve(containerMedia[0] as HTMLElement);
} else {
Expand Down Expand Up @@ -176,6 +189,7 @@ async function getAllImages(): Promise<CrawledImageDom[]> {
blob: base64Image,
height: imageElement.naturalWidth,
width: imageElement.naturalHeight,
urlSrc:imageElement.src
};

return crawledContent;
Expand All @@ -193,6 +207,32 @@ async function getAllImages(): Promise<CrawledImageDom[]> {
return [];
}

// blob:https://web.telegram.org/81018939-9915-4693-ae35-3e8828db5080

/**
*
* @param srcBlobUrl - Url of the blob saved on memory
* @returns {Promise<string>}
* @example
*
* downloadImageItemDowm('blob:https://web.telegram.org/81018939-9915-4693-ae35-3e8828db5080')
*/
async function downloadImageDom(srcBlobUrl:string):Promise<void>{
const base64Media = await retrieveImageAsBase64(srcBlobUrl);

const config = retrieveAddOnConfiguration();

const message = {
base64:base64Media,
path:config.downloadPath
};

browser.runtime.sendMessage(JSON.stringify({
action:"downloadImage",
message
}));
}

function listenAddOn() {
browser.runtime.onMessage.addListener(async () => {
const [images] = await Promise.all([getAllImages()]);
Expand Down
27 changes: 22 additions & 5 deletions src/customHooks/useAddOn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import browser from "webextension-polyfill";
import { CrawledImageDom } from "../../typesContentScript";
import { useContext, useEffect, useState } from "react";
import { MessageBrowserActions } from "../../helpers/content_script/types";
import { downloadBase64, zipGallery } from "../../helpers/files";
import { blobToBase64, zipGallery } from "../../helpers/files";
import { ContextGlobal } from "../../structure/configuration/Global";
import { saveAs } from "file-saver";
import { ReturnUseAddOn } from "./types";

export default function useAddOn() {
export default function useAddOn():ReturnUseAddOn {
const [state, setState] = useState<CrawledImageDom[]>([]);
const [isTelegramK, setIsTelegramK] = useState<boolean | null>(null);

Expand Down Expand Up @@ -85,15 +85,32 @@ export default function useAddOn() {

const zippedFiles = await zipGallery(toZip);

saveAs(zippedFiles, `${window.crypto.randomUUID()}.zip`);
const b64ZipFile = await blobToBase64(zippedFiles);
attemptDownloadFile(`data:application/zip;base64,${b64ZipFile}`)
} else {
state.forEach((data) => downloadBase64(data.blob));
state.forEach((data) => attemptDownloadFile(data.blob));
}
}

/**
* Download the file with `background_scripts` file
* @param blob - Url of the blob
*/
async function attemptDownloadFile(blob:string){
const dto:MessageBrowserActions<"downloadImage"> = {
action:"downloadImage",
message:{
base64:blob
}
}

browser.runtime.sendMessage(JSON.stringify(dto));
}

return {
handleDownloadAll,
state,
isTelegramK,
attemptDownloadFile
};
}
8 changes: 8 additions & 0 deletions src/customHooks/useAddOn/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { CrawledImageDom } from "../../typesContentScript";

export interface ReturnUseAddOn {
state: CrawledImageDom[];
isTelegramK: boolean | null;
handleDownloadAll: () => Promise<void>;
attemptDownloadFile: (blob:string) => Promise<void>;
}
41 changes: 31 additions & 10 deletions src/customHooks/useGlobal/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
import { useEffect, useState } from "react";
import { GlobalStateI, ReturnUseGlobal } from "./types";
import { MutableRefObject, useEffect, useRef, useState } from "react";
import { GlobalStateI, ReturnUseGlobal, SetPathDownload, TypePath } from "./types";
import { retrieveAddOnConfiguration } from "../../helpers/dom";

const INITIAL_STATE: GlobalStateI = {
showThumbnails: false,
zipBulkDownloads: false,
displayDownloadOnChat: true,
initialLoad: true,
downloadPath: "/",
typePath:"root"
};

export default function useGlobal(): ReturnUseGlobal {
const [state, setState] = useState<GlobalStateI>(INITIAL_STATE);

useEffect(() => {
const data = window.localStorage.getItem("configuration");

const parsedData: GlobalStateI =
data === null ? INITIAL_STATE : JSON.parse(data);
const pathTypes:MutableRefObject<{[key in TypePath]:TypePath}> = useRef({
custom:"custom",
default:"default",
root:"root"
});

window.localStorage.setItem("configuration", JSON.stringify(parsedData));
useEffect(() => {
const parsedData = retrieveAddOnConfiguration();
setState({ ...parsedData, initialLoad: false });
}, []);

useEffect(() => {
if (state.initialLoad) return;

const info = JSON.stringify(state);

window.localStorage.setItem("configuration", info);
}, [state]);

Expand All @@ -47,10 +49,29 @@ export default function useGlobal(): ReturnUseGlobal {
displayDownloadOnChat,
}));

const setPathDownload: SetPathDownload = (type, path = "/") => {
if (type === "default" || type==="custom") {
setState((current) => ({
...current,
typePath:type,
downloadPath: path,
}));
return;
}

setState(current=>({
...current,
typePath:type,
downloadPath:""
}))
};

return {
...state,
setShowThumbnails,
setBulkDownload,
setDisplayDownloadOnChat,
setPathDownload,
pathTypes:pathTypes.current
};
}
Loading

0 comments on commit 7b0eed7

Please sign in to comment.