├── .github
└── workflows
│ └── docker-build.yaml
├── .gitignore
├── Dockerfile
├── README.md
├── config.json.example
├── dumpyarabot
├── __init__.py
├── __main__.py
├── config.py
├── handlers.py
├── schemas.py
└── utils.py
├── extract_and_push.sh
├── mypy.ini
├── pyproject.toml
├── scripts
├── format.sh
└── lint.sh
├── uv.lock
└── whitelist.txt
/.github/workflows/docker-build.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - master
5 |
6 | name: Build and push docker image to container registry
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Build image.
13 | run: docker build -t ghcr.io/androiddumps/dumpbot .
14 | - name: Login to container registry
15 | run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io --username androiddumps --password-stdin
16 | - name: Push image
17 | run: docker push ghcr.io/androiddumps/dumpbot
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *log*
2 | *tmp*
3 | *test*
4 | *__pycache__*
5 | .idea/
6 | venv/
7 | *.session*
8 | .env
9 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:focal
2 | ENV DEBIAN_FRONTEND=noninteractive
3 | RUN apt update -y && apt install -y curl jq wget axel aria2 unace unrar zip unzip p7zip-full p7zip-rar sharutils rar uudeview mpack arj cabextract rename liblzma-dev brotli lz4 python-is-python3 python3 python3-dev python3-pip git gawk sudo cpio
4 | RUN python3 -m pip install backports.lzma protobuf pycrypto aospdtgen extract-dtb dumpyara gdown git+https://github.com/Juvenal-Yescas/mediafire-dl
5 | COPY extract_and_push.sh /usr/local/bin/extract_and_push
6 | WORKDIR /dumpyara
7 | ENTRYPOINT extract_and_push
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dumpbot
2 |
3 | ## Link
4 | https://t.me/dumpyarabot
5 |
6 | ## Commands
7 |
8 | ### `/dump [URL] [options]`
9 |
10 | Initiate a new firmware dump process.
11 |
12 | #### Parameters:
13 | - `URL`: The URL of the firmware to be dumped (required)
14 |
15 | #### Options:
16 | - `a`: Use alternative dumper
17 | - `f`: Force a new dump even if an existing one is found
18 | - `b`: Add the dump to the blacklist
19 |
20 | #### Usage Examples:
21 | - Basic usage: `/dump https://example.com/firmware.zip`
22 | - Use alternative dumper: `/dump https://example.com/firmware.zip a`
23 | - Force new dump: `/dump https://example.com/firmware.zip f`
24 | - Add to blacklist: `/dump https://example.com/firmware.zip b`
25 | - Combine options: `/dump https://example.com/firmware.zip afb`
26 |
27 | ### `/cancel [job_id]`
28 |
29 | Cancel an ongoing dump process.
30 |
31 | #### Parameters:
32 | - `job_id`: The ID of the job to be cancelled (required)
33 |
34 | #### Usage Example:
35 | - `/cancel 123`
36 |
--------------------------------------------------------------------------------
/config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "bot_token": "",
3 | "allowed_users": [
4 | 271096459,
5 | 456642569,
6 | 591929714,
7 | 432729528,
8 | 293861388,
9 | 388628872,
10 | 92027269,
11 | 370300617,
12 | 234006496,
13 | 690734297,
14 | 406160519,
15 | 555923570,
16 | 410295342,
17 | 271913103,
18 | 273713314,
19 | 158598724,
20 | 252376788,
21 | 470460877,
22 | 524963551,
23 | 23663027,
24 | 174189495,
25 | 444730308,
26 | 152405066,
27 | 480991272,
28 | 1040450749,
29 | 491180611,
30 | 492511185,
31 | 445041211
32 | ],
33 | "allowed_chats": [
34 | -1001412293127
35 | ]
36 | }
--------------------------------------------------------------------------------
/dumpyarabot/__init__.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from rich.logging import RichHandler
4 |
5 | FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
6 | logging.basicConfig(level=logging.INFO, format=FORMAT, handlers=[RichHandler()])
7 | logging.getLogger("httpx").setLevel(logging.WARNING)
8 |
9 | logger = logging.getLogger("rich")
10 |
--------------------------------------------------------------------------------
/dumpyarabot/__main__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | from telegram.ext import ApplicationBuilder, CommandHandler
5 |
6 | from dumpyarabot.handlers import cancel_dump, dump
7 |
8 | from .config import settings
9 |
10 | if __name__ == "__main__":
11 | application = ApplicationBuilder().token(settings.TELEGRAM_BOT_TOKEN).build()
12 | application.bot_data["restart"] = False
13 |
14 | dump_handler = CommandHandler("dump", dump)
15 | cancel_dump_handler = CommandHandler("cancel", cancel_dump)
16 | # TODO: Fix the restart handler implementation
17 | # restart_handler = CommandHandler("restart", restart)
18 | application.add_handler(dump_handler)
19 | application.add_handler(cancel_dump_handler)
20 | # application.add_handler(restart_handler)
21 |
22 | application.run_polling()
23 |
24 | if application.bot_data["restart"]:
25 | os.execl(sys.executable, sys.executable, *sys.argv)
26 |
--------------------------------------------------------------------------------
/dumpyarabot/config.py:
--------------------------------------------------------------------------------
1 | from pydantic import AnyHttpUrl
2 | from pydantic_settings import BaseSettings, SettingsConfigDict
3 |
4 |
5 | class Settings(BaseSettings):
6 | TELEGRAM_BOT_TOKEN: str
7 |
8 | JENKINS_URL: AnyHttpUrl
9 | JENKINS_USER_NAME: str
10 | JENKINS_USER_TOKEN: str
11 |
12 | SUDO_USERS: list[int] = []
13 |
14 | ALLOWED_CHATS: list[int]
15 | model_config = SettingsConfigDict(env_file=".env")
16 |
17 |
18 | settings = Settings()
19 |
--------------------------------------------------------------------------------
/dumpyarabot/handlers.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from pydantic import ValidationError
4 | from rich.console import Console
5 | from telegram import Chat, Message, Update
6 | from telegram.ext import ContextTypes
7 |
8 | from dumpyarabot import schemas, utils
9 | from dumpyarabot.config import settings
10 |
11 | console = Console()
12 |
13 |
14 | async def dump(
15 | update: Update,
16 | context: ContextTypes.DEFAULT_TYPE,
17 | ) -> None:
18 | """Handler for the /dump command."""
19 | chat: Optional[Chat] = update.effective_chat
20 | message: Optional[Message] = update.effective_message
21 |
22 | if not chat or not message:
23 | console.print("[red]Chat or message object is None[/red]")
24 | return
25 |
26 | # Ensure it can only be used in the correct group
27 | if chat.id not in settings.ALLOWED_CHATS:
28 | console.print(
29 | f"[yellow]Unauthorized chat attempt from chat_id: {chat.id}[/yellow]"
30 | )
31 | await context.bot.send_message(
32 | chat_id=chat.id,
33 | reply_to_message_id=message.message_id,
34 | text="You can't use this here",
35 | )
36 | return
37 |
38 | # Ensure that we had some arguments passed
39 | if not context.args:
40 | console.print("[yellow]No arguments provided for dump command[/yellow]")
41 | usage = "Usage: `/dump [URL] [a|f|b|p]`\nURL: required, a: alt dumper, f: force, b: blacklist, p: use privdump"
42 | await context.bot.send_message(
43 | chat_id=chat.id,
44 | reply_to_message_id=message.message_id,
45 | text=usage,
46 | parse_mode="Markdown",
47 | )
48 | return
49 |
50 | url = context.args[0]
51 | options = "".join("".join(context.args[1:]).split())
52 |
53 | use_alt_dumper = "a" in options
54 | force = "f" in options
55 | add_blacklist = "b" in options
56 | use_privdump = "p" in options
57 |
58 | console.print("[green]Dump request:[/green]")
59 | console.print(f" URL: {url}")
60 | console.print(f" Alt dumper: {use_alt_dumper}")
61 | console.print(f" Force: {force}")
62 | console.print(f" Blacklist: {add_blacklist}")
63 | console.print(f" Privdump: {use_privdump}")
64 |
65 | # Delete the user's message immediately if privdump is used
66 | if use_privdump:
67 | console.print(
68 | f"[blue]Privdump requested - deleting message {message.message_id}[/blue]"
69 | )
70 | try:
71 | await context.bot.delete_message(
72 | chat_id=chat.id, message_id=message.message_id
73 | )
74 | console.print(
75 | "[green]Successfully deleted original message for privdump[/green]"
76 | )
77 | except Exception as e:
78 | console.print(f"[red]Failed to delete message for privdump: {e}[/red]")
79 |
80 | # Try to check for existing build and call jenkins if necessary
81 | try:
82 | dump_args = schemas.DumpArguments(
83 | url=url,
84 | use_alt_dumper=use_alt_dumper,
85 | add_blacklist=add_blacklist,
86 | use_privdump=use_privdump,
87 | )
88 |
89 | if not force:
90 | console.print("[blue]Checking for existing builds...[/blue]")
91 | initial_message = await context.bot.send_message(
92 | chat_id=chat.id,
93 | reply_to_message_id=None if use_privdump else message.message_id,
94 | text="Checking for existing builds...",
95 | )
96 |
97 | exists, status_message = await utils.check_existing_build(dump_args)
98 | if exists:
99 | console.print(
100 | f"[yellow]Found existing build: {status_message}[/yellow]"
101 | )
102 | await context.bot.edit_message_text(
103 | chat_id=chat.id,
104 | message_id=initial_message.message_id,
105 | text=status_message,
106 | )
107 | return
108 |
109 | await context.bot.delete_message(
110 | chat_id=chat.id,
111 | message_id=initial_message.message_id,
112 | )
113 |
114 | if not use_privdump:
115 | dump_args.initial_message_id = message.message_id
116 |
117 | console.print("[blue]Calling Jenkins to start build...[/blue]")
118 | response_text = await utils.call_jenkins(dump_args)
119 | console.print(f"[green]Jenkins response: {response_text}[/green]")
120 |
121 | except ValidationError:
122 | console.print(f"[red]Invalid URL provided: {url}[/red]")
123 | response_text = "Invalid URL"
124 |
125 | except Exception:
126 | console.print("[red]Unexpected error occurred:[/red]")
127 | console.print_exception()
128 | response_text = "An error occurred"
129 |
130 | # Reply to the user with whatever the status is
131 | await context.bot.send_message(
132 | chat_id=chat.id,
133 | reply_to_message_id=None if use_privdump else message.message_id,
134 | text=response_text,
135 | )
136 |
137 |
138 | async def cancel_dump(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
139 | """Handler for the /cancel command."""
140 | chat: Optional[Chat] = update.effective_chat
141 | message: Optional[Message] = update.effective_message
142 | user = update.effective_user
143 |
144 | if not chat or not message or not user:
145 | console.print("[red]Chat, message or user object is None[/red]")
146 | return
147 |
148 | # Ensure it can only be used in the correct group
149 | if chat.id not in settings.ALLOWED_CHATS:
150 | console.print(
151 | f"[yellow]Unauthorized chat attempt for cancel from chat_id: {chat.id}[/yellow]"
152 | )
153 | await context.bot.send_message(
154 | chat_id=chat.id,
155 | reply_to_message_id=message.message_id,
156 | text="You can't use this here",
157 | )
158 | return
159 |
160 | # Check if the user is an admin
161 | admins = await chat.get_administrators()
162 | if user not in [admin.user for admin in admins]:
163 | console.print(
164 | f"[yellow]Non-admin user {user.id} tried to use cancel command[/yellow]"
165 | )
166 | await context.bot.send_message(
167 | chat_id=chat.id,
168 | reply_to_message_id=message.message_id,
169 | text="You don't have permission to use this command",
170 | )
171 | return
172 |
173 | # Ensure that we had some arguments passed
174 | if not context.args:
175 | console.print("[yellow]No job_id provided for cancel command[/yellow]")
176 | usage = (
177 | "Usage: `/cancel [job_id] [p]`\njob_id: required, p: cancel privdump job"
178 | )
179 | await context.bot.send_message(
180 | chat_id=chat.id,
181 | reply_to_message_id=message.message_id,
182 | text=usage,
183 | parse_mode="Markdown",
184 | )
185 | return
186 |
187 | job_id = context.args[0]
188 | use_privdump = "p" in context.args[1:] if len(context.args) > 1 else False
189 |
190 | console.print("[blue]Cancel request:[/blue]")
191 | console.print(f" Job ID: {job_id}")
192 | console.print(f" Privdump: {use_privdump}")
193 | console.print(f" Requested by: {user.username} (ID: {user.id})")
194 |
195 | try:
196 | response_message = await utils.cancel_jenkins_job(job_id, use_privdump)
197 | console.print(
198 | f"[green]Successfully processed cancel request: {response_message}[/green]"
199 | )
200 | except Exception as e:
201 | console.print("[red]Error processing cancel request:[/red]")
202 | console.print_exception()
203 | response_message = f"Error cancelling job: {str(e)}"
204 |
205 | await context.bot.send_message(
206 | chat_id=chat.id,
207 | reply_to_message_id=message.message_id,
208 | text=response_message,
209 | )
210 |
211 |
212 | async def restart(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
213 | chat: Optional[Chat] = update.effective_chat
214 | message: Optional[Message] = update.effective_message
215 | user = update.effective_user
216 |
217 | if not chat or not message or not user:
218 | return
219 |
220 | # Ensure it can only be used in the correct group
221 | if chat.id not in settings.ALLOWED_CHATS:
222 | await context.bot.send_message(
223 | chat_id=chat.id,
224 | reply_to_message_id=message.message_id,
225 | text="You can't use this here",
226 | )
227 | return
228 |
229 | # Check if the user is an admin
230 | admins = await chat.get_administrators()
231 | if user not in [admin.user for admin in admins]:
232 | await context.bot.send_message(
233 | chat_id=chat.id,
234 | reply_to_message_id=message.message_id,
235 | text="You don't have permission to use this command",
236 | )
237 | return
238 |
239 | context.application.stop_running()
240 | context.bot_data["restart"] = True
241 |
--------------------------------------------------------------------------------
/dumpyarabot/schemas.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List, Optional
2 |
3 | from pydantic import AnyHttpUrl, BaseModel
4 |
5 |
6 | class JenkinsBuild(BaseModel):
7 | number: int
8 | result: Optional[str]
9 | actions: List[Dict]
10 |
11 |
12 | class DumpArguments(BaseModel):
13 | url: AnyHttpUrl
14 | use_alt_dumper: bool
15 | add_blacklist: bool
16 | use_privdump: bool
17 | initial_message_id: Optional[int] = None
18 |
--------------------------------------------------------------------------------
/dumpyarabot/utils.py:
--------------------------------------------------------------------------------
1 | from typing import List, Tuple
2 |
3 | import httpx
4 | from rich.console import Console
5 |
6 | from dumpyarabot import schemas
7 | from dumpyarabot.config import settings
8 |
9 | console = Console()
10 |
11 |
12 | async def get_jenkins_builds(job_name: str) -> List[schemas.JenkinsBuild]:
13 | """Fetch all builds from Jenkins for a specific job."""
14 | console.print(f"[blue]Fetching builds for job: {job_name}[/blue]")
15 | async with httpx.AsyncClient() as client:
16 | try:
17 | response = await client.get(
18 | f"{settings.JENKINS_URL}/job/{job_name}/api/json",
19 | params={
20 | "tree": "allBuilds[number,result,actions[parameters[name,value]]]"
21 | },
22 | auth=(settings.JENKINS_USER_NAME, settings.JENKINS_USER_TOKEN),
23 | timeout=30.0,
24 | )
25 | response.raise_for_status()
26 | builds = [
27 | schemas.JenkinsBuild(**build) for build in response.json()["allBuilds"]
28 | ]
29 | console.print(
30 | f"[green]Successfully fetched {len(builds)} builds for {job_name}[/green]"
31 | )
32 | return builds
33 | except Exception as e:
34 | console.print(f"[red]Failed to fetch builds for {job_name}: {e}[/red]")
35 | raise
36 |
37 |
38 | def _is_matching_build(
39 | build: schemas.JenkinsBuild, args: schemas.DumpArguments
40 | ) -> bool:
41 | """Check if a build matches the given arguments."""
42 | for action in build.actions:
43 | if "parameters" in action:
44 | params = {param["name"]: param["value"] for param in action["parameters"]}
45 | if matches := (
46 | params.get("URL") == args.url.unicode_string()
47 | and params.get("USE_ALT_DUMPER") == args.use_alt_dumper
48 | and params.get("ADD_BLACKLIST") == args.add_blacklist
49 | ):
50 | console.print("[green]Found matching build parameters[/green]")
51 | return matches
52 | return False
53 |
54 |
55 | def _get_build_status(build: schemas.JenkinsBuild) -> Tuple[bool, str]:
56 | """Get the status of a build."""
57 | console.print(f"[blue]Checking build status: #{build.number}[/blue]")
58 | if build.result is None:
59 | console.print("[yellow]Build is currently in progress[/yellow]")
60 | return (
61 | True,
62 | f"Build #{build.number} is currently in progress for this URL and settings.",
63 | )
64 | elif build.result == "SUCCESS":
65 | console.print("[green]Build completed successfully[/green]")
66 | return (
67 | True,
68 | f"Build #{build.number} has already successfully completed for this URL and settings.",
69 | )
70 | else:
71 | console.print(
72 | f"[yellow]Build result was {build.result}, will start new build[/yellow]"
73 | )
74 | return (
75 | False,
76 | f"Build #{build.number} exists for this URL and settings, but result was {build.result}. A new build will be started.",
77 | )
78 |
79 |
80 | async def check_existing_build(args: schemas.DumpArguments) -> Tuple[bool, str]:
81 | """Check if a build with the given parameters already exists."""
82 | job_name = "privdump" if args.use_privdump else "dumpyara"
83 | console.print(f"[blue]Checking existing builds for {job_name}[/blue]")
84 | console.print("Build parameters:", args)
85 |
86 | builds = await get_jenkins_builds(job_name)
87 |
88 | for build in builds:
89 | if _is_matching_build(build, args):
90 | status = _get_build_status(build)
91 | console.print(f"[yellow]Found matching build - Status: {status}[/yellow]")
92 | return status
93 |
94 | console.print(f"[green]No matching build found for {job_name}[/green]")
95 | return False, f"No matching build found. A new {job_name} build will be started."
96 |
97 |
98 | async def call_jenkins(args: schemas.DumpArguments) -> str:
99 | """Call Jenkins to start a new build."""
100 | job_name = "privdump" if args.use_privdump else "dumpyara"
101 | console.print(f"[blue]Starting new {job_name} build[/blue]")
102 | console.print("Build parameters:", args)
103 |
104 | async with httpx.AsyncClient() as client:
105 | try:
106 | response = await client.post(
107 | f"{settings.JENKINS_URL}/job/{job_name}/buildWithParameters",
108 | params={
109 | "URL": args.url.unicode_string(),
110 | "USE_ALT_DUMPER": args.use_alt_dumper,
111 | "ADD_BLACKLIST": args.add_blacklist,
112 | "INITIAL_MESSAGE_ID": args.initial_message_id,
113 | },
114 | auth=(settings.JENKINS_USER_NAME, settings.JENKINS_USER_TOKEN),
115 | )
116 | response.raise_for_status()
117 | console.print(f"[green]Successfully triggered {job_name} build[/green]")
118 | return f"{job_name.capitalize()} job triggered"
119 | except Exception as e:
120 | console.print(f"[red]Failed to trigger {job_name} build: {e}[/red]")
121 | raise
122 |
123 |
124 | async def cancel_jenkins_job(job_id: str, use_privdump: bool = False) -> str:
125 | """Cancel a Jenkins job."""
126 | job_name = "privdump" if use_privdump else "dumpyara"
127 | console.print(f"[blue]Attempting to cancel {job_name} job {job_id}[/blue]")
128 |
129 | async with httpx.AsyncClient() as client:
130 | try:
131 | response = await client.post(
132 | f"{settings.JENKINS_URL}/job/{job_name}/{job_id}/stop",
133 | auth=(settings.JENKINS_USER_NAME, settings.JENKINS_USER_TOKEN),
134 | follow_redirects=True,
135 | )
136 | if response.status_code == 200:
137 | console.print(
138 | f"[green]Successfully cancelled {job_name} job {job_id}[/green]"
139 | )
140 | return f"Job with ID {job_id} has been cancelled in {job_name}."
141 | elif response.status_code == 404:
142 | console.print(
143 | f"[yellow]Job {job_id} not found, checking queue[/yellow]"
144 | )
145 | response = await client.post(
146 | f"{settings.JENKINS_URL}/queue/cancelItem",
147 | params={"id": job_id},
148 | auth=(settings.JENKINS_USER_NAME, settings.JENKINS_USER_TOKEN),
149 | follow_redirects=True,
150 | )
151 | if response.status_code == 204:
152 | console.print(
153 | f"[green]Successfully removed {job_name} job {job_id} from queue[/green]"
154 | )
155 | return f"Job with ID {job_id} has been removed from the {job_name} queue."
156 |
157 | console.print(f"[yellow]Failed to cancel {job_name} job {job_id}[/yellow]")
158 | return f"Failed to cancel job with ID {job_id} in {job_name}. Job not found or already completed."
159 | except Exception as e:
160 | console.print(
161 | f"[red]Error while cancelling {job_name} job {job_id}: {e}[/red]"
162 | )
163 | raise
164 |
--------------------------------------------------------------------------------
/extract_and_push.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | [[ -z ${API_KEY} ]] && echo "API_KEY not defined, exiting!" && exit 1
4 | [[ -z ${GITLAB_SERVER} ]] && GITLAB_SERVER="dumps.tadiphone.dev"
5 | [[ -z ${PUSH_HOST} ]] && PUSH_HOST="dumps"
6 | [[ -z $ORG ]] && ORG="dumps"
7 |
8 | CHAT_ID="-1001412293127"
9 |
10 | [[ -z ${INITIAL_MESSAGE_ID} ]] && START_MESSAGE_ID="" || START_MESSAGE_ID="${INITIAL_MESSAGE_ID}"
11 |
12 | # usage: normal - sendTg normal "message to send"
13 | # reply - sendTg reply message_id "reply to send"
14 | # edit - sendTg edit message_id "new message" ( new message must be different )
15 | # Uses global var API_KEY
16 | sendTG() {
17 | local mode="${1:?Error: Missing mode}" && shift
18 | local api_url="https://api.telegram.org/bot${API_KEY:?}"
19 | if [[ ${mode} =~ normal ]]; then
20 | curl --compressed -s "${api_url}/sendmessage" --data "text=$(urlEncode "${*:?Error: Missing message text.}")&chat_id=${CHAT_ID:?}&parse_mode=HTML&disable_web_page_preview=True"
21 | elif [[ ${mode} =~ reply ]]; then
22 | local message_id="${1:?Error: Missing message id for reply.}" && shift
23 | curl --compressed -s "${api_url}/sendmessage" --data "text=$(urlEncode "${*:?Error: Missing message text.}")&chat_id=${CHAT_ID:?}&parse_mode=HTML&reply_to_message_id=${message_id}&disable_web_page_preview=True"
24 | elif [[ ${mode} =~ edit ]]; then
25 | local message_id="${1:?Error: Missing message id for edit.}" && shift
26 | curl --compressed -s "${api_url}/editMessageText" --data "text=$(urlEncode "${*:?Error: Missing message text.}")&chat_id=${CHAT_ID:?}&parse_mode=HTML&message_id=${message_id}&disable_web_page_preview=True"
27 | fi
28 | }
29 |
30 | # usage: temporary - To just edit the last message sent but the new content will be overwritten when this function is used again
31 | # sendTG_edit_wrapper temporary "${MESSAGE_ID}" new message
32 | # permanent - To edit the last message sent but also store it permanently, new content will be appended when this function is used again
33 | # sendTG_edit_wrapper permanent "${MESSAGE_ID}" new message
34 | # Uses global var MESSAGE for all message contents
35 | sendTG_edit_wrapper() {
36 | local mode="${1:?Error: Missing mode}" && shift
37 | local message_id="${1:?Error: Missing message id variable}" && shift
38 | case "${mode}" in
39 | temporary) sendTG edit "${message_id}" "${*:?}" > /dev/null ;;
40 | permanent)
41 | MESSAGE="${*:?}"
42 | sendTG edit "${message_id}" "${MESSAGE}" > /dev/null
43 | ;;
44 | esac
45 | }
46 |
47 |
48 | # Inform the user about final status of build
49 | terminate() {
50 | case ${1:?} in
51 | ## Success
52 | 0)
53 | local string="done (#${BUILD_ID})"
54 | ;;
55 | ## Failure
56 | 1)
57 | local string="failed! (#${BUILD_ID})
58 | View console logs for more."
59 | ;;
60 | ## Aborted
61 | 2)
62 | local string="aborted! (#${BUILD_ID})
63 | Branch already exists on GitLab ($branch
)."
64 | ;;
65 | esac
66 |
67 | ## Template
68 | sendTG reply "${START_MESSAGE_ID}" "Job ${string}"
69 | exit "${1:?}"
70 | }
71 |
72 | # https://github.com/dylanaraps/pure-bash-bible#percent-encode-a-string
73 | urlEncode() {
74 | declare LC_ALL=C
75 | for ((i = 0; i < ${#1}; i++)); do
76 | : "${1:i:1}"
77 | case "${_}" in
78 | [a-zA-Z0-9.~_-])
79 | printf '%s' "${_}"
80 | ;;
81 | *)
82 | printf '%%%02X' "'${_}"
83 | ;;
84 | esac
85 | done 2>| /dev/null
86 | printf '\n'
87 | }
88 |
89 | curl --compressed --fail-with-body --silent --location "https://$GITLAB_SERVER" > /dev/null || {
90 | if _json="$(sendTG normal "Can't access $GITLAB_SERVER, cancelling job!")"; then
91 | CURL_MSG_ID="$(jq ".result.message_id" <<< "${_json}")"
92 | sendTG reply "${CURL_MSG_ID}" "Job failed!"
93 | fi
94 | exit 1
95 | }
96 |
97 | # Check if link is in whitelist.
98 | mapfile -t LIST < "${HOME}/dumpbot/whitelist.txt"
99 |
100 | ## Set 'WHITELISTED' to true if download link (sub-)domain is present
101 | for WHITELISTED_LINKS in "${LIST[@]}"; do
102 | if [[ "${URL}" == *"${WHITELISTED_LINKS}"* ]]; then
103 | WHITELISTED=true
104 | break
105 | else
106 | WHITELISTED=false
107 | fi
108 | done
109 |
110 | ## Print if link will be published, or not.
111 | if [ "${ADD_BLACKLIST}" == true ] || [ "${WHITELISTED}" == false ]; then
112 | echo "[INFO] Download link will not be published on channel."
113 | elif [ "${ADD_BLACKLIST}" == false ] && [ "${WHITELISTED}" == true ]; then
114 | echo "[INFO] Download link will be published on channel."
115 | fi
116 |
117 | if [[ -f $URL ]]; then
118 | cp -v "$URL" .
119 | MESSAGE="Found file locally.
"
120 | if _json="$(sendTG normal "${MESSAGE}")"; then
121 | # Store both message IDs
122 | MESSAGE_ID="$(jq ".result.message_id" <<< "${_json}")"
123 | START_MESSAGE_ID="${MESSAGE_ID}"
124 | else
125 | # disable sendTG and sendTG_edit_wrapper if wasn't able to send initial message
126 | sendTG() { :; } && sendTG_edit_wrapper() { :; }
127 | fi
128 | else
129 | if [[ "$JOB_NAME" == *"privdump"* ]]; then
130 | MESSAGE="Started private dump on
jenkins"
131 | else
132 | MESSAGE="Started
dump on
jenkins"
133 | fi
134 | MESSAGE+=$'\n'"Job ID: $BUILD_ID
."
135 | if _json="$(sendTG reply "${INITIAL_MESSAGE_ID}" "${MESSAGE}")"; then
136 | # Store both message IDs
137 | MESSAGE_ID="$(jq ".result.message_id" <<< "${_json}")"
138 | START_MESSAGE_ID="${MESSAGE_ID}"
139 | else
140 | # disable sendTG and sendTG_edit_wrapper if wasn't able to send initial message
141 | sendTG() { :; } && sendTG_edit_wrapper() { :; }
142 | fi
143 |
144 | # Override '${URL}' with best possible mirror of it
145 | case "${URL}" in
146 | # For Xiaomi: replace '${URL}' with (one of) the fastest mirror
147 | *"d.miui.com"*)
148 | # Do not run this loop in case we're already using one of the reccomended mirrors
149 | if ! echo "${URL}" | rg -q 'cdnorg|bkt-sgp-miui-ota-update-alisgp'; then
150 | # Set '${URL_ORIGINAL}' and '${FILE_PATH}' in case we might need to roll back
151 | URL_ORIGINAL=$(echo "${URL}" | sed -E 's|(https://[^/]+).*|\1|')
152 | FILE_PATH=$(echo ${URL#*d.miui.com/} | sed 's/?.*//')
153 |
154 | # Array of different possible mirrors
155 | MIRRORS=(
156 | "https://cdnorg.d.miui.com"
157 | "https://bkt-sgp-miui-ota-update-alisgp.oss-ap-southeast-1.aliyuncs.com"
158 | "https://bn.d.miui.com"
159 | "${URL_ORIGINAL}"
160 | )
161 |
162 | # Check back and forth for the best available mirror
163 | for URLS in "${MIRRORS[@]}"; do
164 | # Change mirror's domain with one(s) from array
165 | URL=${URLS}/${FILE_PATH}
166 |
167 | # Be sure that the mirror is available. Once found, break the loop
168 | if [ "$(curl -I -sS "${URL}" | head -n1 | cut -d' ' -f2)" == "404" ]; then
169 | echo "[ERROR] ${URLS} is not available. Trying with other mirror(s)..."
170 | else
171 | echo "[INFO] Found best available mirror."
172 | break
173 | fi
174 | done
175 | fi
176 | ;;
177 | # For Pixeldrain: replace the link with a direct one
178 | *"pixeldrain.com/u"*)
179 | echo "[INFO] Replacing with best available mirror."
180 | URL="https://pd.cybar.xyz/${URL##*/}"
181 | ;;
182 | *"pixeldrain.com/d"*)
183 | echo "[INFO] Replacing with direct download link."
184 | URL="https://pixeldrain.com/api/filesystem/${URL##*/}"
185 | ;;
186 | esac
187 |
188 | # Confirm download has started
189 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Downloading the file...
" > /dev/null
190 | echo "[INFO] Started downloading... ($(date +%R:%S))"
191 |
192 | # downloadError: Kill the script in case downloading failed
193 | downloadError() {
194 | echo "Download failed. Exiting."
195 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Failed to download the file.
" > /dev/null
196 | terminate 1
197 | }
198 |
199 | # Properly check for different hosting websties.
200 | case ${URL} in
201 | *drive.google.com*)
202 | uvx gdown@5.2.0 -q "${URL}" --fuzzy > /dev/null || downloadError
203 | ;;
204 | *mediafire.com*)
205 | uvx --from git+https://github.com/Juvenal-Yescas/mediafire-dl@master mediafire-dl "${URL}" > /dev/null || downloadError
206 | ;;
207 | *mega.nz*)
208 | megatools dl "${URL}" > /dev/null || downloadError
209 | ;;
210 | *)
211 | aria2c -q -s16 -x16 --check-certificate=false "${URL}" || {
212 | rm -fv ./*
213 | wget -q --no-check-certificate "${URL}" || downloadError
214 | }
215 | ;;
216 | esac
217 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Downloaded the file.
" > /dev/null
218 | echo "[INFO] Finished downloading the file. ($(date +%R:%S))"
219 | fi
220 |
221 | # Clean query strings if any from URL
222 | oldifs=$IFS
223 | IFS="?"
224 | read -ra CLEANED <<< "${URL}"
225 | URL=${CLEANED[0]}
226 | IFS=$oldifs
227 |
228 | FILE=${URL##*/}
229 | EXTENSION=${URL##*.}
230 | UNZIP_DIR=${FILE/.$EXTENSION/}
231 | export UNZIP_DIR
232 |
233 | if [[ ! -f ${FILE} ]]; then
234 | FILE="$(find . -type f)"
235 | if [[ "$(wc -l <<< "${FILE}")" != 1 ]]; then
236 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Can't seem to find downloaded file!
" > /dev/null
237 | terminate 1
238 | fi
239 | fi
240 |
241 | if [[ "${USE_ALT_DUMPER}" == "false" ]]; then
242 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Extracting firmware with Python dumper..." > /dev/null
243 | uvx dumpyara "${FILE}" -o "${PWD}" || {
244 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Extraction failed!
" > /dev/null
245 | terminate 1
246 | }
247 | else
248 | # Clone necessary tools
249 | if ! [[ -d "${HOME}/Firmware_extractor" ]]; then
250 | git clone -q https://github.com/AndroidDumps/Firmware_extractor "${HOME}/Firmware_extractor"
251 | else
252 | git -C "${HOME}/Firmware_extractor" pull -q --rebase
253 | fi
254 |
255 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Extracting firmware with alternative dumper..." > /dev/null
256 | bash "${HOME}"/Firmware_extractor/extractor.sh "${FILE}" "${PWD}" || {
257 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Extraction failed!
" > /dev/null
258 | terminate 1
259 | }
260 |
261 | PARTITIONS=(system systemex system_ext system_other
262 | vendor cust odm odm_ext oem factory product modem
263 | xrom oppo_product opproduct reserve india my_preload
264 | my_odm my_stock my_operator my_country my_product my_company
265 | my_engineering my_heytap my_custom my_manifest my_carrier my_region
266 | my_bigball my_version special_preload vendor_dlkm odm_dlkm system_dlkm
267 | mi_ext radio product_h preas preavs preload
268 | )
269 |
270 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Extracting partitions...
" > /dev/null
271 |
272 | # Set commonly used binary names
273 | FSCK_EROFS="${HOME}/Firmware_extractor/tools/fsck.erofs"
274 | EXT2RD="${HOME}/Firmware_extractor/tools/ext2rd"
275 |
276 | # Extract the images
277 | for p in "${PARTITIONS[@]}"; do
278 | if [[ -f $p.img ]]; then
279 | # Create a folder for each partition
280 | mkdir "$p" || rm -rf "${p:?}"/*
281 |
282 | # Try to extract images via 'fsck.erofs'
283 | echo "[INFO] Extracting '$p' via 'fsck.erofs'..."
284 | ${FSCK_EROFS} --extract="$p" "$p".img >> /dev/null 2>&1 || {
285 | echo "[WARN] Extraction via 'fsck.erofs' failed."
286 |
287 | # Uses 'ext2rd' if images could not be extracted via 'fsck.erofs'
288 | echo "[INFO] Extracting '$p' via 'ext2rd'..."
289 | ${EXT2RD} "$p".img ./:"${p}" > /dev/null || {
290 | echo "[WARN] Extraction via 'ext2rd' failed."
291 |
292 | # Uses '7zz' if images could not be extracted via 'ext2rd'
293 | echo "[INFO] Extracting '$p' via '7zz'..."
294 | 7zz -snld x "$p".img -y -o"$p"/ > /dev/null || {
295 | echo "[ERROR] Extraction via '7zz' failed."
296 |
297 | # Only abort if we're at the first occourence
298 | if [[ "${p}" == "${PARTITIONS[0]}" ]]; then
299 | # In case of failure, bail out and abort dumping altogether
300 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Extraction failed!
" > /dev/null
301 | terminate 1
302 | fi
303 | }
304 | }
305 | }
306 |
307 | # Clean-up
308 | rm -f "$p".img
309 | fi
310 | done
311 |
312 | # Also extract 'fsg.mbn' from 'radio.img'
313 | if [ -f "${PWD}/fsg.mbn" ]; then
314 | echo "[INFO] Extracting 'fsg.mbn' via '7zz'..."
315 |
316 | # Create '${PWD}/radio/fsg'
317 | mkdir "${PWD}"/radio/fsg
318 |
319 | # Thankfully, 'fsg.mbn' is a simple EXT2 partition
320 | 7zz -snld x "${PWD}/fsg.mbn" -o"${PWD}/radio/fsg" > /dev/null
321 |
322 | # Remove 'fsg.mbn'
323 | rm -rf "${PWD}/fsg.mbn"
324 | fi
325 | fi
326 |
327 | rm -f "$FILE"
328 |
329 | for image in init_boot.img vendor_kernel_boot.img vendor_boot.img boot.img dtbo.img; do
330 | if [[ ! -f ${image} ]]; then
331 | x=$(find . -type f -name "${image}")
332 | if [[ -n $x ]]; then
333 | mv -v "$x" "${image}"
334 | fi
335 | fi
336 | done
337 |
338 | # Extract kernel, device-tree blobs [...]
339 | ## Set commonly used tools
340 | UNPACKBOOTIMG="${HOME}/Firmware_extractor/tools/unpackbootimg"
341 |
342 | # Extract 'boot.img'
343 | if [[ -f "${PWD}/boot.img" ]]; then
344 | # Set a variable for each path
345 | ## Image
346 | IMAGE=${PWD}/boot.img
347 |
348 | ## Output
349 | OUTPUT=${PWD}/boot
350 |
351 | # Python rewrite automatically extracts such partitions
352 | if [[ "${USE_ALT_DUMPER}" == "true" ]]; then
353 | mkdir -p "${OUTPUT}/ramdisk"
354 |
355 | # Unpack 'boot.img' through 'unpackbootimg'
356 | echo "[INFO] Extracting 'boot.img' content..."
357 | ${UNPACKBOOTIMG} -i "${IMAGE}" -o "${OUTPUT}" > /dev/null || \
358 | echo "[ERROR] Extraction unsuccessful."
359 |
360 | # Decrompress 'boot.img-ramdisk'
361 | ## Run only if 'boot.img-ramdisk' is not empty
362 | if file boot.img-ramdisk | grep -q LZ4 || file boot.img-ramdisk | grep -q gzip; then
363 | echo "[INFO] Extracting ramdisk..."
364 | unlz4 "${OUTPUT}/boot.img-ramdisk" "${OUTPUT}/ramdisk.lz4" > /dev/null
365 | 7zz -snld x "${OUTPUT}/ramdisk.lz4" -o"${OUTPUT}/ramdisk" > /dev/null || \
366 | echo "[ERROR] Failed to extract ramdisk."
367 |
368 | ## Clean-up
369 | rm -rf "${OUTPUT}/ramdisk.lz4"
370 | fi
371 | fi
372 |
373 | # Extract 'ikconfig'
374 | echo "[INFO] Extract 'ikconfig'..."
375 | if command -v extract-ikconfig > /dev/null ; then
376 | extract-ikconfig "${PWD}"/boot.img > "${PWD}"/ikconfig || {
377 | echo "[ERROR] Failed to generate 'ikconfig'"
378 | }
379 | fi
380 |
381 | # Generate non-stack symbols
382 | echo "[INFO] Generating 'kallsyms.txt'..."
383 | uvx --from git+https://github.com/marin-m/vmlinux-to-elf@master kallsyms-finder "${IMAGE}" > kallsyms.txt || \
384 | echo "[ERROR] Failed to generate 'kallsyms.txt'"
385 |
386 | # Generate analyzable '.elf'
387 | echo "[INFO] Extracting 'boot.elf'..."
388 | uvx --from git+https://github.com/marin-m/vmlinux-to-elf@master vmlinux-to-elf "${IMAGE}" boot.elf > /dev/null ||
389 | echo "[ERROR] Failed to generate 'boot.elf'"
390 |
391 | # Create necessary directories
392 | mkdir -p "${OUTPUT}/dts" "${OUTPUT}/dtb"
393 |
394 | # Extract device-tree blobs from 'boot.img'
395 | echo "[INFO] boot.img: Extracting device-tree blobs..."
396 | extract-dtb "${IMAGE}" -o "${OUTPUT}/dtb" > /dev/null || \
397 | echo "[INFO] No device-tree blobs found."
398 | rm -rf "${OUTPUT}/dtb/00_kernel"
399 |
400 | # Do not run 'dtc' if no DTB was found
401 | if [ "$(find "${OUTPUT}/dtb" -name "*.dtb")" ]; then
402 | echo "[INFO] Decompiling device-tree blobs..."
403 | # Decompile '.dtb' to '.dts'
404 | for dtb in $(find "${PWD}/boot/dtb" -type f); do
405 | dtc -q -I dtb -O dts "${dtb}" >> "${OUTPUT}/dts/$(basename "${dtb}" .dtb).dts" || \
406 | echo "[ERROR] Failed to decompile."
407 | done
408 | fi
409 | fi
410 |
411 | # Extract 'vendor_boot.img'
412 | if [[ -f "${PWD}/vendor_boot.img" ]]; then
413 | # Set a variable for each path
414 | ## Image
415 | IMAGE=${PWD}/vendor_boot.img
416 |
417 | ## Output
418 | OUTPUT=${PWD}/vendor_boot
419 |
420 | # Python rewrite automatically extracts such partitions
421 | if [[ "${USE_ALT_DUMPER}" == "true" ]]; then
422 | mkdir -p "${OUTPUT}/ramdisk"
423 |
424 | ## Unpack 'vendor_boot.img' through 'unpackbootimg'
425 | echo "[INFO] Extracting 'vendor_boot.img' content..."
426 | ${UNPACKBOOTIMG} -i "${IMAGE}" -o "${OUTPUT}" > /dev/null || \
427 | echo "[ERROR] Extraction unsuccessful."
428 |
429 | # Decrompress 'vendor_boot.img-vendor_ramdisk'
430 | echo "[INFO] Extracting ramdisk..."
431 | unlz4 "${OUTPUT}/vendor_boot.img-vendor_ramdisk" "${OUTPUT}/ramdisk.lz4" > /dev/null
432 | 7zz -snld x "${OUTPUT}/ramdisk.lz4" -o"${OUTPUT}/ramdisk" > /dev/null || \
433 | echo "[ERROR] Failed to extract ramdisk."
434 |
435 | ## Clean-up
436 | rm -rf "${OUTPUT}/ramdisk.lz4"
437 | fi
438 |
439 | # Create necessary directories
440 | mkdir -p "${OUTPUT}/dts" "${OUTPUT}/dtb"
441 |
442 | # Extract device-tree blobs from 'vendor_boot.img'
443 | echo "[INFO] vendor_boot.img: Extracting device-tree blobs..."
444 | extract-dtb "${IMAGE}" -o "${OUTPUT}/dtb" > /dev/null || \
445 | echo "[INFO] No device-tree blobs found."
446 | rm -rf "${OUTPUT}/dtb/00_kernel"
447 |
448 | # Decompile '.dtb' to '.dts'
449 | if [ "$(find "${OUTPUT}/dtb" -name "*.dtb")" ]; then
450 | echo "[INFO] Decompiling device-tree blobs..."
451 | # Decompile '.dtb' to '.dts'
452 | for dtb in $(find "${OUTPUT}/dtb" -type f); do
453 | dtc -q -I dtb -O dts "${dtb}" >> "${OUTPUT}/dts/$(basename "${dtb}" .dtb).dts" || \
454 | echo "[ERROR] Failed to decompile."
455 | done
456 | fi
457 | fi
458 |
459 | # Extract 'vendor_kernel_boot.img'
460 | if [[ -f "${PWD}/vendor_kernel_boot.img" ]]; then
461 | # Set a variable for each path
462 | ## Image
463 | IMAGE=${PWD}/vendor_kernel_boot.img
464 |
465 | ## Output
466 | OUTPUT=${PWD}/vendor_kernel_boot
467 |
468 | # Python rewrite automatically extracts such partitions
469 | if [[ "${USE_ALT_DUMPER}" == "true" ]]; then
470 | mkdir -p "${OUTPUT}/ramdisk"
471 |
472 | # Unpack 'vendor_kernel_boot.img' through 'unpackbootimg'
473 | echo "[INFO] Extracting 'vendor_kernel_boot.img' content..."
474 | ${UNPACKBOOTIMG} -i "${IMAGE}" -o "${OUTPUT}" > /dev/null || \
475 | echo "[ERROR] Extraction unsuccessful."
476 |
477 | # Decrompress 'vendor_kernel_boot.img-vendor_ramdisk'
478 | echo "[INFO] Extracting ramdisk..."
479 | unlz4 "${OUTPUT}/vendor_kernel_boot.img-vendor_ramdisk" "${OUTPUT}/ramdisk.lz4" > /dev/null
480 | 7zz -snld x "${OUTPUT}/ramdisk.lz4" -o"${OUTPUT}/ramdisk" > /dev/null || \
481 | echo "[ERROR] Failed to extract ramdisk."
482 |
483 | ## Clean-up
484 | rm -rf "${OUTPUT}/ramdisk.lz4"
485 | fi
486 |
487 | # Create necessary directories
488 | mkdir -p "${OUTPUT}/dts" "${OUTPUT}/dtb"
489 |
490 | # Extract device-tree blobs from 'vendor_kernel_boot.img'
491 | echo "[INFO] vendor_kernel_boot.img: Extracting device-tree blobs..."
492 | extract-dtb "${IMAGE}" -o "${OUTPUT}/dtb" > /dev/null || \
493 | echo "[INFO] No device-tree blobs found."
494 | rm -rf "${OUTPUT}/dtb/00_kernel"
495 |
496 | # Decompile '.dtb' to '.dts'
497 | if [ "$(find "${OUTPUT}/dtb" -name "*.dtb")" ]; then
498 | echo "[INFO] Decompiling device-tree blobs..."
499 | # Decompile '.dtb' to '.dts'
500 | for dtb in $(find "${OUTPUT}/dtb" -type f); do
501 | dtc -q -I dtb -O dts "${dtb}" >> "${OUTPUT}/dts/$(basename "${dtb}" .dtb).dts" || \
502 | echo "[ERROR] Failed to decompile."
503 | done
504 | fi
505 | fi
506 |
507 | # Extract 'init_boot.img'
508 | if [[ -f "${PWD}/init_boot.img" ]] && [[ "${USE_ALT_DUMPER}" == "true" ]]; then
509 | # Set a variable for each path
510 | ## Image
511 | IMAGE=${PWD}/init_boot.img
512 |
513 | ## Output
514 | OUTPUT=${PWD}/init_boot
515 |
516 | # Create necessary directories
517 | mkdir -p "${OUTPUT}/ramdisk"
518 |
519 | # Unpack 'init_boot.img' through 'unpackbootimg'
520 | echo "[INFO] Extracting 'init_boot.img' content..."
521 | ${UNPACKBOOTIMG} -i "${IMAGE}" -o "${OUTPUT}" > /dev/null || \
522 | echo "[ERROR] Extraction unsuccessful."
523 |
524 | # Decrompress 'init_boot.img-ramdisk'
525 | echo "[INFO] Extracting ramdisk..."
526 | unlz4 "${OUTPUT}/init_boot.img-ramdisk" "${OUTPUT}/ramdisk.lz4" > /dev/null
527 | 7zz -snld x "${OUTPUT}/ramdisk.lz4" -o"${OUTPUT}/ramdisk" > /dev/null || \
528 | echo "[ERROR] Failed to extract ramdisk."
529 |
530 | ## Clean-up
531 | rm -rf "${OUTPUT}/ramdisk.lz4"
532 | fi
533 |
534 | # Extract 'dtbo.img'
535 | if [[ -f "${PWD}/dtbo.img" ]]; then
536 | # Set a variable for each path
537 | ## Image
538 | IMAGE=${PWD}/dtbo.img
539 |
540 | ## Output
541 | OUTPUT=${PWD}/dtbo
542 |
543 | # Create necessary directories
544 | mkdir -p "${OUTPUT}/dts"
545 |
546 | # Extract device-tree blobs from 'dtbo.img'
547 | echo "[INFO] dbto.img: Extracting device-tree blobs..."
548 | extract-dtb "${IMAGE}" -o "${OUTPUT}" > /dev/null || \
549 | echo "[INFO] No device-tree blobs found."
550 | rm -rf "${OUTPUT}/00_kernel"
551 |
552 | # Decompile '.dtb' to '.dts'
553 | echo "[INFO] Decompiling device-tree blobs..."
554 | for dtb in $(find "${OUTPUT}" -type f); do
555 | dtc -q -I dtb -O dts "${dtb}" >> "${OUTPUT}/dts/$(basename "${dtb}" .dtb).dts" || \
556 | echo "[ERROR] Failed to decompile."
557 | done
558 | fi
559 |
560 | # Oppo/Realme/OnePlus devices have some images in folders, extract those
561 | for dir in "vendor/euclid" "system/system/euclid" "reserve/reserve"; do
562 | [[ -d ${dir} ]] && {
563 | pushd "${dir}" || terminate 1
564 | for f in *.img; do
565 | [[ -f $f ]] || continue
566 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Partition Name: ${p}
" > /dev/null
567 | 7zz -snld x "$f" -o"${f/.img/}" > /dev/null
568 | rm -fv "$f"
569 | done
570 | popd || terminate 1
571 | }
572 | done
573 |
574 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"All partitions extracted.
" > /dev/null
575 |
576 | # Generate 'board-info.txt'
577 | echo "[INFO] Generating 'board-info.txt'..."
578 |
579 | ## Generic
580 | if [ -f ./vendor/build.prop ]; then
581 | strings ./vendor/build.prop | grep "ro.vendor.build.date.utc" | sed "s|ro.vendor.build.date.utc|require version-vendor|g" >> ./board-info.txt
582 | fi
583 |
584 | ## Qualcomm-specific
585 | if [[ $(find . -name "modem") ]] && [[ $(find . -name "*./tz*") ]]; then
586 | find ./modem -type f -exec strings {} \; | rg "QC_IMAGE_VERSION_STRING=MPSS." | sed "s|QC_IMAGE_VERSION_STRING=MPSS.||g" | cut -c 4- | sed -e 's/^/require version-baseband=/' >> "${PWD}"/board-info.txt
587 | find ./tz* -type f -exec strings {} \; | rg "QC_IMAGE_VERSION_STRING" | sed "s|QC_IMAGE_VERSION_STRING|require version-trustzone|g" >> "${PWD}"/board-info.txt
588 | fi
589 |
590 | ## Sort 'board-info.txt' content
591 | if [ -f "${PWD}"/board-info.txt ]; then
592 | sort -u -o ./board-info.txt ./board-info.txt
593 | fi
594 |
595 | # Prop extraction
596 | echo "[INFO] Extracting properties..."
597 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Extracting properties...
" > /dev/null
598 |
599 | oplus_pipeline_key=$(rg -m1 -INoP --no-messages "(?<=^ro.oplus.pipeline_key=).*" my_manifest/build*.prop)
600 | honor_product_base_version=$(rg -m1 -INoP --no-messages "(?<=^ro.comp.hl.product_base_version=).*" product_h/etc/prop/local*.prop)
601 |
602 | flavor=$(rg -m1 -INoP --no-messages "(?<=^ro.build.flavor=).*" {vendor,system,system/system}/build.prop)
603 | [[ -z ${flavor} ]] && flavor=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.flavor=).*" vendor/build*.prop)
604 | [[ -z ${flavor} ]] && flavor=$(rg -m1 -INoP --no-messages "(?<=^ro.build.flavor=).*" {vendor,system,system/system}/build*.prop)
605 | [[ -z ${flavor} ]] && flavor=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.flavor=).*" {system,system/system}/build*.prop)
606 | [[ -z ${flavor} ]] && flavor=$(rg -m1 -INoP --no-messages "(?<=^ro.build.type=).*" {system,system/system}/build*.prop)
607 |
608 | release=$(rg -m1 -INoP --no-messages "(?<=^ro.build.version.release=).*" {my_manifest,vendor,system,system/system}/build*.prop)
609 | [[ -z ${release} ]] && release=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.version.release=).*" vendor/build*.prop)
610 | [[ -z ${release} ]] && release=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.version.release=).*" {system,system/system}/build*.prop)
611 | release=$(echo "$release" | head -1)
612 |
613 | id=$(rg -m1 -INoP --no-messages "(?<=^ro.build.id=).*" my_manifest/build*.prop)
614 | [[ -z ${id} ]] && id=$(rg -m1 -INoP --no-messages "(?<=^ro.build.id=).*" system/system/build_default.prop)
615 | [[ -z ${id} ]] && id=$(rg -m1 -INoP --no-messages "(?<=^ro.build.id=).*" vendor/euclid/my_manifest/build.prop)
616 | [[ -z ${id} ]] && id=$(rg -m1 -INoP --no-messages "(?<=^ro.build.id=).*" {vendor,system,system/system}/build*.prop)
617 | [[ -z ${id} ]] && id=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.id=).*" vendor/build*.prop)
618 | [[ -z ${id} ]] && id=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.id=).*" {system,system/system}/build*.prop)
619 | id=$(echo "$id" | head -1)
620 |
621 | incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.build.version.incremental=).*" my_manifest/build*.prop)
622 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.build.version.incremental=).*" system/system/build_default.prop)
623 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.build.version.incremental=).*" vendor/euclid/my_manifest/build.prop)
624 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.build.version.incremental=).*" {vendor,system,system/system}/build*.prop | head -1)
625 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.version.incremental=).*" my_manifest/build*.prop)
626 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.version.incremental=).*" vendor/euclid/my_manifest/build.prop)
627 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.version.incremental=).*" vendor/build*.prop)
628 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.version.incremental=).*" {system,system/system}/build*.prop | head -1)
629 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.build.version.incremental=).*" my_product/build*.prop)
630 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.version.incremental=).*" my_product/build*.prop)
631 | [[ -z ${incremental} ]] && incremental=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.version.incremental=).*" my_product/build*.prop)
632 | incremental=$(echo "$incremental" | head -1)
633 |
634 | tags=$(rg -m1 -INoP --no-messages "(?<=^ro.build.tags=).*" {vendor,system,system/system}/build*.prop)
635 | [[ -z ${tags} ]] && tags=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.tags=).*" vendor/build*.prop)
636 | [[ -z ${tags} ]] && tags=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.tags=).*" {system,system/system}/build*.prop)
637 | tags=$(echo "$tags" | head -1)
638 |
639 | platform=$(rg -m1 -INoP --no-messages "(?<=^ro.board.platform=).*" {vendor,system,system/system}/build*.prop | head -1)
640 | [[ -z ${platform} ]] && platform=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.board.platform=).*" vendor/build*.prop)
641 | [[ -z ${platform} ]] && platform=$(rg -m1 -INoP --no-messages rg"(?<=^ro.system.board.platform=).*" {system,system/system}/build*.prop)
642 | platform=$(echo "$platform" | head -1)
643 |
644 | manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.manufacturer=).*" odm/etc/build*.prop)
645 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.manufacturer=).*" odm/etc/fingerprint/build.default.prop)
646 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.manufacturer=).*" my_product/build*.prop)
647 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.manufacturer=).*" my_manifest/build*.prop)
648 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.manufacturer=).*" system/system/build_default.prop)
649 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.manufacturer=).*" vendor/euclid/my_manifest/build.prop)
650 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.manufacturer=).*" {vendor,system,system/system}/build*.prop | head -1)
651 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand.sub=).*" my_product/build*.prop)
652 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand.sub=).*" system/system/euclid/my_product/build*.prop)
653 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.product.manufacturer=).*" vendor/build*.prop)
654 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.manufacturer=).*" my_manifest/build*.prop)
655 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.manufacturer=).*" system/system/build_default.prop)
656 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.manufacturer=).*" vendor/euclid/my_manifest/build.prop)
657 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.manufacturer=).*" vendor/build*.prop)
658 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.system.product.manufacturer=).*" {system,system/system}/build*.prop)
659 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.system.manufacturer=).*" {system,system/system}/build*.prop)
660 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.manufacturer=).*" my_manifest/build*.prop)
661 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.manufacturer=).*" system/system/build_default.prop)
662 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.manufacturer=).*" vendor/euclid/my_manifest/build.prop)
663 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.manufacturer=).*" vendor/odm/etc/build*.prop)
664 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.manufacturer=).*" {oppo_product,my_product}/build*.prop | head -1)
665 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.manufacturer=).*" vendor/euclid/*/build.prop)
666 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.system.product.manufacturer=).*" vendor/euclid/*/build.prop)
667 | [[ -z ${manufacturer} ]] && manufacturer=$(rg -m1 -INoP --no-messages "(?<=^ro.product.product.manufacturer=).*" vendor/euclid/product/build*.prop)
668 | manufacturer=$(echo "$manufacturer" | head -1)
669 |
670 | fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.odm.build.fingerprint=).*" odm/etc/*build*.prop)
671 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.fingerprint=).*" my_manifest/build*.prop)
672 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.fingerprint=).*" system/system/build_default.prop)
673 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.fingerprint=).*" vendor/euclid/my_manifest/build.prop)
674 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.fingerprint=).*" odm/etc/fingerprint/build.default.prop)
675 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.fingerprint=).*" vendor/build*.prop)
676 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.build.fingerprint=).*" my_manifest/build*.prop)
677 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.build.fingerprint=).*" system/system/build_default.prop)
678 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.build.fingerprint=).*" vendor/euclid/my_manifest/build.prop)
679 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.build.fingerprint=).*" {system,system/system}/build*.prop)
680 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.product.build.fingerprint=).*" product/build*.prop)
681 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.fingerprint=).*" {system,system/system}/build*.prop)
682 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.build.fingerprint=).*" my_product/build.prop)
683 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.fingerprint=).*" my_product/build.prop)
684 | [[ -z ${fingerprint} ]] && fingerprint=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.fingerprint=).*" my_product/build.prop)
685 | fingerprint=$(echo "$fingerprint" | head -1)
686 |
687 | codename=$(rg -m1 -INoP --no-messages "(?<=^ro.build.product=).*" product_h/etc/prop/local*.prop | head -1)
688 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.device=).*" odm/etc/build*.prop | head -1)
689 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.device=).*" system/system/build_default.prop)
690 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.device=).*" odm/etc/fingerprint/build.default.prop)
691 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.device=).*" my_manifest/build*.prop)
692 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.device=).*" system/system/build_default.prop)
693 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.device=).*" vendor/euclid/my_manifest/build.prop)
694 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.device=).*" system/system/build_default.prop)
695 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.device=).*" vendor/euclid/my_manifest/build.prop)
696 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.product.device=).*" system/system/build_default.prop)
697 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.product.device=).*" vendor/build*.prop | head -1)
698 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.device=).*" vendor/build*.prop | head -1)
699 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.device=).*" {vendor,system,system/system}/build*.prop | head -1)
700 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.product.device.oem=).*" odm/build.prop | head -1)
701 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.product.device.oem=).*" vendor/euclid/odm/build.prop | head -1)
702 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.device=).*" my_manifest/build*.prop)
703 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.system.device=).*" {system,system/system}/build*.prop | head -1)
704 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.system.device=).*" vendor/euclid/*/build.prop | head -1)
705 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.product.device=).*" vendor/euclid/*/build.prop | head -1)
706 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.product.device=).*" system/system/build_default.prop)
707 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.product.model=).*" vendor/euclid/*/build.prop | head -1)
708 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.device=).*" {oppo_product,my_product}/build*.prop | head -1)
709 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.product.device=).*" oppo_product/build*.prop)
710 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.system.device=).*" my_product/build*.prop)
711 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.device=).*" my_product/build*.prop)
712 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.build.fota.version=).*" {system,system/system}/build*.prop | cut -d - -f1 | head -1)
713 | [[ -z ${codename} ]] && codename=$(rg -m1 -INoP --no-messages "(?<=^ro.build.product=).*" {vendor,system,system/system}/build*.prop | head -1)
714 | [[ -z ${codename} ]] && codename=$(echo "$fingerprint" | cut -d / -f3 | cut -d : -f1)
715 | [[ -z $codename ]] && {
716 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Codename not detected! Aborting!
" > /dev/null
717 | terminate 1
718 | }
719 |
720 | brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.brand=).*" odm/etc/"${codename}"_build.prop | head -1)
721 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.brand=).*" odm/etc/build*.prop | head -1)
722 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.brand=).*" system/system/build_default.prop)
723 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand=).*" odm/etc/fingerprint/build.default.prop)
724 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand=).*" my_product/build*.prop)
725 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand=).*" system/system/build_default.prop)
726 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand=).*" vendor/euclid/my_manifest/build.prop)
727 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand=).*" {vendor,system,system/system}/build*.prop | head -1)
728 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand.sub=).*" my_product/build*.prop)
729 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand.sub=).*" system/system/euclid/my_product/build*.prop)
730 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.brand=).*" my_manifest/build*.prop)
731 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.brand=).*" system/system/build_default.prop)
732 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.brand=).*" vendor/euclid/my_manifest/build.prop)
733 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.vendor.brand=).*" vendor/build*.prop | head -1)
734 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.product.brand=).*" vendor/build*.prop | head -1)
735 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.system.brand=).*" {system,system/system}/build*.prop | head -1)
736 | [[ -z ${brand} || ${brand} == "OPPO" ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.system.brand=).*" vendor/euclid/*/build.prop | head -1)
737 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.product.brand=).*" vendor/euclid/product/build*.prop)
738 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.brand=).*" my_manifest/build*.prop)
739 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.brand=).*" vendor/euclid/my_manifest/build.prop)
740 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.odm.brand=).*" vendor/odm/etc/build*.prop)
741 | [[ -z ${brand} ]] && brand=$(rg -m1 -INoP --no-messages "(?<=^ro.product.brand=).*" {oppo_product,my_product}/build*.prop | head -1)
742 | [[ -z ${brand} ]] && brand=$(echo "$fingerprint" | cut -d / -f1)
743 | [[ -z ${brand} ]] && brand="$manufacturer"
744 |
745 | description=$(rg -m1 -INoP --no-messages "(?<=^ro.build.description=).*" {system,system/system}/build.prop)
746 | [[ -z ${description} ]] && description=$(rg -m1 -INoP --no-messages "(?<=^ro.build.description=).*" {system,system/system}/build*.prop)
747 | [[ -z ${description} ]] && description=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.description=).*" vendor/build.prop)
748 | [[ -z ${description} ]] && description=$(rg -m1 -INoP --no-messages "(?<=^ro.vendor.build.description=).*" vendor/build*.prop)
749 | [[ -z ${description} ]] && description=$(rg -m1 -INoP --no-messages "(?<=^ro.product.build.description=).*" product/build.prop)
750 | [[ -z ${description} ]] && description=$(rg -m1 -INoP --no-messages "(?<=^ro.product.build.description=).*" product/build*.prop)
751 | [[ -z ${description} ]] && description=$(rg -m1 -INoP --no-messages "(?<=^ro.system.build.description=).*" {system,system/system}/build*.prop)
752 | [[ -z ${description} ]] && description="$flavor $release $id $incremental $tags"
753 |
754 | # In case there's an additional space on the 'description',
755 | # remove it as it prevents 'git' from creating a branch.
756 | if [[ $(echo "${description}" | head -c1) == " " ]]; then
757 | description=$(echo "${description}" | sed s/'\s'//)
758 | fi
759 |
760 | is_ab=$(rg -m1 -INoP --no-messages "(?<=^ro.build.ab_update=).*" {system,system/system,vendor}/build*.prop)
761 | is_ab=$(echo "$is_ab" | head -1)
762 | [[ -z ${is_ab} ]] && is_ab="false"
763 |
764 | codename=$(echo "$codename" | tr ' ' '_')
765 |
766 | # Append 'oplus_pipeline_key' in case it's set
767 | if [[ -n "${oplus_pipeline_key}" ]]; then
768 | branch=$(echo "${description}"--"${oplus_pipeline_key}" | head -1 | tr ' ' '-')
769 | # Append 'honor_product_base_version' in case it's set
770 | elif [[ -n "${honor_product_base_version}" ]]; then
771 | branch=$(echo "${description}"--"${honor_product_base_version}" | head -1 | tr ' ' '-')
772 | else
773 | branch=$(echo "$description" | head -1 | tr ' ' '-')
774 | fi
775 |
776 | repo_subgroup=$(echo "$brand" | tr '[:upper:]' '[:lower:]')
777 | [[ -z $repo_subgroup ]] && repo_subgroup=$(echo "$manufacturer" | tr '[:upper:]' '[:lower:]')
778 | repo_name=$(echo "$codename" | tr '[:upper:]' '[:lower:]')
779 | repo="$repo_subgroup/$repo_name"
780 | platform=$(echo "$platform" | tr '[:upper:]' '[:lower:]' | tr -dc '[:print:]' | tr '_' '-' | cut -c 1-35)
781 | top_codename=$(echo "$codename" | tr '[:upper:]' '[:lower:]' | tr -dc '[:print:]' | tr '_' '-' | cut -c 1-35)
782 | manufacturer=$(echo "$manufacturer" | tr '[:upper:]' '[:lower:]' | tr -dc '[:print:]' | tr '_' '-' | cut -c 1-35)
783 |
784 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"All props extracted.
" > /dev/null
785 |
786 | printf "%s\n" "flavor: ${flavor}
787 | release: ${release}
788 | id: ${id}
789 | incremental: ${incremental}
790 | tags: ${tags}
791 | fingerprint: ${fingerprint}
792 | brand: ${brand}
793 | codename: ${codename}
794 | description: ${description}
795 | branch: ${branch}
796 | repo: ${repo}
797 | manufacturer: ${manufacturer}
798 | platform: ${platform}
799 | top_codename: ${top_codename}
800 | is_ab: ${is_ab}"
801 |
802 | # Generate device tree ('aospdtgen')
803 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Generating device tree...
" > /dev/null
804 | mkdir -p aosp-device-tree
805 |
806 | echo "[INFO] Generating device tree..."
807 | if uvx aospdtgen@1.1.1 . --output ./aosp-device-tree > /dev/null; then
808 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Device tree successfully generated.
" > /dev/null
809 | else
810 | echo "[ERROR] Failed to generate device tree."
811 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Failed to generate device tree.
" > /dev/null
812 | fi
813 |
814 | # Generate 'all_files.txt'
815 | echo "[INFO] Generating 'all_files.txt'..."
816 | find . -type f ! -name all_files.txt -and ! -path "*/aosp-device-tree/*" -printf '%P\n' | sort | grep -v ".git/" > ./all_files.txt
817 |
818 | # Check whether the subgroup exists or not
819 | if ! group_id_json="$(curl --compressed --fail-with-body -sH "Authorization: Bearer $DUMPER_TOKEN" "https://$GITLAB_SERVER/api/v4/groups/$ORG%2f$repo_subgroup")"; then
820 | echo "Response: $group_id_json"
821 | if ! group_id_json="$(curl --compressed --fail-with-body -sH "Authorization: Bearer $DUMPER_TOKEN" "https://$GITLAB_SERVER/api/v4/groups" -X POST -F name="${repo_subgroup^}" -F parent_id=64 -F path="${repo_subgroup}" -F visibility=public)"; then
822 | echo "Creating subgroup for $repo_subgroup failed"
823 | echo "Response: $group_id_json"
824 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Creating subgroup for $repo_subgroup failed!
" > /dev/null
825 | fi
826 | fi
827 |
828 | if ! group_id="$(jq '.id' -e <<< "${group_id_json}")"; then
829 | echo "Unable to get gitlab group id"
830 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Unable to get gitlab group id!
" > /dev/null
831 | terminate 1
832 | fi
833 |
834 | # Create the repo if it doesn't exist
835 | project_id_json="$(curl --compressed -sH "Authorization: bearer ${DUMPER_TOKEN}" "https://$GITLAB_SERVER/api/v4/projects/$ORG%2f$repo_subgroup%2f$repo_name")"
836 | if ! project_id="$(jq .id -e <<< "${project_id_json}")"; then
837 | project_id_json="$(curl --compressed -sH "Authorization: bearer ${DUMPER_TOKEN}" "https://$GITLAB_SERVER/api/v4/projects" -X POST -F namespace_id="$group_id" -F name="$repo_name" -F visibility=public)"
838 | if ! project_id="$(jq .id -e <<< "${project_id_json}")"; then
839 | echo "Could get get project id"
840 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Could not get project id!
" > /dev/null
841 | terminate 1
842 | fi
843 | fi
844 |
845 | branch_json="$(curl --compressed -sH "Authorization: bearer ${DUMPER_TOKEN}" "https://$GITLAB_SERVER/api/v4/projects/$project_id/repository/branches/$branch")"
846 | [[ "$(jq -r '.name' -e <<< "${branch_json}")" == "$branch" ]] && {
847 | echo "$branch already exists in $repo"
848 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"$branch already exists in
$repo!" > /dev/null
849 | terminate 2
850 | }
851 |
852 | # Add, commit, and push after filtering out certain files
853 | git init --initial-branch "$branch"
854 | git config user.name "dumper"
855 | git config user.email "dumper@$GITLAB_SERVER"
856 |
857 | ## Committing
858 | echo "[INFO] Adding files and committing..."
859 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Committing...
" > /dev/null
860 | git add --ignore-errors -A >> /dev/null 2>&1
861 | git commit --quiet --signoff --message="$description" || {
862 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Committing failed!
" > /dev/null
863 | echo "[ERROR] Committing failed!"
864 | terminate 1
865 | }
866 |
867 | ## Pushing
868 | echo "[INFO] Pushing..."
869 | sendTG_edit_wrapper temporary "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Pushing...
" > /dev/null
870 | git push "$PUSH_HOST:$ORG/$repo.git" HEAD:refs/heads/"$branch" || {
871 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Pushing failed!
" > /dev/null
872 | echo "[ERROR] Pushing failed!"
873 | terminate 1
874 | }
875 |
876 | # Set default branch to the newly pushed branch
877 | curl --compressed -s -H "Authorization: bearer ${DUMPER_TOKEN}" "https://$GITLAB_SERVER/api/v4/projects/$project_id" -X PUT -F default_branch="$branch" > /dev/null
878 |
879 | # Send message to Telegram group
880 | sendTG_edit_wrapper permanent "${MESSAGE_ID}" "${MESSAGE}"$'\n'"Pushed
$description" > /dev/null
881 |
882 | ## Only add this line in case URL is expected in the whitelist
883 | if [ "${WHITELISTED}" == true ] && [ "${ADD_BLACKLIST}" == false ]; then
884 | link="[firmware]"
885 | fi
886 |
887 | echo -e "[INFO] Sending Telegram notification"
888 | tg_html_text="Brand: $brand
889 | Device: $codename
890 | Version: $release
891 | Fingerprint: $fingerprint
892 | Platform: $platform
893 | [repo] $link"
894 |
895 | # Send message to Telegram channel
896 | curl --compressed -s "https://api.telegram.org/bot${API_KEY}/sendmessage" --data "text=${tg_html_text}&chat_id=@android_dumps&parse_mode=HTML&disable_web_page_preview=True" > /dev/null
897 |
898 | terminate 0
899 |
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | plugins = pydantic.mypy
3 | ignore_missing_imports = True
4 | disallow_untyped_defs = True
5 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | authors = [
3 | {name = "Akhil Narang", email = "me@akhilnarang.dev"},
4 | ]
5 | requires-python = "<4.0,>=3.10"
6 | dependencies = [
7 | "python-telegram-bot<21.0,>=20.7",
8 | "pydantic<3.0.0,>=2.5.2",
9 | "httpx<1.0.0,>=0.25.2",
10 | "rich<14.0.0,>=13.7.0",
11 | "pydantic-settings<3.0.0,>=2.1.0",
12 | ]
13 | name = "dumpyarabot"
14 | version = "0.1.0"
15 | description = "Bot for AndroidDumps"
16 | readme = "README.md"
17 |
18 | [tool.uv]
19 | dev-dependencies = [
20 | "black<24.0.0,>=23.12.0",
21 | "mypy<2.0.0,>=1.7.1",
22 | "isort<6.0.0,>=5.13.2",
23 | "ruff<1.0.0,>=0.1.8",
24 | "autoflake<3.0.0,>=2.2.1",
25 | ]
26 |
27 |
--------------------------------------------------------------------------------
/scripts/format.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -ex
3 |
4 | # If argument was given then format only that file, else format entire dumpyarabot
5 | if [[ -z "$path" ]]; then
6 | path="dumpyarabot"
7 | fi
8 |
9 | # Format
10 | autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place ${path} --exclude=__init__.py
11 | black ${path}
12 | isort ${path}
13 |
--------------------------------------------------------------------------------
/scripts/lint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -ex
4 |
5 | # If argument was given then lint only that file, else lint entire dumpyarabot
6 | if [[ -z "$path" ]]; then
7 | path="dumpyarabot"
8 | fi
9 |
10 | # Lint
11 | mypy $path
12 | black $path --check
13 | isort --check-only $path
14 | ruff $path
15 |
--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | requires-python = ">=3.10, <4.0"
3 | resolution-markers = [
4 | "python_full_version < '3.13'",
5 | "python_full_version >= '3.13'",
6 | ]
7 |
8 | [[package]]
9 | name = "annotated-types"
10 | version = "0.7.0"
11 | source = { registry = "https://pypi.org/simple" }
12 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
13 | wheels = [
14 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
15 | ]
16 |
17 | [[package]]
18 | name = "anyio"
19 | version = "4.6.0"
20 | source = { registry = "https://pypi.org/simple" }
21 | dependencies = [
22 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
23 | { name = "idna" },
24 | { name = "sniffio" },
25 | { name = "typing-extensions", marker = "python_full_version < '3.11'" },
26 | ]
27 | sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 }
28 | wheels = [
29 | { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 },
30 | ]
31 |
32 | [[package]]
33 | name = "autoflake"
34 | version = "2.3.1"
35 | source = { registry = "https://pypi.org/simple" }
36 | dependencies = [
37 | { name = "pyflakes" },
38 | { name = "tomli", marker = "python_full_version < '3.11'" },
39 | ]
40 | sdist = { url = "https://files.pythonhosted.org/packages/2a/cb/486f912d6171bc5748c311a2984a301f4e2d054833a1da78485866c71522/autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e", size = 27642 }
41 | wheels = [
42 | { url = "https://files.pythonhosted.org/packages/a2/ee/3fd29bf416eb4f1c5579cf12bf393ae954099258abd7bde03c4f9716ef6b/autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840", size = 32483 },
43 | ]
44 |
45 | [[package]]
46 | name = "black"
47 | version = "23.12.1"
48 | source = { registry = "https://pypi.org/simple" }
49 | dependencies = [
50 | { name = "click" },
51 | { name = "mypy-extensions" },
52 | { name = "packaging" },
53 | { name = "pathspec" },
54 | { name = "platformdirs" },
55 | { name = "tomli", marker = "python_full_version < '3.11'" },
56 | { name = "typing-extensions", marker = "python_full_version < '3.11'" },
57 | ]
58 | sdist = { url = "https://files.pythonhosted.org/packages/fd/f4/a57cde4b60da0e249073009f4a9087e9e0a955deae78d3c2a493208d0c5c/black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5", size = 620809 }
59 | wheels = [
60 | { url = "https://files.pythonhosted.org/packages/fb/58/677da52d845b59505a8a787ff22eff9cfd9046b5789aa2bd387b236db5c5/black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2", size = 1560531 },
61 | { url = "https://files.pythonhosted.org/packages/11/92/522a4f1e4b2b8da62e4ec0cb8acf2d257e6d39b31f4214f0fd94d2eeb5bd/black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba", size = 1404644 },
62 | { url = "https://files.pythonhosted.org/packages/a4/dc/af67d8281e9a24f73d24b060f3f03f6d9ad6be259b3c6acef2845e17d09c/black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0", size = 1711153 },
63 | { url = "https://files.pythonhosted.org/packages/7e/0f/94d7c36b421ea187359c413be7b9fc66dc105620c3a30b1c94310265830a/black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3", size = 1332918 },
64 | { url = "https://files.pythonhosted.org/packages/ed/2c/d9b1a77101e6e5f294f6553d76c39322122bfea2a438aeea4eb6d4b22749/black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba", size = 1541926 },
65 | { url = "https://files.pythonhosted.org/packages/72/e2/d981a3ff05ba9abe3cfa33e70c986facb0614fd57c4f802ef435f4dd1697/black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b", size = 1388465 },
66 | { url = "https://files.pythonhosted.org/packages/eb/59/1f5c8eb7bba8a8b1bb5c87f097d16410c93a48a6655be3773db5d2783deb/black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59", size = 1691993 },
67 | { url = "https://files.pythonhosted.org/packages/37/bf/a80abc6fcdb00f0d4d3d74184b172adbf2197f6b002913fa0fb6af4dc6db/black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50", size = 1340929 },
68 | { url = "https://files.pythonhosted.org/packages/66/16/8726cedc83be841dfa854bbeef1288ee82272282a71048d7935292182b0b/black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e", size = 1569989 },
69 | { url = "https://files.pythonhosted.org/packages/d2/1e/30f5eafcc41b8378890ba39b693fa111f7dca8a2620ba5162075d95ffe46/black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec", size = 1398647 },
70 | { url = "https://files.pythonhosted.org/packages/99/de/ddb45cc044256431d96d846ce03164d149d81ca606b5172224d1872e0b58/black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e", size = 1720450 },
71 | { url = "https://files.pythonhosted.org/packages/98/2b/54e5dbe9be5a10cbea2259517206ff7b6a452bb34e07508c7e1395950833/black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9", size = 1351070 },
72 | { url = "https://files.pythonhosted.org/packages/7b/14/4da7b12a9abc43a601c215cb5a3d176734578da109f0dbf0a832ed78be09/black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e", size = 194363 },
73 | ]
74 |
75 | [[package]]
76 | name = "certifi"
77 | version = "2024.8.30"
78 | source = { registry = "https://pypi.org/simple" }
79 | sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
80 | wheels = [
81 | { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
82 | ]
83 |
84 | [[package]]
85 | name = "click"
86 | version = "8.1.7"
87 | source = { registry = "https://pypi.org/simple" }
88 | dependencies = [
89 | { name = "colorama", marker = "platform_system == 'Windows'" },
90 | ]
91 | sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
92 | wheels = [
93 | { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
94 | ]
95 |
96 | [[package]]
97 | name = "colorama"
98 | version = "0.4.6"
99 | source = { registry = "https://pypi.org/simple" }
100 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
101 | wheels = [
102 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
103 | ]
104 |
105 | [[package]]
106 | name = "dumpyarabot"
107 | version = "0.1.0"
108 | source = { virtual = "." }
109 | dependencies = [
110 | { name = "httpx" },
111 | { name = "pydantic" },
112 | { name = "pydantic-settings" },
113 | { name = "python-telegram-bot" },
114 | { name = "rich" },
115 | ]
116 |
117 | [package.dev-dependencies]
118 | dev = [
119 | { name = "autoflake" },
120 | { name = "black" },
121 | { name = "isort" },
122 | { name = "mypy" },
123 | { name = "ruff" },
124 | ]
125 |
126 | [package.metadata]
127 | requires-dist = [
128 | { name = "httpx", specifier = ">=0.25.2,<1.0.0" },
129 | { name = "pydantic", specifier = ">=2.5.2,<3.0.0" },
130 | { name = "pydantic-settings", specifier = ">=2.1.0,<3.0.0" },
131 | { name = "python-telegram-bot", specifier = ">=20.7,<21.0" },
132 | { name = "rich", specifier = ">=13.7.0,<14.0.0" },
133 | ]
134 |
135 | [package.metadata.requires-dev]
136 | dev = [
137 | { name = "autoflake", specifier = ">=2.2.1,<3.0.0" },
138 | { name = "black", specifier = ">=23.12.0,<24.0.0" },
139 | { name = "isort", specifier = ">=5.13.2,<6.0.0" },
140 | { name = "mypy", specifier = ">=1.7.1,<2.0.0" },
141 | { name = "ruff", specifier = ">=0.1.8,<1.0.0" },
142 | ]
143 |
144 | [[package]]
145 | name = "exceptiongroup"
146 | version = "1.2.2"
147 | source = { registry = "https://pypi.org/simple" }
148 | sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
149 | wheels = [
150 | { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
151 | ]
152 |
153 | [[package]]
154 | name = "h11"
155 | version = "0.14.0"
156 | source = { registry = "https://pypi.org/simple" }
157 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
158 | wheels = [
159 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
160 | ]
161 |
162 | [[package]]
163 | name = "httpcore"
164 | version = "1.0.6"
165 | source = { registry = "https://pypi.org/simple" }
166 | dependencies = [
167 | { name = "certifi" },
168 | { name = "h11" },
169 | ]
170 | sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 }
171 | wheels = [
172 | { url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 },
173 | ]
174 |
175 | [[package]]
176 | name = "httpx"
177 | version = "0.26.0"
178 | source = { registry = "https://pypi.org/simple" }
179 | dependencies = [
180 | { name = "anyio" },
181 | { name = "certifi" },
182 | { name = "httpcore" },
183 | { name = "idna" },
184 | { name = "sniffio" },
185 | ]
186 | sdist = { url = "https://files.pythonhosted.org/packages/bd/26/2dc654950920f499bd062a211071925533f821ccdca04fa0c2fd914d5d06/httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf", size = 125671 }
187 | wheels = [
188 | { url = "https://files.pythonhosted.org/packages/39/9b/4937d841aee9c2c8102d9a4eeb800c7dad25386caabb4a1bf5010df81a57/httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd", size = 75862 },
189 | ]
190 |
191 | [[package]]
192 | name = "idna"
193 | version = "3.10"
194 | source = { registry = "https://pypi.org/simple" }
195 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
196 | wheels = [
197 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
198 | ]
199 |
200 | [[package]]
201 | name = "isort"
202 | version = "5.13.2"
203 | source = { registry = "https://pypi.org/simple" }
204 | sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 }
205 | wheels = [
206 | { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 },
207 | ]
208 |
209 | [[package]]
210 | name = "markdown-it-py"
211 | version = "3.0.0"
212 | source = { registry = "https://pypi.org/simple" }
213 | dependencies = [
214 | { name = "mdurl" },
215 | ]
216 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
217 | wheels = [
218 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
219 | ]
220 |
221 | [[package]]
222 | name = "mdurl"
223 | version = "0.1.2"
224 | source = { registry = "https://pypi.org/simple" }
225 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
226 | wheels = [
227 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
228 | ]
229 |
230 | [[package]]
231 | name = "mypy"
232 | version = "1.11.2"
233 | source = { registry = "https://pypi.org/simple" }
234 | dependencies = [
235 | { name = "mypy-extensions" },
236 | { name = "tomli", marker = "python_full_version < '3.11'" },
237 | { name = "typing-extensions" },
238 | ]
239 | sdist = { url = "https://files.pythonhosted.org/packages/5c/86/5d7cbc4974fd564550b80fbb8103c05501ea11aa7835edf3351d90095896/mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", size = 3078806 }
240 | wheels = [
241 | { url = "https://files.pythonhosted.org/packages/78/cd/815368cd83c3a31873e5e55b317551500b12f2d1d7549720632f32630333/mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", size = 10939401 },
242 | { url = "https://files.pythonhosted.org/packages/f1/27/e18c93a195d2fad75eb96e1f1cbc431842c332e8eba2e2b77eaf7313c6b7/mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", size = 10111697 },
243 | { url = "https://files.pythonhosted.org/packages/dc/08/cdc1fc6d0d5a67d354741344cc4aa7d53f7128902ebcbe699ddd4f15a61c/mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", size = 12500508 },
244 | { url = "https://files.pythonhosted.org/packages/64/12/aad3af008c92c2d5d0720ea3b6674ba94a98cdb86888d389acdb5f218c30/mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", size = 13020712 },
245 | { url = "https://files.pythonhosted.org/packages/03/e6/a7d97cc124a565be5e9b7d5c2a6ebf082379ffba99646e4863ed5bbcb3c3/mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", size = 9567319 },
246 | { url = "https://files.pythonhosted.org/packages/e2/aa/cc56fb53ebe14c64f1fe91d32d838d6f4db948b9494e200d2f61b820b85d/mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", size = 10859630 },
247 | { url = "https://files.pythonhosted.org/packages/04/c8/b19a760fab491c22c51975cf74e3d253b8c8ce2be7afaa2490fbf95a8c59/mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", size = 10037973 },
248 | { url = "https://files.pythonhosted.org/packages/88/57/7e7e39f2619c8f74a22efb9a4c4eff32b09d3798335625a124436d121d89/mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", size = 12416659 },
249 | { url = "https://files.pythonhosted.org/packages/fc/a6/37f7544666b63a27e46c48f49caeee388bf3ce95f9c570eb5cfba5234405/mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", size = 12897010 },
250 | { url = "https://files.pythonhosted.org/packages/84/8b/459a513badc4d34acb31c736a0101c22d2bd0697b969796ad93294165cfb/mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", size = 9562873 },
251 | { url = "https://files.pythonhosted.org/packages/35/3a/ed7b12ecc3f6db2f664ccf85cb2e004d3e90bec928e9d7be6aa2f16b7cdf/mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", size = 10990335 },
252 | { url = "https://files.pythonhosted.org/packages/04/e4/1a9051e2ef10296d206519f1df13d2cc896aea39e8683302f89bf5792a59/mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", size = 10007119 },
253 | { url = "https://files.pythonhosted.org/packages/f3/3c/350a9da895f8a7e87ade0028b962be0252d152e0c2fbaafa6f0658b4d0d4/mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", size = 12506856 },
254 | { url = "https://files.pythonhosted.org/packages/b6/49/ee5adf6a49ff13f4202d949544d3d08abb0ea1f3e7f2a6d5b4c10ba0360a/mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", size = 12952066 },
255 | { url = "https://files.pythonhosted.org/packages/27/c0/b19d709a42b24004d720db37446a42abadf844d5c46a2c442e2a074d70d9/mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", size = 9664000 },
256 | { url = "https://files.pythonhosted.org/packages/42/3a/bdf730640ac523229dd6578e8a581795720a9321399de494374afc437ec5/mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", size = 2619625 },
257 | ]
258 |
259 | [[package]]
260 | name = "mypy-extensions"
261 | version = "1.0.0"
262 | source = { registry = "https://pypi.org/simple" }
263 | sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
264 | wheels = [
265 | { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
266 | ]
267 |
268 | [[package]]
269 | name = "packaging"
270 | version = "24.1"
271 | source = { registry = "https://pypi.org/simple" }
272 | sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 }
273 | wheels = [
274 | { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 },
275 | ]
276 |
277 | [[package]]
278 | name = "pathspec"
279 | version = "0.12.1"
280 | source = { registry = "https://pypi.org/simple" }
281 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
282 | wheels = [
283 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
284 | ]
285 |
286 | [[package]]
287 | name = "platformdirs"
288 | version = "4.3.6"
289 | source = { registry = "https://pypi.org/simple" }
290 | sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 }
291 | wheels = [
292 | { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 },
293 | ]
294 |
295 | [[package]]
296 | name = "pydantic"
297 | version = "2.9.2"
298 | source = { registry = "https://pypi.org/simple" }
299 | dependencies = [
300 | { name = "annotated-types" },
301 | { name = "pydantic-core" },
302 | { name = "typing-extensions" },
303 | ]
304 | sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 }
305 | wheels = [
306 | { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 },
307 | ]
308 |
309 | [[package]]
310 | name = "pydantic-core"
311 | version = "2.23.4"
312 | source = { registry = "https://pypi.org/simple" }
313 | dependencies = [
314 | { name = "typing-extensions" },
315 | ]
316 | sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
317 | wheels = [
318 | { url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 },
319 | { url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 },
320 | { url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 },
321 | { url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 },
322 | { url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 },
323 | { url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 },
324 | { url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 },
325 | { url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 },
326 | { url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 },
327 | { url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 },
328 | { url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 },
329 | { url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 },
330 | { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 },
331 | { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 },
332 | { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 },
333 | { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 },
334 | { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 },
335 | { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 },
336 | { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 },
337 | { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 },
338 | { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 },
339 | { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 },
340 | { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 },
341 | { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 },
342 | { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 },
343 | { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 },
344 | { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 },
345 | { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 },
346 | { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 },
347 | { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 },
348 | { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 },
349 | { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 },
350 | { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 },
351 | { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 },
352 | { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 },
353 | { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 },
354 | { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 },
355 | { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 },
356 | { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 },
357 | { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 },
358 | { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 },
359 | { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 },
360 | { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 },
361 | { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 },
362 | { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 },
363 | { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 },
364 | { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 },
365 | { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 },
366 | { url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 },
367 | { url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 },
368 | { url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 },
369 | { url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 },
370 | { url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 },
371 | { url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 },
372 | { url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 },
373 | { url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 },
374 | ]
375 |
376 | [[package]]
377 | name = "pydantic-settings"
378 | version = "2.5.2"
379 | source = { registry = "https://pypi.org/simple" }
380 | dependencies = [
381 | { name = "pydantic" },
382 | { name = "python-dotenv" },
383 | ]
384 | sdist = { url = "https://files.pythonhosted.org/packages/68/27/0bed9dd26b93328b60a1402febc780e7be72b42847fa8b5c94b7d0aeb6d1/pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0", size = 70938 }
385 | wheels = [
386 | { url = "https://files.pythonhosted.org/packages/29/8d/29e82e333f32d9e2051c10764b906c2a6cd140992910b5f49762790911ba/pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907", size = 26864 },
387 | ]
388 |
389 | [[package]]
390 | name = "pyflakes"
391 | version = "3.2.0"
392 | source = { registry = "https://pypi.org/simple" }
393 | sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788 }
394 | wheels = [
395 | { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725 },
396 | ]
397 |
398 | [[package]]
399 | name = "pygments"
400 | version = "2.18.0"
401 | source = { registry = "https://pypi.org/simple" }
402 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
403 | wheels = [
404 | { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
405 | ]
406 |
407 | [[package]]
408 | name = "python-dotenv"
409 | version = "1.0.1"
410 | source = { registry = "https://pypi.org/simple" }
411 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
412 | wheels = [
413 | { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
414 | ]
415 |
416 | [[package]]
417 | name = "python-telegram-bot"
418 | version = "20.8"
419 | source = { registry = "https://pypi.org/simple" }
420 | dependencies = [
421 | { name = "httpx" },
422 | ]
423 | sdist = { url = "https://files.pythonhosted.org/packages/38/4c/90e0cee1ad7525d4009ae300219c6ee553aedc38cce59c8deb5dffb1859d/python-telegram-bot-20.8.tar.gz", hash = "sha256:0e1e4a6dbce3f4ba606990d66467a5a2d2018368fe44756fae07410a74e960dc", size = 407744 }
424 | wheels = [
425 | { url = "https://files.pythonhosted.org/packages/6f/8e/4e4ed06986557fce0c41c3dfc60c5495b1095cf8a552bdc4c56e96aefdac/python_telegram_bot-20.8-py3-none-any.whl", hash = "sha256:a98ddf2f237d6584b03a2f8b20553e1b5e02c8d3a1ea8e17fd06cc955af78c14", size = 604866 },
426 | ]
427 |
428 | [[package]]
429 | name = "rich"
430 | version = "13.9.1"
431 | source = { registry = "https://pypi.org/simple" }
432 | dependencies = [
433 | { name = "markdown-it-py" },
434 | { name = "pygments" },
435 | { name = "typing-extensions", marker = "python_full_version < '3.11'" },
436 | ]
437 | sdist = { url = "https://files.pythonhosted.org/packages/b3/78/87d00a1df7c457ad9aa0139f01b8a11c67209f27f927c503b0109bf2ed6c/rich-13.9.1.tar.gz", hash = "sha256:097cffdf85db1babe30cc7deba5ab3a29e1b9885047dab24c57e9a7f8a9c1466", size = 222907 }
438 | wheels = [
439 | { url = "https://files.pythonhosted.org/packages/ab/71/cd9549551f1aa11cf7e5f92bae5817979e8b3a19e31e8810c15f3f45c311/rich-13.9.1-py3-none-any.whl", hash = "sha256:b340e739f30aa58921dc477b8adaa9ecdb7cecc217be01d93730ee1bc8aa83be", size = 242147 },
440 | ]
441 |
442 | [[package]]
443 | name = "ruff"
444 | version = "0.6.8"
445 | source = { registry = "https://pypi.org/simple" }
446 | sdist = { url = "https://files.pythonhosted.org/packages/74/f9/4ce3e765a72ab8fe0f80f48508ea38b4196daab3da14d803c21349b2d367/ruff-0.6.8.tar.gz", hash = "sha256:a5bf44b1aa0adaf6d9d20f86162b34f7c593bfedabc51239953e446aefc8ce18", size = 3084543 }
447 | wheels = [
448 | { url = "https://files.pythonhosted.org/packages/db/07/42ee57e8b76ca585297a663a552b4f6d6a99372ca47fdc2276ef72cc0f2f/ruff-0.6.8-py3-none-linux_armv6l.whl", hash = "sha256:77944bca110ff0a43b768f05a529fecd0706aac7bcce36d7f1eeb4cbfca5f0f2", size = 10404327 },
449 | { url = "https://files.pythonhosted.org/packages/eb/51/d42571ff8156d65086acb72d39aa64cb24181db53b497d0ed6293f43f07a/ruff-0.6.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27b87e1801e786cd6ede4ada3faa5e254ce774de835e6723fd94551464c56b8c", size = 10018797 },
450 | { url = "https://files.pythonhosted.org/packages/c1/d7/fa5514a60b03976af972b67fe345deb0335dc96b9f9a9fa4df9890472427/ruff-0.6.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd48f945da2a6334f1793d7f701725a76ba93bf3d73c36f6b21fb04d5338dcf5", size = 9691303 },
451 | { url = "https://files.pythonhosted.org/packages/d6/c4/d812a74976927e51d0782a47539069657ac78535779bfa4d061c4fc8d89d/ruff-0.6.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:677e03c00f37c66cea033274295a983c7c546edea5043d0c798833adf4cf4c6f", size = 10719452 },
452 | { url = "https://files.pythonhosted.org/packages/ec/b6/aa700c4ae6db9b3ee660e23f3c7db596e2b16a3034b797704fba33ddbc96/ruff-0.6.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f1476236b3eacfacfc0f66aa9e6cd39f2a624cb73ea99189556015f27c0bdeb", size = 10161353 },
453 | { url = "https://files.pythonhosted.org/packages/ea/39/0b10075ffcd52ff3a581b9b69eac53579deb230aad300ce8f9d0b58e77bc/ruff-0.6.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f5a2f17c7d32991169195d52a04c95b256378bbf0de8cb98478351eb70d526f", size = 10980630 },
454 | { url = "https://files.pythonhosted.org/packages/c1/af/9eb9efc98334f62652e2f9318f137b2667187851911fac3b395365a83708/ruff-0.6.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5fd0d4b7b1457c49e435ee1e437900ced9b35cb8dc5178921dfb7d98d65a08d0", size = 11768996 },
455 | { url = "https://files.pythonhosted.org/packages/e0/59/8b1369cf7878358952b1c0a1559b4d6b5c824c003d09b0db26d26c9d094f/ruff-0.6.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8034b19b993e9601f2ddf2c517451e17a6ab5cdb1c13fdff50c1442a7171d87", size = 11317469 },
456 | { url = "https://files.pythonhosted.org/packages/b9/6d/e252e9b11bbca4114c386ee41ad559d0dac13246201d77ea1223c6fea17f/ruff-0.6.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cfb227b932ba8ef6e56c9f875d987973cd5e35bc5d05f5abf045af78ad8e098", size = 12467185 },
457 | { url = "https://files.pythonhosted.org/packages/48/44/7caa223af7d4ea0f0b2bd34acca65a7694a58317714675a2478815ab3f45/ruff-0.6.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef0411eccfc3909269fed47c61ffebdcb84a04504bafa6b6df9b85c27e813b0", size = 10887766 },
458 | { url = "https://files.pythonhosted.org/packages/81/ed/394aff3a785f171869158b9d5be61eec9ffb823c3ad5d2bdf2e5f13cb029/ruff-0.6.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:007dee844738c3d2e6c24ab5bc7d43c99ba3e1943bd2d95d598582e9c1b27750", size = 10711609 },
459 | { url = "https://files.pythonhosted.org/packages/47/31/f31d04c842e54699eab7e3b864538fea26e6c94b71806cd10aa49f13e1c1/ruff-0.6.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ce60058d3cdd8490e5e5471ef086b3f1e90ab872b548814e35930e21d848c9ce", size = 10237621 },
460 | { url = "https://files.pythonhosted.org/packages/20/95/a764e84acf11d425f2f23b8b78b4fd715e9c20be4aac157c6414ca859a67/ruff-0.6.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1085c455d1b3fdb8021ad534379c60353b81ba079712bce7a900e834859182fa", size = 10558329 },
461 | { url = "https://files.pythonhosted.org/packages/2a/76/d4e38846ac9f6dd62dce858a54583911361b5339dcf8f84419241efac93a/ruff-0.6.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:70edf6a93b19481affd287d696d9e311388d808671bc209fb8907b46a8c3af44", size = 10954102 },
462 | { url = "https://files.pythonhosted.org/packages/e7/36/f18c678da6c69f8d022480f3e8ddce6e4a52e07602c1d212056fbd234f8f/ruff-0.6.8-py3-none-win32.whl", hash = "sha256:792213f7be25316f9b46b854df80a77e0da87ec66691e8f012f887b4a671ab5a", size = 8511090 },
463 | { url = "https://files.pythonhosted.org/packages/4c/c4/0ca7d8ffa358b109db7d7d045a1a076fd8e5d9cbeae022242d3c060931da/ruff-0.6.8-py3-none-win_amd64.whl", hash = "sha256:ec0517dc0f37cad14a5319ba7bba6e7e339d03fbf967a6d69b0907d61be7a263", size = 9350079 },
464 | { url = "https://files.pythonhosted.org/packages/d9/bd/a8b0c64945a92eaeeb8d0283f27a726a776a1c9d12734d990c5fc7a1278c/ruff-0.6.8-py3-none-win_arm64.whl", hash = "sha256:8d3bb2e3fbb9875172119021a13eed38849e762499e3cfde9588e4b4d70968dc", size = 8669595 },
465 | ]
466 |
467 | [[package]]
468 | name = "sniffio"
469 | version = "1.3.1"
470 | source = { registry = "https://pypi.org/simple" }
471 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
472 | wheels = [
473 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
474 | ]
475 |
476 | [[package]]
477 | name = "tomli"
478 | version = "2.0.1"
479 | source = { registry = "https://pypi.org/simple" }
480 | sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 }
481 | wheels = [
482 | { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 },
483 | ]
484 |
485 | [[package]]
486 | name = "typing-extensions"
487 | version = "4.12.2"
488 | source = { registry = "https://pypi.org/simple" }
489 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
490 | wheels = [
491 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
492 | ]
493 |
--------------------------------------------------------------------------------
/whitelist.txt:
--------------------------------------------------------------------------------
1 | assorted.downloads.oppo.com/
2 | bkt-sgp-miui-ota-update-alisgp.oss-ap-southeast-1.aliyuncs.com/
3 | dl.google.com/
4 | d.miui.com/
5 | gauss-componentotacostmanual-sg.allawnofs.com/
6 | gauss-otacostmanual-cn.allawnfs.com/
7 | lolinet.com/
8 | mediafire.com/
9 | pixeldrain.com/
10 | rms01.realme.net/
11 | sourceforge.net/
12 | update.hihonorcdn.com/
--------------------------------------------------------------------------------