Compare commits
19 Commits
a1cd6cc89a
...
master
Author | SHA1 | Date |
---|---|---|
icechen | 57daf38d3d | |
icechen | f3baaa174f | |
icechen | fdc5516388 | |
icechen | b55af04082 | |
icechen | be3da0a45d | |
icechen | 473d3e55c7 | |
icechen | fc35a1f0c8 | |
icechen | 5d479d322b | |
icechen | c5868a8ebf | |
icechen | 1d44a7eca4 | |
icechen | 907409203c | |
icechen | 8b8cfc64bc | |
icechen | 8c78b93e6e | |
icechen | 5a1c0cfffb | |
icechen | a7bd08ec79 | |
icechen | 3044a43a89 | |
icechen | 26cb0eaef1 | |
icechen | 78b2b4b753 | |
icechen | 65ac000169 |
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
|
@ -3,6 +3,11 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.7.1",
|
||||||
|
"@emotion/styled": "^11.6.0",
|
||||||
|
"@mui/icons-material": "^5.4.2",
|
||||||
|
"@mui/material": "^5.4.2",
|
||||||
|
"@reduxjs/toolkit": "^1.8.0",
|
||||||
"@testing-library/jest-dom": "^5.16.2",
|
"@testing-library/jest-dom": "^5.16.2",
|
||||||
"@testing-library/react": "^12.1.2",
|
"@testing-library/react": "^12.1.2",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
@ -10,10 +15,15 @@
|
||||||
"@types/node": "^16.11.25",
|
"@types/node": "^16.11.25",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
|
"axios": "^0.26.0",
|
||||||
|
"filesize": "^8.0.7",
|
||||||
"mtproton": "6.0.0",
|
"mtproton": "6.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-redux": "^7.2.6",
|
||||||
|
"react-router-dom": "^6.2.1",
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
|
"redux": "^4.1.2",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.5.5",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
@ -41,5 +51,11 @@
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {}
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.2",
|
||||||
|
"postcss": "^8.4.7",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.1.8",
|
||||||
|
"redux-devtools": "^3.7.0",
|
||||||
|
"tailwindcss": "^3.0.23"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
|
@ -26,7 +26,7 @@
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>React App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body style="margin: 0">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
<!--
|
||||||
|
@ -39,5 +39,13 @@
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
-->
|
-->
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||||
|
/>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
38
src/App.css
38
src/App.css
|
@ -1,38 +0,0 @@
|
||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
|
||||||
render(<App />);
|
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
48
src/App.tsx
48
src/App.tsx
|
@ -1,48 +0,0 @@
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import "./App.css";
|
|
||||||
import Client from "mtproton/envs/browser";
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
let [countriesList, setcountriesList] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let client = new Client({
|
|
||||||
api_id: 18987971,
|
|
||||||
api_hash: "fcfd9e6ed3f9e48a360bb57cc0d59d98",
|
|
||||||
});
|
|
||||||
client.call("help.getCountriesList").then((result: any) => {
|
|
||||||
console.log("country:", result);
|
|
||||||
result.countries.map((country: any) => {
|
|
||||||
// @ts-ignore
|
|
||||||
setcountriesList((oldArray) => [
|
|
||||||
...oldArray,
|
|
||||||
country.default_name + ": " + country.country_codes[0].country_code,
|
|
||||||
]);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// client
|
|
||||||
// .invoke({
|
|
||||||
// _: "getCountryCode",
|
|
||||||
// })
|
|
||||||
// .then((res) => {
|
|
||||||
// console.log(res);
|
|
||||||
// });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="App">
|
|
||||||
{countriesList.map((country: any, index) => {
|
|
||||||
return (
|
|
||||||
<div key={index}>
|
|
||||||
{country}
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
import style from "./style.module.css";
|
||||||
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { Close } from "@mui/icons-material";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
IconButton,
|
||||||
|
} from "@mui/material";
|
||||||
|
import fileSize from "filesize";
|
||||||
|
import SendIcon from "@mui/icons-material/Send";
|
||||||
|
import { inputFile, sendFile, uploadBigFile } from "../../telegram/telegram";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { addFileOfStorage } from "../../store/fileList";
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
normal,
|
||||||
|
addFile,
|
||||||
|
receive,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isSignIn: boolean;
|
||||||
|
onNotSignIn: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HomeButton(props: Props) {
|
||||||
|
let [status, setStatus] = useState(Status.normal);
|
||||||
|
let [boxStyle, setBoxStyle] = useState([style.box]);
|
||||||
|
let [isReceive, setIsReceive] = useState(false);
|
||||||
|
let [waitedFileList, setWaitedFileList] = useState<any[]>([]);
|
||||||
|
let [uploadRatio, setUploadRatio] = useState(0);
|
||||||
|
let [isFinish, setIsFinish] = useState(false);
|
||||||
|
let [startUploadTime, setStartUploadTime] = useState(0);
|
||||||
|
|
||||||
|
let uploadSpeed = useMemo(() => {
|
||||||
|
if (waitedFileList.length === 0) {
|
||||||
|
return "0/s";
|
||||||
|
}
|
||||||
|
let size = waitedFileList[0].size;
|
||||||
|
if (uploadRatio === 0) {
|
||||||
|
return "0/s";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
fileSize((size * uploadRatio * 10) / (Date.now() - startUploadTime)) +
|
||||||
|
"/s"
|
||||||
|
);
|
||||||
|
}, [startUploadTime, uploadRatio, waitedFileList]);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
let onNormal = () => {
|
||||||
|
setStatus(Status.normal);
|
||||||
|
};
|
||||||
|
let onAddFile = () => {
|
||||||
|
setStatus(Status.addFile);
|
||||||
|
};
|
||||||
|
let onReceive = () => {
|
||||||
|
setStatus(Status.receive);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
switch (status) {
|
||||||
|
case Status.normal:
|
||||||
|
setBoxStyle([style.box]);
|
||||||
|
break;
|
||||||
|
case Status.addFile:
|
||||||
|
setBoxStyle([style.box, style.boxActivePlus]);
|
||||||
|
break;
|
||||||
|
case Status.receive:
|
||||||
|
setBoxStyle([style.box, style.boxActiveReceive]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
let addFileInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
let OnChangeFileInput = (e: any) => {
|
||||||
|
setWaitedFileList(e.target.files);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handleFileUpload = () => {
|
||||||
|
setUploadRatio(1);
|
||||||
|
setStartUploadTime(Date.now());
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function (event) {
|
||||||
|
if (event.target && event.target.result) {
|
||||||
|
let bytes = event.target.result;
|
||||||
|
uploadBigFile(bytes as ArrayBuffer, setUploadRatio)
|
||||||
|
.then((file) => {
|
||||||
|
console.log("uploadFile ret:", file);
|
||||||
|
sendFile(
|
||||||
|
// @ts-ignore
|
||||||
|
inputFile(file.file_id, file.total_part, waitedFileList[0].name),
|
||||||
|
waitedFileList[0].type
|
||||||
|
).then((result) => {
|
||||||
|
console.log("sendFile ret:", result);
|
||||||
|
result.updates[1].message.media.document.file_name =
|
||||||
|
waitedFileList[0].name;
|
||||||
|
dispatch(
|
||||||
|
addFileOfStorage(result.updates[1].message.media.document)
|
||||||
|
);
|
||||||
|
setIsFinish(true);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("uploadFile error:", error);
|
||||||
|
alert(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(waitedFileList[0]);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={boxStyle.join(" ")}>
|
||||||
|
{!isReceive ? (
|
||||||
|
<>
|
||||||
|
<div className={style.plus}>➕</div>
|
||||||
|
<div
|
||||||
|
className={style.addFile}
|
||||||
|
onMouseEnter={onAddFile}
|
||||||
|
onMouseLeave={onNormal}
|
||||||
|
onClick={() => addFileInput.current?.click()}
|
||||||
|
>
|
||||||
|
添加文件
|
||||||
|
<input
|
||||||
|
ref={addFileInput}
|
||||||
|
type="file"
|
||||||
|
style={{ display: "none" }}
|
||||||
|
onChange={OnChangeFileInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className={style.receive}
|
||||||
|
onMouseEnter={onReceive}
|
||||||
|
onMouseLeave={onNormal}
|
||||||
|
onClick={() => {
|
||||||
|
if (props.isSignIn) {
|
||||||
|
setIsReceive(true);
|
||||||
|
} else {
|
||||||
|
props.onNotSignIn();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
接收文件
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
placeholder={"输入6位传输口令获取文件"}
|
||||||
|
style={{}}
|
||||||
|
className={isReceive ? style.receiveInput : style.receiveInputNone}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
className={style.receiveInputClose}
|
||||||
|
aria-label="delete"
|
||||||
|
onClick={() => {
|
||||||
|
setIsReceive(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Close />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Dialog open={waitedFileList.length !== 0}>
|
||||||
|
<DialogTitle>文件上传</DialogTitle>
|
||||||
|
<DialogContent sx={{ minWidth: 300 }}>
|
||||||
|
是否上传文件?
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
文件名: {waitedFileList.length > 0 ? waitedFileList[0].name : ""}
|
||||||
|
<br />
|
||||||
|
文件大小:{" "}
|
||||||
|
{waitedFileList.length > 0 ? fileSize(waitedFileList[0].size) : ""}
|
||||||
|
<br />
|
||||||
|
{uploadRatio !== 0 && <>正在上传: {uploadRatio.toFixed(2)}%</>}
|
||||||
|
<br />
|
||||||
|
{uploadRatio !== 0 && (
|
||||||
|
<>已用时间: {(Date.now() - startUploadTime) / 1000}s</>
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
{uploadRatio !== 0 && <>上传速度: {uploadSpeed}</>}
|
||||||
|
<br />
|
||||||
|
{isFinish && <>上传完成</>}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => {
|
||||||
|
setWaitedFileList([]);
|
||||||
|
setUploadRatio(0);
|
||||||
|
setIsFinish(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
{uploadRatio === 0 && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleFileUpload}
|
||||||
|
endIcon={<SendIcon />}
|
||||||
|
>
|
||||||
|
上传
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
body {
|
||||||
|
/*background-color: yellow;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 400px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 0 15px #a7a7a7;
|
||||||
|
transition: 0.5s;
|
||||||
|
color: #252525;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boxActivePlus {
|
||||||
|
transform: translateX(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.boxActiveReceive {
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plus {
|
||||||
|
font-size: 40px;
|
||||||
|
margin-left: 30px;
|
||||||
|
height: 100px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addFile {
|
||||||
|
font-size: 24px;
|
||||||
|
padding-left: 30px;
|
||||||
|
margin-right: 140px;
|
||||||
|
font-weight: bold;
|
||||||
|
flex-grow: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receive {
|
||||||
|
position: absolute;
|
||||||
|
height: 60px;
|
||||||
|
width: 120px;
|
||||||
|
right: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: black;
|
||||||
|
border-radius: 35px;
|
||||||
|
border: 0;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receive:hover {
|
||||||
|
background-color: #fdda65;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiveInput {
|
||||||
|
position: absolute;
|
||||||
|
width: 348px;
|
||||||
|
height: 48px;
|
||||||
|
background-color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #252525;
|
||||||
|
border-radius: 35px;
|
||||||
|
margin: 0 0 0 10px;
|
||||||
|
padding: 0 0 0 20px;
|
||||||
|
line-height: 60px;
|
||||||
|
outline: none;
|
||||||
|
border: #f1f1f1 solid 6px;
|
||||||
|
transition: 0.3s;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiveInputNone {
|
||||||
|
width: 120px;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiveInput:hover {
|
||||||
|
border: #fdda65 solid 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiveInput:active {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiveInput::selection {
|
||||||
|
background-color: #ffd95a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiveInputClose {
|
||||||
|
position: absolute !important;
|
||||||
|
right: 20px;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Button } from "@mui/material";
|
||||||
|
import style from "./style.module.css";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isSignIn: boolean;
|
||||||
|
onOpenSignIn: () => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HomeMenu(props: Props) {
|
||||||
|
return (
|
||||||
|
<div className={props.className}>
|
||||||
|
<Button className={style.text}>定价</Button>
|
||||||
|
{props.isSignIn ? (
|
||||||
|
<Link className={style.text} to={`/dashboard`}>
|
||||||
|
<Button className={style.text}>文件列表</Button>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Button className={style.text} onClick={props.onOpenSignIn}>
|
||||||
|
注册 / 登录
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
.text {
|
||||||
|
color: #252525 !important;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { Button, Paper, Snackbar, TextField } from "@mui/material";
|
||||||
|
import style from "./style.module.css";
|
||||||
|
import CountriesInput, { Country } from "../signin/countriesInput";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { sendSignInCode, signIn } from "../../telegram/telegram";
|
||||||
|
import { getMe } from "../../telegram/user";
|
||||||
|
import { login } from "../../store/user";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
|
||||||
|
export function HomeSignIn() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
let [country, setCountry] = useState({} as Country | null);
|
||||||
|
let [phone, setPhone] = useState("");
|
||||||
|
let [phoneCode, setPhoneCode] = useState("");
|
||||||
|
let [openSuccess, setOpenSuccess] = useState(false);
|
||||||
|
const phoneNumber = useMemo(
|
||||||
|
() => (country ? country.country_code : "") + phone,
|
||||||
|
[country, phone]
|
||||||
|
);
|
||||||
|
|
||||||
|
let [codeHash, setCodeHash] = useState("");
|
||||||
|
|
||||||
|
let onSendCode = () => {
|
||||||
|
sendSignInCode(phoneNumber)
|
||||||
|
.then((phone_code_hash: string) => {
|
||||||
|
setCodeHash(phone_code_hash);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log("error", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let onSignIn = () => {
|
||||||
|
signIn(phoneNumber, phoneCode, codeHash)
|
||||||
|
.then((user: any) => {
|
||||||
|
console.log("user", user);
|
||||||
|
setOpenSuccess(true);
|
||||||
|
getMe()
|
||||||
|
.then(function (user) {
|
||||||
|
// 已登录
|
||||||
|
dispatch(login(user));
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
if (error.code === 401) {
|
||||||
|
// Unauthorized
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log("error", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
width: "400px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
border: "1px solid #e0e0e0",
|
||||||
|
padding: "50px 0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 className={style.h1}>登录 / 注册</h1>
|
||||||
|
<div className={style.describe}>可通过登录码验证或二维码注册/登录</div>
|
||||||
|
{codeHash === "" ? (
|
||||||
|
<>
|
||||||
|
<div className={style.signInInput}>
|
||||||
|
<CountriesInput
|
||||||
|
onChange={(value) => {
|
||||||
|
setCountry(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
style={{ marginTop: "10px" }}
|
||||||
|
label="Phone Number"
|
||||||
|
className={style.phone}
|
||||||
|
placeholder={"手机号码"}
|
||||||
|
value={phone}
|
||||||
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size={"large"}
|
||||||
|
style={{ alignSelf: "flex-end", marginRight: "50px" }}
|
||||||
|
variant="contained"
|
||||||
|
onClick={onSendCode}
|
||||||
|
>
|
||||||
|
下一步
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className={style.signInInput}>
|
||||||
|
<TextField
|
||||||
|
style={{ marginTop: "10px" }}
|
||||||
|
label="Auth Code"
|
||||||
|
className={style.phone}
|
||||||
|
placeholder={"验证码"}
|
||||||
|
value={phoneCode}
|
||||||
|
onChange={(e) => setPhoneCode(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||||
|
<Button
|
||||||
|
size={"large"}
|
||||||
|
style={{ alignSelf: "flex-end", marginRight: "50px" }}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
setCodeHash("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
上一步
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={"large"}
|
||||||
|
style={{ alignSelf: "flex-end", marginRight: "50px" }}
|
||||||
|
variant="contained"
|
||||||
|
onClick={onSignIn}
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={style.explain}>
|
||||||
|
<h1 className={style.h1}>重要说明</h1>
|
||||||
|
<div className={style.explainContent}>
|
||||||
|
本服务基于Telegram官方文件上传API,符合
|
||||||
|
Telegram用户协议和隐私政策。但请您不要滥
|
||||||
|
用此服务,以避免账号被telegram封禁或导致 服务不可使用。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style.describe}>
|
||||||
|
未注册账号自动创建为新账号
|
||||||
|
<br /> 登录注册均视为已同意 用户协议 和 隐私政策。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Snackbar
|
||||||
|
anchorOrigin={{ vertical: "top", horizontal: "right" }}
|
||||||
|
open={openSuccess}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenSuccess(false);
|
||||||
|
}}
|
||||||
|
message="登录成功"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
.h1 {
|
||||||
|
font-size: 36px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.describe {
|
||||||
|
color: #a7a7a7;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signInInput {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone {
|
||||||
|
/*margin-left: 50px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.explain {
|
||||||
|
margin: 30px 0;
|
||||||
|
padding: 30px 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explainContent {
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { Autocomplete, Box, TextField } from "@mui/material";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import Telegram from "../../telegram/telegram";
|
||||||
|
|
||||||
|
export interface Country {
|
||||||
|
default_name: string;
|
||||||
|
country_code: string;
|
||||||
|
// patterns?: string;
|
||||||
|
iso2: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CountriesInput(props: {
|
||||||
|
onChange: (country: Country | null) => void;
|
||||||
|
}) {
|
||||||
|
let [countriesList, setCountriesList] = useState([] as Country[]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Telegram.call("help.getCountriesList", undefined, undefined, true).then(
|
||||||
|
(result: any) => {
|
||||||
|
let resp: Country[] = [];
|
||||||
|
result.countries.map((country: any) => {
|
||||||
|
resp.push({
|
||||||
|
default_name: country.default_name,
|
||||||
|
country_code: country.country_codes[0].country_code,
|
||||||
|
// patterns:
|
||||||
|
// country.country_codes[0]?.patterns.length > 0
|
||||||
|
// ? country.country_codes[0]?.patterns[0]
|
||||||
|
// : "",
|
||||||
|
iso2: country.iso2,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
setCountriesList(resp);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
disablePortal
|
||||||
|
autoHighlight
|
||||||
|
id="country-select"
|
||||||
|
options={countriesList.sort(function (a, b) {
|
||||||
|
const nameA = a.default_name.toUpperCase(); // ignore upper and lowercase
|
||||||
|
const nameB = b.default_name.toUpperCase(); // ignore upper and lowercase
|
||||||
|
if (nameA < nameB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (nameA > nameB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// names must be equal
|
||||||
|
return 0;
|
||||||
|
})}
|
||||||
|
sx={{ width: 300 }}
|
||||||
|
onChange={(_event, value) => {
|
||||||
|
props.onChange(value as Country);
|
||||||
|
}}
|
||||||
|
getOptionLabel={(option) => option.default_name}
|
||||||
|
renderOption={(props, option) => (
|
||||||
|
<Box
|
||||||
|
component="li"
|
||||||
|
sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
loading="lazy"
|
||||||
|
width="20"
|
||||||
|
src={`https://flagcdn.com/w20/${option.iso2.toLowerCase()}.png`}
|
||||||
|
srcSet={`https://flagcdn.com/w40/${option.iso2.toLowerCase()}.png 2x`}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{option.default_name} ({option.iso2}) +{option.country_code}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
renderInput={(params) => <TextField {...params} label="Country" />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CountriesInput;
|
|
@ -0,0 +1,185 @@
|
||||||
|
import CountriesInput, { Country } from "./countriesInput";
|
||||||
|
import { Button, Input, InputAdornment, Paper, TextField } from "@mui/material";
|
||||||
|
import SendIcon from "@mui/icons-material/Send";
|
||||||
|
import { Download, Upload } from "@mui/icons-material";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { User } from "../../user";
|
||||||
|
import Telegram, {
|
||||||
|
downloadFile,
|
||||||
|
inputFile,
|
||||||
|
sendFile,
|
||||||
|
uploadBigFile,
|
||||||
|
} from "../../telegram/telegram";
|
||||||
|
|
||||||
|
function SignIn() {
|
||||||
|
let [phone, setPhone] = useState("");
|
||||||
|
let [code, setCode] = useState("");
|
||||||
|
let [phoneCodeHash, setPhoneCodeHash] = useState("");
|
||||||
|
let [country, setCountry] = useState({} as Country | null);
|
||||||
|
let [user, setUser] = useState({} as User);
|
||||||
|
let [fileDocument, setFileDocument] = useState({} as any);
|
||||||
|
const [fileList, setFileList] = useState([]);
|
||||||
|
|
||||||
|
let sendCode = () => {
|
||||||
|
console.log("phone:", phone);
|
||||||
|
Telegram.call("auth.sendCode", {
|
||||||
|
phone_number: (country ? country.country_code : "") + phone,
|
||||||
|
settings: {
|
||||||
|
_: "codeSettings",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result: any) => {
|
||||||
|
console.log("auth.sendCode:", result);
|
||||||
|
setPhoneCodeHash(result.phone_code_hash);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log("auth.sendCode:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let login = () => {
|
||||||
|
console.log("auth code:", code);
|
||||||
|
console.log("auth hash code:", phoneCodeHash);
|
||||||
|
Telegram.call("auth.signIn", {
|
||||||
|
phone_number: (country ? country.country_code : "") + phone,
|
||||||
|
phone_code_hash: phoneCodeHash,
|
||||||
|
phone_code: code,
|
||||||
|
})
|
||||||
|
.then((result: any) => {
|
||||||
|
console.log("auth.signIn:", result);
|
||||||
|
setUser(result.user);
|
||||||
|
SendMessage();
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log("auth.signIn:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let SendMessage = () => {
|
||||||
|
Telegram.call("messages.sendMessage", {
|
||||||
|
peer: {
|
||||||
|
_: "inputPeerSelf",
|
||||||
|
},
|
||||||
|
message: "ice",
|
||||||
|
random_id: (10000 + Math.random() * (100000 - 10000))
|
||||||
|
.toFixed()
|
||||||
|
.toString(),
|
||||||
|
})
|
||||||
|
.then((result: any) => {
|
||||||
|
console.log("messages.sendMessage:", result);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log("messages.sendMessage:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let uploadFile = () => {
|
||||||
|
console.log("uploadFile:", fileList);
|
||||||
|
// @ts-ignore
|
||||||
|
uploadBigFile(fileList[0]?.bytes)
|
||||||
|
.then((file) => {
|
||||||
|
console.log("uploadFile ret:", file);
|
||||||
|
sendFile(
|
||||||
|
// @ts-ignore
|
||||||
|
inputFile(file.file_id, file.total_part, fileList[0].name),
|
||||||
|
// @ts-ignore
|
||||||
|
fileList[0].type
|
||||||
|
).then((result) => {
|
||||||
|
console.log("sendFile ret:", result);
|
||||||
|
setFileDocument(result.updates[1].message.media);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("uploadFile error:", error);
|
||||||
|
alert(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let changeFile = (e: any) => {
|
||||||
|
console.log(e.target.files);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function (event) {
|
||||||
|
if (event.target && event.target.result) {
|
||||||
|
let bytes = event.target.result;
|
||||||
|
console.log(bytes);
|
||||||
|
e.target.files[0].bytes = bytes;
|
||||||
|
setFileList(e.target.files);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(e.target.files[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
let download = () => {
|
||||||
|
console.log("download:", fileDocument);
|
||||||
|
downloadFile(fileDocument).catch((error) => {
|
||||||
|
console.log("download error:", error);
|
||||||
|
alert(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={3}
|
||||||
|
sx={{
|
||||||
|
padding: "150px 50px",
|
||||||
|
position: "absolute",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "space-around",
|
||||||
|
height: "40%",
|
||||||
|
width: "40%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CountriesInput
|
||||||
|
onChange={(country) => {
|
||||||
|
console.log(country);
|
||||||
|
setCountry(country);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label={"Phone Number"}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
{country && country.country_code
|
||||||
|
? "+" + country.country_code
|
||||||
|
: ""}
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
value={phone}
|
||||||
|
onChange={(e) => {
|
||||||
|
console.log(e.target.value);
|
||||||
|
setPhone(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label={"Auth Code"}
|
||||||
|
value={code}
|
||||||
|
onChange={(e) => {
|
||||||
|
console.log(e.target.value);
|
||||||
|
setCode(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{phoneCodeHash === "" ? (
|
||||||
|
<Button variant={"contained"} onClick={sendCode}>
|
||||||
|
发送验证码
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button variant={"contained"} onClick={login} endIcon={<SendIcon />}>
|
||||||
|
登录
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Input type="file" onChange={changeFile} />
|
||||||
|
<Button variant={"contained"} onClick={uploadFile} endIcon={<Upload />}>
|
||||||
|
上传
|
||||||
|
</Button>
|
||||||
|
{/*<Button variant={"contained"} onClick={download} endIcon={<Download />}>*/}
|
||||||
|
{/* 下载*/}
|
||||||
|
{/*</Button>*/}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SignIn;
|
|
@ -1 +1,9 @@
|
||||||
declare module "mtproton/envs/browser";
|
declare module "mtproton/envs/browser";
|
||||||
|
declare module "mtproton/src/utils/common";
|
||||||
|
declare module "mtproton/src/index";
|
||||||
|
declare module "mtproton/envs/browser/sha1";
|
||||||
|
declare module "mtproton/envs/browser/sha256";
|
||||||
|
declare module "mtproton/envs/browser/pbkdf2";
|
||||||
|
declare module "mtproton/envs/browser/transport";
|
||||||
|
declare module "mtproton/envs/browser/get-random-bytes";
|
||||||
|
declare module "mtproton/envs/browser/get-local-storage";
|
||||||
|
|
|
@ -1,13 +1,3 @@
|
||||||
body {
|
@tailwind base;
|
||||||
margin: 0;
|
@tailwind components;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
@tailwind utilities;
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
|
@ -1,14 +1,57 @@
|
||||||
import React from 'react';
|
// @ts-nocheck
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import './index.css';
|
import React from "react";
|
||||||
import App from './App';
|
import ReactDOM from "react-dom";
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from "./reportWebVitals";
|
||||||
|
import Dashboard from "./pages/dashboard";
|
||||||
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
|
import Home from "./pages/home";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import store from "./store";
|
||||||
|
import { updateOfStorage } from "./store/fileList";
|
||||||
|
import Test from "./pages/test";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
store.dispatch(updateOfStorage());
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Date.prototype.Format = function (fmt) {
|
||||||
|
var o = {
|
||||||
|
"M+": this.getMonth() + 1, //月份
|
||||||
|
"d+": this.getDate(), //日
|
||||||
|
"h+": this.getHours(), //小时
|
||||||
|
"m+": this.getMinutes(), //分
|
||||||
|
"s+": this.getSeconds(), //秒
|
||||||
|
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
|
||||||
|
S: this.getMilliseconds(), //毫秒
|
||||||
|
};
|
||||||
|
if (/(y+)/.test(fmt))
|
||||||
|
fmt = fmt.replace(
|
||||||
|
RegExp.$1,
|
||||||
|
(this.getFullYear() + "").substr(4 - RegExp.$1.length)
|
||||||
|
);
|
||||||
|
for (var k in o)
|
||||||
|
if (new RegExp("(" + k + ")").test(fmt))
|
||||||
|
fmt = fmt.replace(
|
||||||
|
RegExp.$1,
|
||||||
|
RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
|
||||||
|
);
|
||||||
|
return fmt;
|
||||||
|
};
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<Provider store={store}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
|
<Route path="/test" element={<Test />} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
import style from "./style.module.css";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Paper,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
} from "@mui/material";
|
||||||
|
import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
|
||||||
|
import { useSelectorFileList } from "../../store/fileList";
|
||||||
|
import fileSize from "filesize";
|
||||||
|
import { downloadFile } from "../../telegram/telegram";
|
||||||
|
|
||||||
|
enum ContentType {
|
||||||
|
File,
|
||||||
|
Share,
|
||||||
|
SafeBox,
|
||||||
|
Recycled,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Login() {
|
||||||
|
let [type, setType] = useState(ContentType.File);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100vw",
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={style.menu}>
|
||||||
|
<li onClick={() => setType(ContentType.File)}>文件</li>
|
||||||
|
<li onClick={() => setType(ContentType.Share)}>分享</li>
|
||||||
|
<li onClick={() => setType(ContentType.SafeBox)}>保险箱</li>
|
||||||
|
<li onClick={() => setType(ContentType.Recycled)}>回收站</li>
|
||||||
|
<Link to={`/`}>
|
||||||
|
<div className={style.logo}>LOGO</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className={style.content}>
|
||||||
|
{type === ContentType.File && <File />}
|
||||||
|
{type === ContentType.Share && <Share />}
|
||||||
|
{type === ContentType.SafeBox && <SafeBox />}
|
||||||
|
{type === ContentType.Recycled && <Recycled />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function File() {
|
||||||
|
let [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
let [waitDownloadFile, setWaitDownloadFile] = useState(null);
|
||||||
|
let fileList = useSelectorFileList();
|
||||||
|
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
|
let handleClick = (index: number) => {
|
||||||
|
return (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
console.log("index", index);
|
||||||
|
setWaitDownloadFile(fileList[index]);
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Download = () => {
|
||||||
|
downloadFile(waitDownloadFile)
|
||||||
|
.then((res) => {
|
||||||
|
console.log("res", res);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("download error:", error);
|
||||||
|
alert(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paper sx={{ padding: "100px", width: "100%" }}>
|
||||||
|
<TableContainer sx={{ backgroundColor: "#f2f2f2", height: 500 }}>
|
||||||
|
<Table aria-label={"文件列表"}>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ width: "70%" }}>名称</TableCell>
|
||||||
|
<TableCell>修改时间</TableCell>
|
||||||
|
<TableCell>大小</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{fileList.map((item: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<TableRow key={item.id} sx={{ cursor: "pointer" }}>
|
||||||
|
<TableCell sx={{ position: "relative" }}>
|
||||||
|
{item.attributes[0].file_name}
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
right: 20,
|
||||||
|
top: "50%",
|
||||||
|
transform: "translate(-50%,-50%)",
|
||||||
|
}}
|
||||||
|
onClick={handleClick(index)}
|
||||||
|
>
|
||||||
|
<MoreHorizIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
new Date(item.date * 1000).Format("yyyy-MM-dd hh:mm:ss")
|
||||||
|
}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{fileSize(item.size)}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Paper>
|
||||||
|
<Menu
|
||||||
|
open={open}
|
||||||
|
id="basic-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={Download}>下载</MenuItem>
|
||||||
|
<MenuItem onClick={handleClose}>分享</MenuItem>
|
||||||
|
<Divider />
|
||||||
|
<MenuItem onClick={handleClose}>重命名</MenuItem>
|
||||||
|
<MenuItem onClick={handleClose}>移动</MenuItem>
|
||||||
|
<MenuItem onClick={handleClose}>移至保险箱</MenuItem>
|
||||||
|
<MenuItem onClick={handleClose}>查看详细信息</MenuItem>
|
||||||
|
<MenuItem onClick={handleClose}>删除</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Share() {
|
||||||
|
return <div>分享</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SafeBox() {
|
||||||
|
return <div>保险箱</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Recycled() {
|
||||||
|
return <div>回收站</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
|
@ -0,0 +1,41 @@
|
||||||
|
.menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
list-style: none;
|
||||||
|
width: 350px;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu li {
|
||||||
|
width: 350px;
|
||||||
|
line-height: 100px;
|
||||||
|
text-align: center;
|
||||||
|
border-top: #949494 1px solid;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu > li:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu > li:hover {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
font-size: 48px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getMe } from "../../telegram/user";
|
||||||
|
import { HomeButton } from "../../component/homeButton";
|
||||||
|
import style from "./style.module.css";
|
||||||
|
import { Box, Drawer } from "@mui/material";
|
||||||
|
import { HomeMenu } from "../../component/homeMenu";
|
||||||
|
import { HomeSignIn } from "../../component/homeSignIn";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { useSelectorIsLoggedIn, useSelectorUser } from "../../store/user";
|
||||||
|
import { login } from "../../store/user";
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
// let [isSignIn, setIsSignIn] = useState(false);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
let isSignIn = useSelectorIsLoggedIn();
|
||||||
|
let [isOpenSignIn, setIsOpenSignIn] = useState(false);
|
||||||
|
let user = useSelectorUser();
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
function () {
|
||||||
|
getMe()
|
||||||
|
.then(function (user) {
|
||||||
|
// 已登录
|
||||||
|
dispatch(login(user));
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
if (error.code === 401) {
|
||||||
|
// Unauthorized
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.box}>
|
||||||
|
<div className={style.homeButton}>
|
||||||
|
<HomeButton
|
||||||
|
isSignIn={isSignIn}
|
||||||
|
onNotSignIn={() => {
|
||||||
|
setIsOpenSignIn(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<HomeMenu
|
||||||
|
className={style.homeMenu}
|
||||||
|
isSignIn={isSignIn}
|
||||||
|
onOpenSignIn={() => {
|
||||||
|
setIsOpenSignIn(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{user.user && <div>{user.user.username}</div>}
|
||||||
|
<Drawer
|
||||||
|
anchor={"right"}
|
||||||
|
open={isOpenSignIn && !isSignIn}
|
||||||
|
onClose={() => {
|
||||||
|
setIsOpenSignIn(false);
|
||||||
|
}}
|
||||||
|
ModalProps={{
|
||||||
|
keepMounted: true,
|
||||||
|
}}
|
||||||
|
transitionDuration={500}
|
||||||
|
>
|
||||||
|
<Box sx={{ width: "600px", height: "100%" }}>
|
||||||
|
<div className={style.homeSignIn}>
|
||||||
|
<HomeSignIn />
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
|
@ -0,0 +1,27 @@
|
||||||
|
.homeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 88px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homeSignIn {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homeMenu {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-right: 50px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #eae4de;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
export default function Test() {
|
||||||
|
const people = [
|
||||||
|
{
|
||||||
|
name: "Calvin Hawkins",
|
||||||
|
email: "calvin.hawkins@example.com",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Kristen Ramos",
|
||||||
|
email: "kristen.ramos@example.com",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ted Fox",
|
||||||
|
email: "ted.fox@example.com",
|
||||||
|
image:
|
||||||
|
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<ul className="divide-y divide-gray-200 w-screen h-screen box-border align-middle flex flex-col justify-center">
|
||||||
|
{people.map((person) => (
|
||||||
|
<li
|
||||||
|
key={person.email}
|
||||||
|
className="py-4 flex hover:bg-amber-50 hover:scale-105 hover:animate-none transition-all cursor-pointer animate-pulse select-none container pl-20"
|
||||||
|
>
|
||||||
|
<img className="h-10 w-10 rounded-full" src={person.image} alt="" />
|
||||||
|
<div className="ml-5">
|
||||||
|
<p className=" font-medium text-lg text-gray-900">{person.name}</p>
|
||||||
|
<p className="text-sm text-gray-500 slashed-zero">{person.email}</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
function getLocalStorage() {
|
||||||
|
return {
|
||||||
|
set(key: string, value: string) {
|
||||||
|
return window.localStorage.setItem(key, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(key: string) {
|
||||||
|
return window.localStorage.getItem(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
setObject(key: string, value: any) {
|
||||||
|
console.log("setObject", key, value);
|
||||||
|
return window.localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
},
|
||||||
|
|
||||||
|
getObject(key: string, defaultValue?: any) {
|
||||||
|
let value = window.localStorage.getItem(key);
|
||||||
|
if (value) {
|
||||||
|
return JSON.parse(value);
|
||||||
|
}
|
||||||
|
if (defaultValue) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getLocalStorage;
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
|
||||||
|
import userReducer from "./store/user";
|
||||||
|
import fileListReducer from "./store/fileList";
|
||||||
|
|
||||||
|
export default configureStore({
|
||||||
|
reducer: {
|
||||||
|
user: userReducer,
|
||||||
|
fileList: fileListReducer,
|
||||||
|
},
|
||||||
|
middleware: getDefaultMiddleware({
|
||||||
|
serializableCheck: false,
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import storage from "../storage";
|
||||||
|
|
||||||
|
const FileList = createSlice({
|
||||||
|
name: "fileList",
|
||||||
|
initialState: [] as any[],
|
||||||
|
reducers: {
|
||||||
|
updateOfStorage: (state) => {
|
||||||
|
state = storage().getObject("fileList", []);
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
|
||||||
|
addFileOfStorage: (state, action) => {
|
||||||
|
state.push(action.payload);
|
||||||
|
storage().setObject("fileList", state);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useSelectorFileList() {
|
||||||
|
return useSelector((state: any) => state.fileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const { updateOfStorage, addFileOfStorage } = FileList.actions;
|
||||||
|
|
||||||
|
export default FileList.reducer;
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
const User = createSlice({
|
||||||
|
name: "user",
|
||||||
|
initialState: {
|
||||||
|
isLoggedIn: false,
|
||||||
|
user: {},
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
login: (state, action) => {
|
||||||
|
state.isLoggedIn = true;
|
||||||
|
state.user = action.payload;
|
||||||
|
},
|
||||||
|
logout: (state) => {
|
||||||
|
state.isLoggedIn = false;
|
||||||
|
state.user = {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useSelectorUser() {
|
||||||
|
return useSelector((state: any) => state.user.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSelectorIsLoggedIn() {
|
||||||
|
return useSelector((state: any) => state.user.isLoggedIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const { login, logout } = User.actions;
|
||||||
|
|
||||||
|
export default User.reducer;
|
|
@ -0,0 +1,245 @@
|
||||||
|
import Client from "mtproton/envs/browser";
|
||||||
|
import { rand_id } from "../utils/rand";
|
||||||
|
import { obj } from "../utils/log";
|
||||||
|
import makeMTProto from "mtproton/src/index";
|
||||||
|
import SHA1 from "mtproton/envs/browser/sha1";
|
||||||
|
import SHA256 from "mtproton/envs/browser/sha256";
|
||||||
|
import PBKDF2 from "mtproton/envs/browser/pbkdf2";
|
||||||
|
import Transport from "mtproton/envs/browser/transport";
|
||||||
|
import getRandomBytes from "mtproton/envs/browser/get-random-bytes";
|
||||||
|
import getLocalStorage from "mtproton/envs/browser/get-local-storage";
|
||||||
|
|
||||||
|
class TelegramHelper {
|
||||||
|
private client: any;
|
||||||
|
constructor(appID: number, appHash: string, custom: boolean = false) {
|
||||||
|
if (!custom) {
|
||||||
|
this.client = new Client({
|
||||||
|
api_id: appID,
|
||||||
|
api_hash: appHash,
|
||||||
|
test: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let createTransport = function (dc: any, crypto: any) {
|
||||||
|
return new Transport(dc, crypto);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MTProto = makeMTProto({
|
||||||
|
SHA1,
|
||||||
|
SHA256,
|
||||||
|
PBKDF2,
|
||||||
|
getRandomBytes,
|
||||||
|
getLocalStorage,
|
||||||
|
createTransport,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client = new MTProto({
|
||||||
|
api_id: appID,
|
||||||
|
api_hash: appHash,
|
||||||
|
test: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// this.client.setDefaultDc(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async call(
|
||||||
|
method: string,
|
||||||
|
params?: object,
|
||||||
|
options?: object,
|
||||||
|
hideLog: boolean = false
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
!hideLog && console.dir(`${method} req\n${obj(params)}`);
|
||||||
|
options = {
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
let resp = await this.client.call(method, params, options);
|
||||||
|
!hideLog && console.dir(`${method} resp\n${obj(resp)}`);
|
||||||
|
return resp;
|
||||||
|
} catch (error) {
|
||||||
|
!hideLog && console.log(`${method} error:`, error);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const { error_code, error_message } = error;
|
||||||
|
|
||||||
|
// if (error_code === 420) {
|
||||||
|
// const seconds = Number(error_message.split('FLOOD_WAIT_')[1]);
|
||||||
|
// const ms = seconds * 1000;
|
||||||
|
//
|
||||||
|
// await sleep(ms);
|
||||||
|
//
|
||||||
|
// return this.call(method, params, options);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (error_code === 303) {
|
||||||
|
const [, dcIdAsString] = error_message.split("_MIGRATE_");
|
||||||
|
|
||||||
|
const dcId = Number(dcIdAsString);
|
||||||
|
|
||||||
|
// if (type === "PHONE") {
|
||||||
|
await this.client.setDefaultDc(dcId);
|
||||||
|
// } else {
|
||||||
|
// Object.assign(options, { dcId });
|
||||||
|
// }
|
||||||
|
|
||||||
|
return this.call(method, params, options);
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadBigFile(
|
||||||
|
bytes: ArrayBuffer,
|
||||||
|
callback?: (ratio: number) => void
|
||||||
|
): Promise<{ file_id: number; total_part: number }> {
|
||||||
|
let file_id = rand_id();
|
||||||
|
console.log("file_id", file_id);
|
||||||
|
|
||||||
|
let uploadBytes = new Uint8Array(bytes);
|
||||||
|
console.log("uploading file", bytes);
|
||||||
|
const file_total_parts = Math.ceil(uploadBytes.length / 524288);
|
||||||
|
console.log("file_total_parts", file_total_parts);
|
||||||
|
for (let i = 0; i < file_total_parts; i++) {
|
||||||
|
console.log("push part: ", i);
|
||||||
|
try {
|
||||||
|
let tempBytes = uploadBytes.slice(i * 524288, (i + 1) * 524288);
|
||||||
|
if (i === file_total_parts - 1) {
|
||||||
|
tempBytes = uploadBytes.slice(i * 524288);
|
||||||
|
}
|
||||||
|
console.log("tempBytes", tempBytes);
|
||||||
|
let finished = await Telegram.call("upload.saveBigFilePart", {
|
||||||
|
file_id: file_id,
|
||||||
|
file_part: i,
|
||||||
|
file_total_parts: file_total_parts,
|
||||||
|
bytes: tempBytes,
|
||||||
|
});
|
||||||
|
console.log("finished", finished);
|
||||||
|
if (callback) {
|
||||||
|
callback(((i + 1) / file_total_parts) * 100);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("uploaded");
|
||||||
|
return { file_id: file_id, total_part: file_total_parts };
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputFile(file_id: number, part_number: number, file_name: string) {
|
||||||
|
return {
|
||||||
|
_: "inputFileBig",
|
||||||
|
id: file_id,
|
||||||
|
parts: part_number,
|
||||||
|
name: file_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendFile(inputFile: any, type: string) {
|
||||||
|
let random_id = rand_id();
|
||||||
|
console.log("random_id", random_id);
|
||||||
|
return Telegram.call("messages.sendMedia", {
|
||||||
|
peer: {
|
||||||
|
_: "inputPeerSelf",
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
_: "inputMediaUploadedDocument",
|
||||||
|
file: inputFile,
|
||||||
|
mime_type: type,
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
_: "documentAttributeFilename",
|
||||||
|
file_name: inputFile.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
random_id: rand_id(),
|
||||||
|
message: "test",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadFile(fileDocument: any) {
|
||||||
|
let partSize = 1024 * 1024;
|
||||||
|
const file_total_parts = Math.ceil(fileDocument.size / partSize);
|
||||||
|
|
||||||
|
let results = [];
|
||||||
|
for (let i = 0; i < file_total_parts; i++) {
|
||||||
|
try {
|
||||||
|
let result = await Telegram.call("upload.getFile", {
|
||||||
|
location: {
|
||||||
|
_: "inputDocumentFileLocation",
|
||||||
|
id: fileDocument.id,
|
||||||
|
access_hash: fileDocument.access_hash,
|
||||||
|
file_reference: fileDocument.file_reference,
|
||||||
|
thumb_size: "",
|
||||||
|
},
|
||||||
|
limit: partSize,
|
||||||
|
offset: partSize * i,
|
||||||
|
});
|
||||||
|
console.log("result", result);
|
||||||
|
results.push(result);
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("messages.getDocument:", results);
|
||||||
|
let bytes = new Uint8Array(fileDocument.size);
|
||||||
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
for (let j = 0; j < results[i].bytes.length; j++) {
|
||||||
|
bytes[i * partSize + j] = results[i].bytes[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let downloadBytes = bytes;
|
||||||
|
console.log("downloadBytes", downloadBytes);
|
||||||
|
let blob = new Blob([downloadBytes], {
|
||||||
|
type: fileDocument.mime_type,
|
||||||
|
});
|
||||||
|
let url = window.URL.createObjectURL(blob);
|
||||||
|
let a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileDocument.file_name;
|
||||||
|
a.click();
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendSignInCode(phone: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
let result = await Telegram.call("auth.sendCode", {
|
||||||
|
phone_number: phone,
|
||||||
|
settings: {
|
||||||
|
_: "codeSettings",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return Promise.resolve(result.phone_code_hash);
|
||||||
|
} catch (error: any) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function signIn(phone: string, code: string, phone_code_hash: string) {
|
||||||
|
try {
|
||||||
|
let result = await Telegram.call("auth.signIn", {
|
||||||
|
phone_number: phone,
|
||||||
|
phone_code_hash: phone_code_hash,
|
||||||
|
phone_code: code,
|
||||||
|
});
|
||||||
|
return Promise.resolve(result);
|
||||||
|
} catch (error: any) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const appID = 18987971;
|
||||||
|
const appHash = "fcfd9e6ed3f9e48a360bb57cc0d59d98";
|
||||||
|
let Telegram = new TelegramHelper(appID, appHash);
|
||||||
|
|
||||||
|
export default Telegram;
|
||||||
|
export {
|
||||||
|
uploadBigFile,
|
||||||
|
inputFile,
|
||||||
|
sendFile,
|
||||||
|
downloadFile,
|
||||||
|
sendSignInCode,
|
||||||
|
signIn,
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Telegram from "./telegram";
|
||||||
|
|
||||||
|
async function getMe() {
|
||||||
|
return await Telegram.call("users.getFullUser", {
|
||||||
|
id: { _: "inputUserSelf" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getMe };
|
|
@ -0,0 +1,29 @@
|
||||||
|
export interface Status {
|
||||||
|
_: string;
|
||||||
|
was_online: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
_: string;
|
||||||
|
flags: number;
|
||||||
|
self: boolean;
|
||||||
|
contact: boolean;
|
||||||
|
mutual_contact: boolean;
|
||||||
|
deleted: boolean;
|
||||||
|
bot: boolean;
|
||||||
|
bot_chat_history: boolean;
|
||||||
|
bot_nochats: boolean;
|
||||||
|
verified: boolean;
|
||||||
|
restricted: boolean;
|
||||||
|
min: boolean;
|
||||||
|
bot_inline_geo: boolean;
|
||||||
|
support: boolean;
|
||||||
|
scam: boolean;
|
||||||
|
apply_min_photo: boolean;
|
||||||
|
fake: boolean;
|
||||||
|
id: string;
|
||||||
|
access_hash: string;
|
||||||
|
first_name: string;
|
||||||
|
phone: string;
|
||||||
|
status: Status;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
function ArraybufferToStr(buffer: ArrayBuffer) {
|
||||||
|
let uint8 = new Uint8Array(buffer);
|
||||||
|
|
||||||
|
let decoder = new TextDecoder("utf8");
|
||||||
|
return decoder.decode(uint8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function ab2str(buf: ArrayBuffer) {
|
||||||
|
// return String.fromCharCode.apply(null, buf as Uint16Array);
|
||||||
|
// }
|
||||||
|
// function str2ab(str: string) {
|
||||||
|
// let buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
|
||||||
|
// let bufView = new Uint16Array(buf);
|
||||||
|
// for (let i = 0, strLen = str.length; i < strLen; i++) {
|
||||||
|
// bufView[i] = str.charCodeAt(i);
|
||||||
|
// }
|
||||||
|
// return buf;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
export default ArraybufferToStr;
|
|
@ -0,0 +1,12 @@
|
||||||
|
const maxToLog = 10240;
|
||||||
|
|
||||||
|
export function obj(obj: any): string {
|
||||||
|
if (!obj) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
let s = JSON.stringify(obj, null, 2);
|
||||||
|
if (s.length > maxToLog) {
|
||||||
|
s = s.substr(0, maxToLog) + "...";
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
function between(min: number, max: number): number {
|
||||||
|
return Math.floor(Math.random() * (max - min) + min);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rand_id(): number {
|
||||||
|
return between(100000, 999999);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { between, rand_id };
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
|
||||||
|
darkMode: false, // or 'media' or 'class'
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
variants: {},
|
||||||
|
plugins: [],
|
||||||
|
};
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
|
|
Loading…
Reference in New Issue