diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000000000000000000000000000000000000..deb192e192a797eea84af5d218278b0d1362fa4f --- /dev/null +++ b/.env.sample @@ -0,0 +1,13 @@ +# Don't use quotes( " and ' ) + +API_ID= +API_HASH= +SESSION= + + +# [OPTIONAL] + +REDIS_URI= +REDIS_PASSWORD= +LOG_CHANNEL= +BOT_TOKEN= diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..34747176da01076da3a3c44ab91bf0e9456c5e49 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.otf filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 2f46cf0de15119c6dd2c807e13697801f3fe5cf4..ce5d8ae1e0e50fb836a0b1f846cf923f4e17a60f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,47 @@ -node_modules/ +.env +*.session-journal +*.session +build +test* +*.mp3 +*.webm +*.webp +*.mp4 +*.tgs +*.txt +/*.jpg +/*.png +/*.mp4 +*.log +target/npmlist.json +package-lock.json +ultroid.json +resources/extras/thumbnail.jpg +resources/auth/ -# Keep-alive secrets — never commit real tokens -.keep-alive.env +# Directories +addons/ +vcbot/ +__pycache__/ +venv/ +node_modules/ +glitch_me/ +src/glitch-me +.idea/ +.vscode/ +temp/ +bin-debug/ +bin-release/ +[Oo]bj/ +[Bb]in/ +.settings/ +*.swf +*.air +*.ipa +*.apk -# Runtime logs -keep-alive.log +# temporary files +*.raw -# Devin for Terminal tool config (not part of the project) -.devin/ -*.tgz +# fly.io configs +fly.toml \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..fabd8716071171936b76c41aca01f38437559ab0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +teamultroid@ultroid.tech. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..a69d36986aa278af32dd2c262b039c5f9b358072 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Welcome To Contributing Guide +Any contribution done to this repository will be appreciated! 🎉 + +### About Ultroid +- **Ultroid** is Telegram Userbot, which allow you to perform many task easily. +- It's base core is a pip package named [`pyUltroid`](https://GitHub.com/TeamUltroid/pyUltroid). +- This repository, contains Ultroid's official plugin files. +- moreover, Ultroid User also have an option to get more plugins available at [`UltroidAddons`](https://github.com/TeamUltroid/UltroidAddons) + +### Issues +- Search for existing Similar issues. +- You can open a issue first before contributing, If it contain some major changes like `bug fixes` or `feature addition`. +- If you are busy or getting trouble while making changes, you can just open the issue (explaining it). + +### Contributing +- You can [fork](https://github.com/TeamUltroid/Ultroid/fork) this repository. +- You can commits your changes now. +- You can [Git-Squash](https://docs.github.com/en/get-started/using-git/about-git-rebase) your commits, if they are more. [Optional] +- At last, You are ready to [`make a Pull Request`](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). +- Make sure that, your `Pull Request` contain an explaining title and issue number if it closes any open issue. + +### Thanks +- Congrats! 💫 you have successfully made your Contribution to Ultroid. diff --git a/Dockerfile b/Dockerfile index 550d01a6b4b83e5201df8d9f1fa097457b5bb606..b1776a9b17e8c3a0cf1c66a0aa7a5e3977cea667 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,141 +1,20 @@ -# ── Base image ──────────────────────────────────────────────────────────────── -FROM node:20-slim +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in . +FROM theteamultroid/ultroid:main -# ── System dependencies ─────────────────────────────────────────────────────── -RUN apt-get update && apt-get upgrade -y && \ - apt-get install -y --no-install-recommends \ - python3 \ - python3-pip \ - python3-venv \ - make \ - g++ \ - wget \ - curl \ - git \ - neofetch \ - mediainfo \ - screen \ - nano \ - rsync \ - && rm -rf /var/lib/apt/lists/* +# set timezone +ENV TZ=Asia/Kolkata +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +COPY installer.sh . -# ── Generate a unique 64-char Machine ID ───────────────────────────────────── -# This creates a valid, random 64-character hex string (SHA-256 format) -RUN mkdir -p /var/lib/dbus && \ - head -c 512 /dev/urandom | sha256sum | cut -d' ' -f1 > /etc/machine-id && \ - cp /etc/machine-id /var/lib/dbus/machine-id +RUN bash installer.sh +# changing workdir +WORKDIR "/root/TeamUltroid" -USER root - - -# ── Set HOME and PATH ───────────────────────────────────────────────────────── -ENV HOME=/root \ - PATH="/root/venv/bin:/usr/local/bin:${PATH}" \ - VIRTUAL_ENV=/root/venv \ - PIP_NO_CACHE_DIR=1 \ - HOSTNAME=kanha - - -# ── Install shellular globally ──────────────────────────────────────────────── -RUN npm install -g --prefix /usr/local shellular - - -# ── Create Python venv ──────────────────────────────────────────────────────── -RUN python3 -m venv /root/venv && \ - /root/venv/bin/pip install --upgrade pip && \ - /root/venv/bin/pip install huggingface_hub && \ - rm -f /root/venv/lib/python*/EXTERNALLY-MANAGED - - -# ── Shell config ────────────────────────────────────────────────────────────── -RUN echo 'export PS1="\u@kanha:\w\$ "' >> /root/.bashrc && \ - echo 'cd /data' >> /root/.bashrc && \ - echo 'cd /data' >> /root/.bash_profile - - -# ── Force pip/python → always use /root/venv ───────────────────────────────── -RUN printf '%s\n' '#!/bin/sh' 'exec /root/venv/bin/pip "$@"' > /usr/local/bin/pip && chmod +x /usr/local/bin/pip && \ - printf '%s\n' '#!/bin/sh' 'exec /root/venv/bin/pip3 "$@"' > /usr/local/bin/pip3 && chmod +x /usr/local/bin/pip3 && \ - printf '%s\n' '#!/bin/sh' 'exec /root/venv/bin/python "$@"' > /usr/local/bin/python && chmod +x /usr/local/bin/python && \ - printf '%s\n' '#!/bin/sh' 'exec /root/venv/bin/python3 "$@"' > /usr/local/bin/python3 && chmod +x /usr/local/bin/python3 - - -# ── apt/apt-get wrapper → saves installed packages to /data/apt.txt ────────── -RUN printf '%s\n' \ - '#!/bin/sh' \ - 'APT_FILE="/data/apt.txt"' \ - 'REAL_BIN="$1"' \ - 'shift' \ - 'if [ "$1" = "install" ]; then' \ - ' shift' \ - ' "$REAL_BIN" install "$@"' \ - ' STATUS=$?' \ - ' if [ $STATUS -eq 0 ]; then' \ - ' touch "$APT_FILE"' \ - ' for arg in "$@"; do' \ - ' case "$arg" in -*) continue ;; esac' \ - ' if ! grep -qx "$arg" "$APT_FILE"; then' \ - ' echo "$arg" >> "$APT_FILE"' \ - ' fi' \ - ' done' \ - ' fi' \ - ' exit $STATUS' \ - 'fi' \ - '"$REAL_BIN" "$@"' \ - > /usr/local/bin/_apt_wrapper && chmod +x /usr/local/bin/_apt_wrapper - - -RUN printf '%s\n' '#!/bin/sh' 'exec /usr/local/bin/_apt_wrapper /usr/bin/apt "$@"' \ - > /usr/local/bin/apt && chmod +x /usr/local/bin/apt -RUN printf '%s\n' '#!/bin/sh' 'exec /usr/local/bin/_apt_wrapper /usr/bin/apt-get "$@"' \ - > /usr/local/bin/apt-get && chmod +x /usr/local/bin/apt-get - - -# ── Create entrypoint script ────────────────────────────────────────────────── -RUN printf '%s\n' \ - '#!/bin/sh' \ - 'set -e' \ - 'exec "$@"' \ - > /usr/local/bin/docker-entrypoint.sh && \ - chmod +x /usr/local/bin/docker-entrypoint.sh - - -# ── Ensure /data exists ─────────────────────────────────────────────────────── -RUN mkdir -p /data - - -# ── Git safe directory fix ──────────────────────────────────────────────────── -RUN git config --system --add safe.directory '*' - - -# ── App ─────────────────────────────────────────────────────────────────────── -# Note: Ensure package.json exists in your build context -COPY package*.json /root/app/ -RUN cd /root/app && npm install --omit=dev -COPY . /root/app/ - - -# ── Runtime ─────────────────────────────────────────────────────────────────── -WORKDIR /data -EXPOSE 7860 -ENV PORT=7860 -ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] - - -CMD ["bash", "-c", "\ - { \ - chown -R root:root /data 2>/dev/null; \ - chmod -R 755 /data 2>/dev/null; \ - git config --global --add safe.directory '*' 2>/dev/null; \ - if [ -f /data/apt.txt ]; then \ - apt-get update -qq 2>/dev/null; \ - while IFS= read -r pkg || [ -n \"$pkg\" ]; do \ - [ -z \"$pkg\" ] && continue; \ - apt-get install -y -qq \"$pkg\" 2>/dev/null || true; \ - done < /data/apt.txt; \ - fi \ - } & \ - exec node /root/app/app.js"] +# start the bot. +CMD ["bash", "startup"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..be3f7b28e564e7dd05eaf59d64adba1a4065ac0e --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/app.js b/app.js deleted file mode 100644 index 3e5fa3313644eeaf932616b815adfef83eae2428..0000000000000000000000000000000000000000 --- a/app.js +++ /dev/null @@ -1,306 +0,0 @@ -import express from 'express'; -import { spawn } from 'child_process'; -import crypto from 'crypto'; -import fs from 'fs'; -import os from 'os'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -const app = express(); -const PORT = process.env.PORT || 3000; -const SECRET_KEY = process.env.SECRET_KEY; - -if (!SECRET_KEY) { - console.error('[ERROR] SECRET_KEY environment variable is not set. Exiting.'); - process.exit(1); -} - -app.use(express.json()); -app.use(express.static(path.join(__dirname, 'public'))); - -const sessions = new Set(); - -function stripAnsi(str) { - return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g, ''); -} - -function seedShellularConfig() { - const hostId = process.env.SHELLULAR_HOST_ID; - const keyB64 = process.env.SHELLULAR_KEY; - const machineId = process.env.SHELLULAR_MACHINE_ID; - - if (!hostId || !keyB64 || !machineId) return; - - // Only seed if the saved SHELLULAR_MACHINE_ID matches the actual machine ID. - // If they differ (e.g. new build generated a different machine-id), skip seeding - // so shellular registers fresh rather than failing with "Machine ID mismatch". - const actualMachineId = getHashedMachineId(); - if (actualMachineId && actualMachineId !== machineId) { - console.warn(`[shellular] machine-id mismatch — skipping seed (set MACHINE_ID_RAW to fix)`); - return; - } - - const shellularDir = path.join(os.homedir(), '.shellular'); - const configFile = path.join(shellularDir, 'config.json'); - const keyFile = path.join(shellularDir, `shellular-${machineId}.e2ee`); - - try { - fs.mkdirSync(shellularDir, { recursive: true }); - if (!fs.existsSync(configFile)) { - fs.writeFileSync(configFile, JSON.stringify({ hostId, machineId }), 'utf-8'); - console.log(`[shellular] seeded config: hostId=${hostId}`); - } - if (!fs.existsSync(keyFile)) { - fs.writeFileSync(keyFile, Buffer.from(keyB64, 'base64'), { mode: 0o600 }); - console.log(`[shellular] seeded key: ${keyFile}`); - } - } catch (err) { - console.error('[shellular] failed to seed config:', err.message); - } -} - -function getHashedMachineId() { - try { - const raw = process.env.MACHINE_ID_RAW || - fs.readFileSync('/etc/machine-id', 'utf-8').trim(); - if (!raw) return null; - return crypto.createHash('sha256').update(raw).digest('hex'); - } catch { - return null; - } -} - -seedShellularConfig(); - -app.get('/api/shellular/machine-id', (_req, res) => { - const id = getHashedMachineId(); - id ? res.json({ machineId: id }) : res.status(500).json({ error: 'Cannot read machine-id' }); -}); - -app.get('/api/shellular/raw-machine-id', (_req, res) => { - try { - const raw = process.env.MACHINE_ID_RAW || - fs.readFileSync('/etc/machine-id', 'utf-8').trim(); - raw ? res.json({ raw }) : res.status(500).json({ error: 'Not available' }); - } catch { - res.status(500).json({ error: 'Not available' }); - } -}); - -app.post('/api/shellular/seed-host', requireAuth, (req, res) => { - const { hostId } = req.body || {}; - if (!hostId || typeof hostId !== 'string' || !hostId.trim()) { - return res.status(400).json({ error: 'hostId is required' }); - } - const machineId = getHashedMachineId(); - if (!machineId) return res.status(500).json({ error: 'Cannot read machine-id' }); - - try { - const shellularDir = path.join(os.homedir(), '.shellular'); - fs.mkdirSync(shellularDir, { recursive: true }); - fs.writeFileSync( - path.join(shellularDir, 'config.json'), - JSON.stringify({ hostId: hostId.trim(), machineId }, null, 2), - 'utf-8' - ); - stopShellular(); - outputBuffer = ''; - broadcast({ type: 'clear' }); - setTimeout(startShellular, 600); - res.json({ ok: true }); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -app.post('/api/login', (req, res) => { - const { key } = req.body; - if (!key || key !== SECRET_KEY) { - return res.status(401).json({ error: 'Invalid key' }); - } - const token = crypto.randomUUID(); - sessions.add(token); - res.json({ token }); -}); - -app.post('/api/logout', (req, res) => { - const token = req.headers.authorization?.split(' ')[1]; - sessions.delete(token); - res.json({ ok: true }); -}); - -function requireAuth(req, res, next) { - const token = req.headers.authorization?.split(' ')[1]; - if (!token || !sessions.has(token)) { - return res.status(401).json({ error: 'Unauthorized' }); - } - next(); -} - -let shellularProc = null; -let outputBuffer = ''; -let retryTimer = null; -const sseClients = new Set(); - -function send(res, payload) { - res.write(`data: ${JSON.stringify(payload)}\n\n`); -} - -function broadcast(payload) { - const frame = `data: ${JSON.stringify(payload)}\n\n`; - for (const client of sseClients) { - client.write(frame); - } -} - -function startShellular() { - if (shellularProc || retryTimer) return; - - broadcast({ type: 'status', status: 'starting' }); - - shellularProc = spawn('npx', ['shellular', '--unknown-clients', 'always-allow'], { - env: { ...process.env, FORCE_COLOR: '0' }, - stdio: ['ignore', 'pipe', 'pipe'], - }); - - let procOutput = ''; - - const handleData = (chunk) => { - const text = stripAnsi(chunk.toString()); - procOutput += text; - outputBuffer += text; - broadcast({ type: 'output', text }); - }; - - shellularProc.stdout.on('data', handleData); - shellularProc.stderr.on('data', handleData); - - shellularProc.on('error', (err) => { - const text = `\n[spawn error] ${err.message}\n`; - outputBuffer += text; - broadcast({ type: 'output', text }); - shellularProc = null; - broadcast({ type: 'status', status: 'error' }); - }); - - shellularProc.on('exit', (code, signal) => { - shellularProc = null; - - const isMachineIdMismatch = procOutput.includes('Machine ID mismatch'); - const isRegError = code === 1 && !signal && !isMachineIdMismatch && - (procOutput.includes('invalid_union') || procOutput.includes('Too many requests') || - procOutput.includes('host registration')); - - if (isRegError) { - const WAIT = 30; - const msg = `\n⚠ Registration rate-limited by shellular API.\n Retrying automatically in ${WAIT}s — please wait…\n`; - outputBuffer += msg; - broadcast({ type: 'output', text: msg }); - broadcast({ type: 'status', status: 'retrying' }); - - retryTimer = setTimeout(() => { - retryTimer = null; - const msg2 = '\n[Retrying registration…]\n'; - outputBuffer += msg2; - broadcast({ type: 'output', text: msg2 }); - startShellular(); - }, WAIT * 1000); - } else { - const text = code !== 0 - ? `\n[shellular exited — code=${code ?? '?'}, signal=${signal ?? 'none'}]\n` - : '\n[shellular disconnected]\n'; - outputBuffer += text; - broadcast({ type: 'output', text }); - broadcast({ type: 'status', status: 'stopped' }); - } - }); - - broadcast({ type: 'status', status: 'running' }); -} - -function stopShellular() { - if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; } - if (!shellularProc) return; - shellularProc.kill('SIGTERM'); - shellularProc = null; -} - -app.get('/api/stream', requireAuth, (req, res) => { - res.setHeader('Content-Type', 'text/event-stream'); - res.setHeader('Cache-Control', 'no-cache'); - res.setHeader('Connection', 'keep-alive'); - res.setHeader('X-Accel-Buffering', 'no'); - res.flushHeaders(); - - send(res, { type: 'status', status: shellularProc ? 'running' : 'stopped' }); - if (outputBuffer) send(res, { type: 'output', text: outputBuffer }); - - sseClients.add(res); - req.on('close', () => sseClients.delete(res)); -}); - -app.post('/api/shellular/start', requireAuth, (_req, res) => { - startShellular(); - res.json({ ok: true, running: !!shellularProc }); -}); - -app.post('/api/shellular/stop', requireAuth, (_req, res) => { - stopShellular(); - outputBuffer = ''; - broadcast({ type: 'output', text: '' }); - res.json({ ok: true }); -}); - -app.post('/api/shellular/restart', requireAuth, (_req, res) => { - stopShellular(); - outputBuffer = ''; - broadcast({ type: 'clear' }); - setTimeout(startShellular, 600); - res.json({ ok: true }); -}); - -app.get('/api/status', requireAuth, (_req, res) => { - res.json({ running: !!shellularProc }); -}); - -app.get('/api/setup-status', requireAuth, (_req, res) => { - const seeded = !!( - process.env.SHELLULAR_HOST_ID && - process.env.SHELLULAR_KEY && - process.env.SHELLULAR_MACHINE_ID && - process.env.MACHINE_ID_RAW - ); - res.json({ seeded }); -}); - -app.get('/api/shellular/credentials', requireAuth, (_req, res) => { - try { - const shellularDir = path.join(os.homedir(), '.shellular'); - const configRaw = fs.readFileSync(path.join(shellularDir, 'config.json'), 'utf-8'); - const { hostId, machineId } = JSON.parse(configRaw); - const keyFile = path.join(shellularDir, `shellular-${machineId}.e2ee`); - const keyB64 = fs.readFileSync(keyFile).toString('base64'); - res.json({ hostId, machineId, keyB64 }); - } catch { - res.status(404).json({ error: 'Not registered yet.' }); - } -}); - -app.get('/api/shellular/qr-data', requireAuth, (_req, res) => { - try { - const shellularDir = path.join(os.homedir(), '.shellular'); - const configRaw = fs.readFileSync(path.join(shellularDir, 'config.json'), 'utf-8'); - const { hostId, machineId } = JSON.parse(configRaw); - const keyFile = path.join(shellularDir, `shellular-${machineId}.e2ee`); - const keyB64 = fs.readFileSync(keyFile).toString('base64'); - res.json({ qrData: `${hostId}:${keyB64}` }); - } catch { - res.status(404).json({ error: 'Config not seeded yet.' }); - } -}); - -app.listen(PORT, '0.0.0.0', () => { - console.log(`Shellular Web UI → http://0.0.0.0:${PORT}`); -}); diff --git a/app.json b/app.json new file mode 100644 index 0000000000000000000000000000000000000000..d192badba483b4ed8a94535876f392c504624720 --- /dev/null +++ b/app.json @@ -0,0 +1,56 @@ +{ + "name": "Ultroid UserBot", + "description": "Pluggable telegram userbot, made in python using Telethon.", + "logo": "https://graph.org/file/031957757a4f6a5191040.jpg", + "keywords": [ + "Telethon", + "telegram", + "userbot", + "python", + "ultroid" + ], + "repository": "https://github.com/TeamUltroid/Ultroid", + "website": "https://ultroid.tech", + "success_url": "https://t.me/TeamUltroid", + "stack": "container", + "env": { + "API_ID": { + "description": "You api id, from my.telegram.org or @ScrapperRoBot.", + "value": "", + "required": false + }, + "API_HASH": { + "description": "You api hash, from my.telegram.org or @ScrapperRoBot.", + "value": "", + "required": false + }, + "SESSION": { + "description": "Session String (telethon or pyrogram) for your telegram user account. The userbot will NOT work without a session string!!", + "value": "" + }, + "REDIS_URI": { + "description": "Redis endpoint URL, from redislabs.com", + "value": "" + }, + "REDIS_PASSWORD": { + "description": "Redis endpoint password, from redislabs.com", + "value": "" + }, + "HEROKU_API": { + "description": "Heroku API token. Mandatory for Heroku Deploy...", + "value": "", + "required": false + }, + "HEROKU_APP_NAME": { + "description": "Name of your Heroku app, given in the first blank on this page. To be added if deploying to heroku ONLY.", + "value": "", + "required": false + } + }, + "formation": { + "ultroid": { + "quantity": 1, + "size": "eco" + } + } +} diff --git a/assistant/__init__.py b/assistant/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d80a174c4cd3ef7c04ec7e7ee627343e8df30505 --- /dev/null +++ b/assistant/__init__.py @@ -0,0 +1,34 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from telethon import Button, custom + +from plugins import ATRA_COL, InlinePlugin +from pyUltroid import * +from pyUltroid import _ult_cache +from pyUltroid._misc import owner_and_sudos +from pyUltroid._misc._assistant import asst_cmd, callback, in_pattern +from pyUltroid.fns.helper import * +from pyUltroid.fns.tools import get_stored_file +from strings import get_languages, get_string + +OWNER_NAME = ultroid_bot.full_name +OWNER_ID = ultroid_bot.uid + +AST_PLUGINS = {} + + +async def setit(event, name, value): + try: + udB.set_key(name, value) + except BaseException as er: + LOGS.exception(er) + return await event.edit("`Something Went Wrong`") + + +def get_back_button(name): + return [Button.inline("« Bᴀᴄᴋ", data=f"{name}")] diff --git a/assistant/callbackstuffs.py b/assistant/callbackstuffs.py new file mode 100644 index 0000000000000000000000000000000000000000..9568724ee9541784859c4999a8b6abf1cc450338 --- /dev/null +++ b/assistant/callbackstuffs.py @@ -0,0 +1,1318 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import ast +import asyncio +import re +import sys +import time +from asyncio.exceptions import TimeoutError as AsyncTimeOut +from os import execl, remove +from random import choice + +from bs4 import BeautifulSoup as bs + +try: + from pyUltroid.fns.gDrive import GDriveManager +except ImportError: + GDriveManager = None +from telethon import Button, events +from catbox import CatboxUploader +from telethon.tl.types import MessageMediaWebPage +from telethon.utils import get_peer_id + +from pyUltroid.fns.helper import fast_download, progress +from pyUltroid.fns.tools import Carbon, async_searcher, get_paste, telegraph_client +from pyUltroid.startup.loader import Loader + +from . import * + +# --------------------------------------------------------------------# +telegraph = telegraph_client() +GDrive = GDriveManager() if GDriveManager else None +uploader = CatboxUploader() +# --------------------------------------------------------------------# + +def text_to_url(event): + """function to get media url (with|without) Webpage""" + if isinstance(event.media, MessageMediaWebPage): + webpage = event.media.webpage + if not isinstance(webpage, types.WebPageEmpty) and webpage.type in ["photo"]: + return webpage.display_url + return event.text + + +# --------------------------------------------------------------------# + +_buttons = { + "otvars": { + "text": "Other Variables to set for @TeamUltroid:", + "buttons": [ + [ + Button.inline("Tᴀɢ Lᴏɢɢᴇʀ", data="taglog"), + Button.inline("SᴜᴘᴇʀFʙᴀɴ", data="cbs_sfban"), + ], + [ + Button.inline("Sudo Mode", data="sudo"), + Button.inline("Handler", data="hhndlr"), + ], + [ + Button.inline("Extra Plugins", data="plg"), + Button.inline("Addons", data="eaddon"), + ], + [ + Button.inline("Emoji in Help", data="emoj"), + Button.inline("Set gDrive", data="gdrive"), + ], + [ + Button.inline("Inline Pic", data="inli_pic"), + Button.inline("Sudo HNDLR", data="shndlr"), + ], + [Button.inline("Dual Mode", "cbs_oofdm")], + [Button.inline("« Back", data="setter")], + ], + }, + "sfban": { + "text": "SuperFban Settings:", + "buttons": [ + [Button.inline("FBᴀɴ Gʀᴏᴜᴘ", data="sfgrp")], + [Button.inline("Exᴄʟᴜᴅᴇ Fᴇᴅs", data="abs_sfexf")], + [Button.inline("« Bᴀᴄᴋ", data="cbs_otvars")], + ], + }, + "apauto": { + "text": "This'll auto approve on outgoing messages", + "buttons": [ + [Button.inline("Aᴜᴛᴏ Aᴘᴘʀᴏᴠᴇ ON", data="apon")], + [Button.inline("Aᴜᴛᴏ Aᴘᴘʀᴏᴠᴇ OFF", data="apof")], + [Button.inline("« Bᴀᴄᴋ", data="cbs_pmcstm")], + ], + }, + "alvcstm": { + "text": f"Customise your {HNDLR}alive. Choose from the below options -", + "buttons": [ + [Button.inline("Aʟɪᴠᴇ Tᴇxᴛ", data="abs_alvtx")], + [Button.inline("Aʟɪᴠᴇ ᴍᴇᴅɪᴀ", data="alvmed")], + [Button.inline("Dᴇʟᴇᴛᴇ Aʟɪᴠᴇ Mᴇᴅɪᴀ", data="delmed")], + [Button.inline("« Bᴀᴄᴋ", data="setter")], + ], + }, + "pmcstm": { + "text": "Customise your PMPERMIT Settings -", + "buttons": [ + [ + Button.inline("Pᴍ Tᴇxᴛ", data="pmtxt"), + Button.inline("Pᴍ Mᴇᴅɪᴀ", data="pmmed"), + ], + [ + Button.inline("Aᴜᴛᴏ Aᴘᴘʀᴏᴠᴇ", data="cbs_apauto"), + Button.inline("PMLOGGER", data="pml"), + ], + [ + Button.inline("Sᴇᴛ Wᴀʀɴs", data="swarn"), + Button.inline("Dᴇʟᴇᴛᴇ Pᴍ Mᴇᴅɪᴀ", data="delpmmed"), + ], + [Button.inline("PMPermit Type", data="cbs_pmtype")], + [Button.inline("« Bᴀᴄᴋ", data="cbs_ppmset")], + ], + }, + "pmtype": { + "text": "Select the type of PMPermit needed.", + "buttons": [ + [Button.inline("Inline", data="inpm_in")], + [Button.inline("Normal", data="inpm_no")], + [Button.inline("« Bᴀᴄᴋ", data="cbs_pmcstm")], + ], + }, + "ppmset": { + "text": "PMPermit Settings:", + "buttons": [ + [Button.inline("Tᴜʀɴ PMPᴇʀᴍɪᴛ Oɴ", data="pmon")], + [Button.inline("Tᴜʀɴ PMPᴇʀᴍɪᴛ Oғғ", data="pmoff")], + [Button.inline("Cᴜsᴛᴏᴍɪᴢᴇ PMPᴇʀᴍɪᴛ", data="cbs_pmcstm")], + [Button.inline("« Bᴀᴄᴋ", data="setter")], + ], + }, + "chatbot": { + "text": "From This Feature U can chat with ppls Via ur Assistant Bot.\n[More info](https://t.me/UltroidUpdates/2)", + "buttons": [ + [ + Button.inline("Cʜᴀᴛ Bᴏᴛ Oɴ", data="onchbot"), + Button.inline("Cʜᴀᴛ Bᴏᴛ Oғғ", data="ofchbot"), + ], + [ + Button.inline("Bᴏᴛ Wᴇʟᴄᴏᴍᴇ", data="bwel"), + Button.inline("Bᴏᴛ Wᴇʟᴄᴏᴍᴇ Mᴇᴅɪᴀ", data="botmew"), + ], + [Button.inline("Bᴏᴛ Iɴғᴏ Tᴇxᴛ", data="botinfe")], + [Button.inline("Fᴏʀᴄᴇ Sᴜʙsᴄʀɪʙᴇ", data="pmfs")], + [Button.inline("« Bᴀᴄᴋ", data="setter")], + ], + }, + "vcb": { + "text": "From This Feature U can play songs in group voice chat\n\n[moreinfo](https://t.me/UltroidUpdates/4)", + "buttons": [ + [Button.inline("VC Sᴇssɪᴏɴ", data="abs_vcs")], + [Button.inline("« Bᴀᴄᴋ", data="setter")], + ], + }, + "oofdm": { + "text": "About [Dual Mode](https://t.me/UltroidUpdates/18)", + "buttons": [ + [ + Button.inline("Dᴜᴀʟ Mᴏᴅᴇ Oɴ", "dmof"), + Button.inline("Dᴜᴀʟ Mᴏᴅᴇ Oғғ", "dmof"), + ], + [Button.inline("Dᴜᴀʟ Mᴏᴅᴇ Hɴᴅʟʀ", "dmhn")], + [Button.inline("« Back", data="cbs_otvars")], + ], + }, + "apiset": { + "text": get_string("ast_1"), + "buttons": [ + [Button.inline("Remove.bg API", data="abs_rmbg")], + [Button.inline("DEEP API", data="abs_dapi")], + [Button.inline("OCR API", data="abs_oapi")], + [Button.inline("« Back", data="setter")], + ], + }, +} + +_convo = { + "rmbg": { + "var": "RMBG_API", + "name": "Remove.bg API Key", + "text": get_string("ast_2"), + "back": "cbs_apiset", + }, + "dapi": { + "var": "DEEP_AI", + "name": "Deep AI Api Key", + "text": "Get Your Deep Api from deepai.org and send here.", + "back": "cbs_apiset", + }, + "oapi": { + "var": "OCR_API", + "name": "Ocr Api Key", + "text": "Get Your OCR api from ocr.space and send that Here.", + "back": "cbs_apiset", + }, + "pmlgg": { + "var": "PMLOGGROUP", + "name": "Pm Log Group", + "text": "Send chat id of chat which you want to save as Pm log Group.", + "back": "pml", + }, + "vcs": { + "var": "VC_SESSION", + "name": "Vc Session", + "text": "**Vc session**\nEnter the New session u generated for vc bot.\n\nUse /cancel to terminate the operation.", + "back": "cbs_vcb", + }, + "settag": { + "var": "TAG_LOG", + "name": "Tag Log Group", + "text": f"Make a group, add your assistant and make it admin.\nGet the `{HNDLR}id` of that group and send it here for tag logs.\n\nUse /cancel to cancel.", + "back": "taglog", + }, + "alvtx": { + "var": "ALIVE_TEXT", + "name": "Alive Text", + "text": "**Alive Text**\nEnter the new alive text.\n\nUse /cancel to terminate the operation.", + "back": "cbs_alvcstm", + }, + "sfexf": { + "var": "EXCLUDE_FED", + "name": "Excluded Fed", + "text": "Send the Fed IDs you want to exclude in the ban. Split by a space.\neg`id1 id2 id3`\nSet is as `None` if you dont want any.\nUse /cancel to go back.", + "back": "cbs_sfban", + }, +} + + +TOKEN_FILE = "resources/auths/auth_token.txt" + + +@callback( + re.compile( + "sndplug_(.*)", + ), + owner=True, +) +async def send(eve): + key, name = (eve.data_match.group(1)).decode("UTF-8").split("_") + thumb = "resources/extras/inline.jpg" + await eve.answer("■ Sending ■") + data = f"uh_{key}_" + index = None + if "|" in name: + name, index = name.split("|") + key = "plugins" if key == "Official" else key.lower() + plugin = f"{key}/{name}.py" + _ = f"pasta-{plugin}" + if index is not None: + data += f"|{index}" + _ += f"|{index}" + buttons = [ + [ + Button.inline( + "« Pᴀsᴛᴇ »", + data=_, + ) + ], + [ + Button.inline("« Bᴀᴄᴋ", data=data), + ], + ] + try: + await eve.edit(file=plugin, thumb=thumb, buttons=buttons) + except Exception as er: + await eve.answer(str(er), alert=True) + + +heroku_api, app_name = Var.HEROKU_API, Var.HEROKU_APP_NAME + + +@callback("updatenow", owner=True) +async def update(eve): + repo = Repo() + ac_br = repo.active_branch + ups_rem = repo.remote("upstream") + if heroku_api: + import heroku3 + + try: + heroku = heroku3.from_key(heroku_api) + heroku_app = None + heroku_applications = heroku.apps() + except BaseException as er: + LOGS.exception(er) + return await eve.edit("`Wrong HEROKU_API.`") + for app in heroku_applications: + if app.name == app_name: + heroku_app = app + if not heroku_app: + await eve.edit("`Wrong HEROKU_APP_NAME.`") + repo.__del__() + return + await eve.edit(get_string("clst_1")) + ups_rem.fetch(ac_br) + repo.git.reset("--hard", "FETCH_HEAD") + heroku_git_url = heroku_app.git_url.replace( + "https://", f"https://api:{heroku_api}@" + ) + + if "heroku" in repo.remotes: + remote = repo.remote("heroku") + remote.set_url(heroku_git_url) + else: + remote = repo.create_remote("heroku", heroku_git_url) + try: + remote.push(refspec=f"HEAD:refs/heads/{ac_br}", force=True) + except GitCommandError as error: + await eve.edit(f"`Here is the error log:\n{error}`") + repo.__del__() + return + await eve.edit("`Successfully Updated!\nRestarting, please wait...`") + else: + await eve.edit(get_string("clst_1")) + call_back() + await bash("git pull && pip3 install -r requirements.txt") + await bash("pip3 install -r requirements.txt --break-system-packages") + execl(sys.executable, sys.executable, "-m", "pyUltroid") + +@callback(re.compile("changes(.*)"), owner=True) +async def changes(okk): + match = okk.data_match.group(1).decode("utf-8") + await okk.answer(get_string("clst_3")) + repo = Repo.init() + button = [[Button.inline("Update Now", data="updatenow")]] + changelog, tl_chnglog = await gen_chlog( + repo, f"HEAD..upstream/{repo.active_branch}" + ) + cli = "\n\nClick the below button to update!" + if not match: + try: + if len(tl_chnglog) > 700: + tl_chnglog = f"{tl_chnglog[:700]}..." + button.append([Button.inline("View Complete", "changesall")]) + await okk.edit("• Writing Changelogs 📝 •") + img = await Carbon( + file_name="changelog", + code=tl_chnglog, + backgroundColor=choice(ATRA_COL), + language="md", + ) + return await okk.edit( + f"**• Ultroid Userbot •**{cli}", file=img, buttons=button + ) + except Exception as er: + LOGS.exception(er) + changelog_str = changelog + cli + if len(changelog_str) > 1024: + await okk.edit(get_string("upd_4")) + await asyncio.sleep(2) + with open("ultroid_updates.txt", "w+") as file: + file.write(tl_chnglog) + await okk.edit( + get_string("upd_5"), + file="ultroid_updates.txt", + buttons=button, + ) + remove("ultroid_updates.txt") + return + await okk.edit( + changelog_str, + buttons=button, + parse_mode="html", + ) + + +@callback( + re.compile( + "pasta-(.*)", + ), + owner=True, +) +async def _(e): + ok = (e.data_match.group(1)).decode("UTF-8") + index = None + if "|" in ok: + ok, index = ok.split("|") + with open(ok, "r") as hmm: + _, data = await get_paste(hmm.read()) + if not data.get("link"): + return await e.answer(key[:30], alert=True) + if not key.startswith("http"): + link, raw = data["link"], data["raw"] + else: + link = key + raw = f"{key}/raw" + if ok.startswith("addons"): + key = "Addons" + elif ok.startswith("vcbot"): + key = "VCBot" + else: + key = "Official" + data = f"uh_{key}_" + if index is not None: + data += f"|{index}" + await e.edit( + "", + buttons=[ + [Button.url("Lɪɴᴋ", link), Button.url("Rᴀᴡ", raw)], + [Button.inline("« Bᴀᴄᴋ", data=data)], + ], + ) + + +@callback(re.compile("cbs_(.*)"), owner=True) +async def _edit_to(event): + match = event.data_match.group(1).decode("utf-8") + data = _buttons.get(match) + if not data: + return + await event.edit(data["text"], buttons=data["buttons"], link_preview=False) + + +@callback(re.compile("abs_(.*)"), owner=True) +async def convo_handler(event: events.CallbackQuery): + match = event.data_match.group(1).decode("utf-8") + if not _convo.get(match): + return + await event.delete() + get_ = _convo[match] + back = get_["back"] + async with event.client.conversation(event.sender_id) as conv: + await conv.send_message(get_["text"]) + response = await conv.get_response() + themssg = response.message + try: + themssg = ast.literal_eval(themssg) + except Exception: + pass + if themssg == "/cancel": + return await conv.send_message( + "Cancelled!!", + buttons=get_back_button(back), + ) + await setit(event, get_["var"], themssg) + await conv.send_message( + f"{get_['name']} changed to `{themssg}`", + buttons=get_back_button(back), + ) + + +@callback("authorise", owner=True) +async def _(e): + if not e.is_private: + return + url = GDrive._create_token_file() + await e.edit("Go to the below link and send the code!") + async with asst.conversation(e.sender_id) as conv: + await conv.send_message(url) + code = await conv.get_response() + if GDrive._create_token_file(code=code.text): + await conv.send_message( + "`Success!\nYou are all set to use Google Drive with Ultroid Userbot.`", + buttons=Button.inline("Main Menu", data="setter"), + ) + else: + await conv.send_message("Wrong code! Click authorise again.") + + +@callback("folderid", owner=True, func=lambda x: x.is_private) +async def _(e): + if not e.is_private: + return + msg = ( + "Send your FOLDER ID\n\n" + + "For FOLDER ID:\n" + + "1. Open Google Drive App.\n" + + "2. Create Folder.\n" + + "3. Make that folder public.\n" + + "4. Send link of that folder." + ) + await e.delete() + async with asst.conversation(e.sender_id, timeout=150) as conv: + await conv.send_message(msg) + repl = await conv.get_response() + id = repl.text + if id.startswith("https"): + id = id.split("?id=")[-1] + udB.set_key("GDRIVE_FOLDER_ID", id) + await repl.reply( + "`Success.`", + buttons=get_back_button("gdrive"), + ) + + +@callback("gdrive", owner=True) +async def _(e): + if not e.is_private: + return + await e.edit( + "Click Authorise and send the code.\n\nYou can use your own CLIENT ID and SECRET by [this](https://t.me/UltroidUpdates/37)", + buttons=[ + [ + Button.inline("Folder ID", data="folderid"), + Button.inline("Authorise", data="authorise"), + ], + [Button.inline("« Back", data="cbs_otvars")], + ], + link_preview=False, + ) + + +@callback("dmof", owner=True) +async def rhwhe(e): + if udB.get_key("DUAL_MODE"): + udB.del_key("DUAL_MODE") + key = "Off" + else: + udB.set_key("DUAL_MODE", "True") + key = "On" + Msg = f"Dual Mode : {key}" + await e.edit(Msg, buttons=get_back_button("cbs_otvars")) + + +@callback("dmhn", owner=True) +async def hndlrr(event): + await event.delete() + pru = event.sender_id + var = "DUAL_HNDLR" + name = "Dual Handler" + CH = udB.get_key(var) or "/" + async with event.client.conversation(pru) as conv: + await conv.send_message( + f"Send The Symbol Which u want as Handler/Trigger to use your Assistant bot\nUr Current Handler is [ `{CH}` ]\n\n use /cancel to cancel.", + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + await conv.send_message( + "Cancelled!!", + buttons=get_back_button("cbs_otvars"), + ) + elif len(themssg) > 1: + await conv.send_message( + "Incorrect Handler", + buttons=get_back_button("cbs_otvars"), + ) + else: + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}", + buttons=get_back_button("cbs_otvars"), + ) + + +@callback("emoj", owner=True) +async def emoji(event): + await event.delete() + pru = event.sender_id + var = "EMOJI_IN_HELP" + name = f"Emoji in `{HNDLR}help` menu" + async with event.client.conversation(pru) as conv: + await conv.send_message("Send emoji u want to set 🙃.\n\nUse /cancel to cancel.") + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + await conv.send_message( + "Cancelled!!", + buttons=get_back_button("cbs_otvars"), + ) + elif themssg.startswith(("/", HNDLR)): + await conv.send_message( + "Incorrect Emoji", + buttons=get_back_button("cbs_otvars"), + ) + else: + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}\n", + buttons=get_back_button("cbs_otvars"), + ) + + +@callback("plg", owner=True) +async def pluginch(event): + await event.delete() + pru = event.sender_id + var = "PLUGIN_CHANNEL" + name = "Plugin Channel" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "Send id or username of a channel from where u want to install all plugins\n\nOur Channel~ @ultroidplugins\n\nUse /cancel to cancel.", + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + await conv.send_message( + "Cancelled!!", + buttons=get_back_button("cbs_otvars"), + ) + elif themssg.startswith(("/", HNDLR)): + await conv.send_message( + "Incorrect channel", + buttons=get_back_button("cbs_otvars"), + ) + else: + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}\n After Setting All Things Do Restart", + buttons=get_back_button("cbs_otvars"), + ) + + +@callback("hhndlr", owner=True) +async def hndlrr(event): + await event.delete() + pru = event.sender_id + var = "HNDLR" + name = "Handler/ Trigger" + async with event.client.conversation(pru) as conv: + await conv.send_message( + f"Send The Symbol Which u want as Handler/Trigger to use bot\nUr Current Handler is [ `{HNDLR}` ]\n\n use /cancel to cancel.", + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + await conv.send_message( + "Cancelled!!", + buttons=get_back_button("cbs_otvars"), + ) + elif len(themssg) > 1: + await conv.send_message( + "Incorrect Handler", + buttons=get_back_button("cbs_otvars"), + ) + elif themssg.startswith(("/", "#", "@")): + await conv.send_message( + "This cannot be used as handler", + buttons=get_back_button("cbs_otvars"), + ) + else: + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}", + buttons=get_back_button("cbs_otvars"), + ) + + +@callback("shndlr", owner=True) +async def hndlrr(event): + await event.delete() + pru = event.sender_id + var = "SUDO_HNDLR" + name = "Sudo Handler" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "Send The Symbol Which u want as Sudo Handler/Trigger to use bot\n\n use /cancel to cancel." + ) + + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + await conv.send_message( + "Cancelled!!", + buttons=get_back_button("cbs_otvars"), + ) + elif len(themssg) > 1: + await conv.send_message( + "Incorrect Handler", + buttons=get_back_button("cbs_otvars"), + ) + elif themssg.startswith(("/", "#", "@")): + await conv.send_message( + "This cannot be used as handler", + buttons=get_back_button("cbs_otvars"), + ) + else: + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}", + buttons=get_back_button("cbs_otvars"), + ) + + +@callback("taglog", owner=True) +async def tagloggrr(e): + BUTTON = [ + [Button.inline("SET TAG LOG", data="abs_settag")], + [Button.inline("DELETE TAG LOG", data="deltag")], + get_back_button("cbs_otvars"), + ] + await e.edit( + "Choose Options", + buttons=BUTTON, + ) + + +@callback("deltag", owner=True) +async def _(e): + udB.del_key("TAG_LOG") + await e.answer("Done!!! Tag Logger has been turned Off") + + +@callback("eaddon", owner=True) +async def pmset(event): + BT = ( + [Button.inline("Aᴅᴅᴏɴs Oғғ", data="edof")] + if udB.get_key("ADDONS") + else [Button.inline("Aᴅᴅᴏɴs Oɴ", data="edon")] + ) + + await event.edit( + "ADDONS~ Extra Plugins:", + buttons=[ + BT, + [Button.inline("« Bᴀᴄᴋ", data="cbs_otvars")], + ], + ) + + +@callback("edon", owner=True) +async def eddon(event): + var = "ADDONS" + await setit(event, var, "True") + await event.edit( + "Done! ADDONS has been turned on!!\n\n After Setting All Things Do Restart", + buttons=get_back_button("eaddon"), + ) + + +@callback("edof", owner=True) +async def eddof(event): + udB.set_key("ADDONS", "False") + await event.edit( + "Done! ADDONS has been turned off!! After Setting All Things Do Restart", + buttons=get_back_button("eaddon"), + ) + + +@callback("sudo", owner=True) +async def pmset(event): + BT = ( + [Button.inline("Sᴜᴅᴏ Mᴏᴅᴇ Oғғ", data="ofsudo")] + if udB.get_key("SUDO") + else [Button.inline("Sᴜᴅᴏ Mᴏᴅᴇ Oɴ", data="onsudo")] + ) + + await event.edit( + f"SUDO MODE ~ Some peoples can use ur Bot which u selected. To know More use `{HNDLR}help sudo`", + buttons=[ + BT, + [Button.inline("« Bᴀᴄᴋ", data="cbs_otvars")], + ], + ) + + +@callback("onsudo", owner=True) +async def eddon(event): + var = "SUDO" + await setit(event, var, "True") + await event.edit( + "Done! SUDO MODE has been turned on!!\n\n After Setting All Things Do Restart", + buttons=get_back_button("sudo"), + ) + + +@callback("ofsudo", owner=True) +async def eddof(event): + var = "SUDO" + await setit(event, var, "False") + await event.edit( + "Done! SUDO MODE has been turned off!! After Setting All Things Do Restart", + buttons=get_back_button("sudo"), + ) + + +@callback("sfgrp", owner=True) +async def sfgrp(event): + await event.delete() + name = "FBan Group ID" + var = "FBAN_GROUP_ID" + pru = event.sender_id + async with asst.conversation(pru) as conv: + await conv.send_message( + f"Make a group, add @MissRose_Bot, send `{HNDLR}id`, copy that and send it here.\nUse /cancel to go back.", + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message( + "Cancelled!!", + buttons=get_back_button("cbs_sfban"), + ) + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}", + buttons=get_back_button("cbs_sfban"), + ) + + +@callback("alvmed", owner=True) +async def media(event): + await event.delete() + pru = event.sender_id + var = "ALIVE_PIC" + name = "Alive Media" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "**Alive Media**\nSend me a pic/gif/media to set as alive media.\n\nUse /cancel to terminate the operation.", + ) + response = await conv.get_response() + try: + themssg = response.message + if themssg == "/cancel": + return await conv.send_message( + "Operation cancelled!!", + buttons=get_back_button("cbs_alvcstm"), + ) + except BaseException as er: + LOGS.exception(er) + if ( + not (response.text).startswith("/") + and response.text != "" + and (not response.media or isinstance(response.media, MessageMediaWebPage)) + ): + url = text_to_url(response) + elif response.sticker: + url = response.file.id + else: + media = await event.client.download_media(response, "alvpc") + try: + url = uploader.upload_file(media) + remove(media) + except BaseException as er: + LOGS.exception(er) + return await conv.send_message( + "Terminated.", + buttons=get_back_button("cbs_alvcstm"), + ) + await setit(event, var, url) + await conv.send_message( + f"{name} has been set.", + buttons=get_back_button("cbs_alvcstm"), + ) + + +@callback("delmed", owner=True) +async def dell(event): + try: + udB.del_key("ALIVE_PIC") + return await event.edit( + get_string("clst_5"), buttons=get_back_button("cbs_alabs_vcstm") + ) + except BaseException as er: + LOGS.exception(er) + return await event.edit( + get_string("clst_4"), + buttons=get_back_button("cbs_alabs_vcstm"), + ) + + +@callback("inpm_in", owner=True) +async def inl_on(event): + var = "INLINE_PM" + await setit(event, var, "True") + await event.edit( + "Done!! PMPermit type has been set to inline!", + buttons=[[Button.inline("« Bᴀᴄᴋ", data="cbs_pmtype")]], + ) + + +@callback("inpm_no", owner=True) +async def inl_on(event): + var = "INLINE_PM" + await setit(event, var, "False") + await event.edit( + "Done!! PMPermit type has been set to normal!", + buttons=[[Button.inline("« Bᴀᴄᴋ", data="cbs_pmtype")]], + ) + + +@callback("pmtxt", owner=True) +async def name(event): + await event.delete() + pru = event.sender_id + var = "PM_TEXT" + name = "PM Text" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "**PM Text**\nEnter the new Pmpermit text.\n\nu can use `{name}` `{fullname}` `{count}` `{mention}` `{username}` to get this from user Too\n\nUse /cancel to terminate the operation.", + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message( + "Cancelled!!", + buttons=get_back_button("cbs_pmcstm"), + ) + if len(themssg) > 4090: + return await conv.send_message( + "Message too long!\nGive a shorter message please!!", + buttons=get_back_button("cbs_pmcstm"), + ) + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}\n\nAfter Setting All Things Do restart", + buttons=get_back_button("cbs_pmcstm"), + ) + + +@callback("swarn", owner=True) +async def name(event): + m = range(1, 10) + tultd = [Button.inline(f"{x}", data=f"wrns_{x}") for x in m] + lst = list(zip(tultd[::3], tultd[1::3], tultd[2::3])) + lst.append([Button.inline("« Bᴀᴄᴋ", data="cbs_pmcstm")]) + await event.edit( + "Select the number of warnings for a user before getting blocked in PMs.", + buttons=lst, + ) + + +@callback(re.compile(b"wrns_(.*)"), owner=True) +async def set_wrns(event): + value = int(event.data_match.group(1).decode("UTF-8")) + if dn := udB.set_key("PMWARNS", value): + await event.edit( + f"PM Warns Set to {value}.\nNew users will have {value} chances in PMs before getting banned.", + buttons=get_back_button("cbs_pmcstm"), + ) + else: + await event.edit( + f"Something went wrong, please check your {HNDLR}logs!", + buttons=get_back_button("cbs_pmcstm"), + ) + + +@callback("pmmed", owner=True) +async def media(event): + await event.delete() + pru = event.sender_id + var = "PMPIC" + name = "PM Media" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "**PM Media**\nSend me a pic/gif/sticker/link to set as pmpermit media.\n\nUse /cancel to terminate the operation.", + ) + response = await conv.get_response() + try: + themssg = response.message + if themssg == "/cancel": + return await conv.send_message( + "Operation cancelled!!", + buttons=get_back_button("cbs_pmcstm"), + ) + except BaseException as er: + LOGS.exception(er) + media = await event.client.download_media(response, "pmpc") + if ( + not (response.text).startswith("/") + and response.text != "" + and (not response.media or isinstance(response.media, MessageMediaWebPage)) + ): + url = text_to_url(response) + elif response.sticker: + url = response.file.id + else: + try: + url = uploader.upload_file(media) + remove(media) + except BaseException as er: + LOGS.exception(er) + return await conv.send_message( + "Terminated.", + buttons=get_back_button("cbs_pmcstm"), + ) + await setit(event, var, url) + await conv.send_message( + f"{name} has been set.", + buttons=get_back_button("cbs_pmcstm"), + ) + + +@callback("delpmmed", owner=True) +async def dell(event): + try: + udB.del_key("PMPIC") + return await event.edit( + get_string("clst_5"), buttons=get_back_button("cbs_pmcstm") + ) + except BaseException as er: + LOGS.exception(er) + return await event.edit( + get_string("clst_4"), + buttons=[[Button.inline("« Sᴇᴛᴛɪɴɢs", data="setter")]], + ) + + +@callback("apon", owner=True) +async def apon(event): + var = "AUTOAPPROVE" + await setit(event, var, "True") + await event.edit( + "Done!! AUTOAPPROVE Started!!", + buttons=[[Button.inline("« Bᴀᴄᴋ", data="cbs_apauto")]], + ) + + +@callback("apof", owner=True) +async def apof(event): + try: + udB.set_key("AUTOAPPROVE", "False") + return await event.edit( + "Done! AUTOAPPROVE Stopped!!", + buttons=[[Button.inline("« Bᴀᴄᴋ", data="cbs_apauto")]], + ) + except BaseException as er: + LOGS.exception(er) + return await event.edit( + get_string("clst_4"), + buttons=[[Button.inline("« Sᴇᴛᴛɪɴɢs", data="setter")]], + ) + + +@callback("pml", owner=True) +async def l_vcs(event): + BT = ( + [Button.inline("PMLOGGER OFF", data="pmlogof")] + if udB.get_key("PMLOG") + else [Button.inline("PMLOGGER ON", data="pmlog")] + ) + + await event.edit( + "PMLOGGER This Will Forward Ur Pm to Ur Private Group -", + buttons=[ + BT, + [Button.inline("PᴍLᴏɢɢᴇʀ Gʀᴏᴜᴘ", "abs_pmlgg")], + [Button.inline("« Bᴀᴄᴋ", data="cbs_pmcstm")], + ], + ) + + +@callback("pmlog", owner=True) +async def pmlog(event): + await setit(event, "PMLOG", "True") + await event.edit( + "Done!! PMLOGGER Started!!", + buttons=[[Button.inline("« Bᴀᴄᴋ", data="pml")]], + ) + + +@callback("pmlogof", owner=True) +async def pmlogof(event): + try: + udB.del_key("PMLOG") + return await event.edit( + "Done! PMLOGGER Stopped!!", + buttons=[[Button.inline("« Bᴀᴄᴋ", data="pml")]], + ) + except BaseException as er: + LOGS.exception(er) + return await event.edit( + get_string("clst_4"), + buttons=[[Button.inline("« Sᴇᴛᴛɪɴɢs", data="setter")]], + ) + + +@callback("pmon", owner=True) +async def pmonn(event): + var = "PMSETTING" + await setit(event, var, "True") + await event.edit( + "Done! PMPermit has been turned on!!", + buttons=[[Button.inline("« Bᴀᴄᴋ", data="cbs_ppmset")]], + ) + + +@callback("pmoff", owner=True) +async def pmofff(event): + var = "PMSETTING" + await setit(event, var, "False") + await event.edit( + "Done! PMPermit has been turned off!!", + buttons=[[Button.inline("« Bᴀᴄᴋ", data="cbs_ppmset")]], + ) + + +@callback("botmew", owner=True) +async def hhh(e): + async with e.client.conversation(e.chat_id) as conv: + await conv.send_message("Send Any Media to keep at your Bot's welcome ") + msg = await conv.get_response() + if not msg.media or msg.text.startswith("/"): + return await conv.send_message( + "Terminated!", buttons=get_back_button("cbs_chatbot") + ) + udB.set_key("STARTMEDIA", msg.file.id) + await conv.send_message("Done!", buttons=get_back_button("cbs_chatbot")) + + +@callback("botinfe", owner=True) +async def hhh(e): + async with e.client.conversation(e.chat_id) as conv: + await conv.send_message( + "Send message to set to Display, when user Press Info button in Bot Welcome!\n\nsend `False` to completely remove that button.." + ) + msg = await conv.get_response() + if msg.media or msg.text.startswith("/"): + return await conv.send_message( + "Terminated!", buttons=get_back_button("cbs_chatbot") + ) + udB.set_key("BOT_INFO_START", msg.text) + await conv.send_message("Done!", buttons=get_back_button("cbs_chatbot")) + + +@callback("pmfs", owner=True) +async def heheh(event): + Ll = [] + err = "" + async with event.client.conversation(event.chat_id) as conv: + await conv.send_message( + "• Send The Chat Id(s), which you want user to Join Before using Chat/Pm Bot\n\n• Send /clear to disable PmBot Force sub..\n• • Send /cancel to stop this process.." + ) + await conv.send_message( + "Example : \n`-1001234567\n-100778888`\n\nFor Multiple Chat(s)." + ) + try: + msg = await conv.get_response() + except AsyncTimeOut: + return await conv.send_message("**• TimeUp!**\nStart from /start back.") + if not msg.text or msg.text.startswith("/"): + timyork = "Cancelled!" + if msg.text == "/clear": + udB.del_key("PMBOT_FSUB") + timyork = "Done! Force Subscribe Stopped\nRestart your Bot!" + return await conv.send_message( + "Cancelled!", buttons=get_back_button("cbs_chatbot") + ) + for chat in msg.message.split("\n"): + if chat.startswith("-") or chat.isdigit(): + chat = int(chat) + try: + CHSJSHS = await event.client.get_entity(chat) + Ll.append(get_peer_id(CHSJSHS)) + except Exception as er: + err += f"**{chat}** : {er}\n" + if err: + return await conv.send_message(err) + udB.set_key("PMBOT_FSUB", str(Ll)) + await conv.send_message( + "Done!\nRestart Your Bot.", buttons=get_back_button("cbs_chatbot") + ) + + +@callback("bwel", owner=True) +async def name(event): + await event.delete() + pru = event.sender_id + var = "STARTMSG" + name = "Bot Welcome Message:" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "**BOT WELCOME MSG**\nEnter the msg which u want to show when someone start your assistant Bot.\nYou Can use `{me}` , `{mention}` Parameters Too\nUse /cancel to terminate the operation.", + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message( + "Cancelled!!", + buttons=get_back_button("cbs_chatbot"), + ) + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}", + buttons=get_back_button("cbs_chatbot"), + ) + + +@callback("onchbot", owner=True) +async def chon(event): + var = "PMBOT" + await setit(event, var, "True") + Loader(path="assistant/pmbot.py", key="PM Bot").load() + if AST_PLUGINS.get("pmbot"): + for i, e in AST_PLUGINS["pmbot"]: + event.client.remove_event_handler(i) + for i, e in AST_PLUGINS["pmbot"]: + event.client.add_event_handler(i, events.NewMessage(**e)) + await event.edit( + "Done! Now u Can Chat With People Via This Bot", + buttons=[Button.inline("« Bᴀᴄᴋ", data="cbs_chatbot")], + ) + + +@callback("ofchbot", owner=True) +async def chon(event): + var = "PMBOT" + await setit(event, var, "False") + if AST_PLUGINS.get("pmbot"): + for i, e in AST_PLUGINS["pmbot"]: + event.client.remove_event_handler(i) + await event.edit( + "Done! Chat People Via This Bot Stopped.", + buttons=[Button.inline("« Bᴀᴄᴋ", data="cbs_chatbot")], + ) + + +@callback("inli_pic", owner=True) +async def media(event): + await event.delete() + pru = event.sender_id + var = "INLINE_PIC" + name = "Inline Media" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "**Inline Media**\nSend me a pic/gif/ or link to set as inline media.\n\nUse /cancel to terminate the operation.", + ) + response = await conv.get_response() + try: + themssg = response.message + if themssg == "/cancel": + return await conv.send_message( + "Operation cancelled!!", + buttons=get_back_button("setter"), + ) + except BaseException as er: + LOGS.exception(er) + media = await event.client.download_media(response, "inlpic") + if ( + not (response.text).startswith("/") + and response.text != "" + and (not response.media or isinstance(response.media, MessageMediaWebPage)) + ): + url = text_to_url(response) + else: + try: + url = uploader.upload_file(media) + remove(media) + except BaseException as er: + LOGS.exception(er) + return await conv.send_message( + "Terminated.", + buttons=get_back_button("setter"), + ) + await setit(event, var, url) + await conv.send_message( + f"{name} has been set.", + buttons=get_back_button("setter"), + ) + + +FD_MEDIA = {} + + +@callback(re.compile("fd(.*)"), owner=True) +async def fdroid_dler(event): + uri = event.data_match.group(1).decode("utf-8") + if FD_MEDIA.get(uri): + return await event.edit(file=FD_MEDIA[uri]) + await event.answer("• Starting Download •", alert=True) + await event.edit("• Downloading.. •") + URL = f"https://f-droid.org/packages/{uri}" + conte = await async_searcher(URL, re_content=True) + BSC = bs(conte, "html.parser", from_encoding="utf-8") + dl_ = BSC.find("p", "package-version-download").find("a")["href"] + title = BSC.find("h3", "package-name").text.strip() + thumb = BSC.find("img", "package-icon")["src"] + if thumb.startswith("/"): + thumb = f"https://f-droid.org{thumb}" + thumb, _ = await fast_download(thumb, filename=f"{uri}.png") + s_time = time.time() + file, _ = await fast_download( + dl_, + filename=f"{title}.apk", + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + event, + s_time, + "Downloading...", + ) + ), + ) + + time.time() + n_file = await event.client.fast_uploader( + file, show_progress=True, event=event, message="Uploading...", to_delete=True + ) + buttons = Button.switch_inline("Search Back", query="fdroid", same_peer=True) + try: + msg = await event.edit( + f"**• [{title}]({URL}) •**", file=n_file, thumb=thumb, buttons=buttons + ) + except Exception as er: + LOGS.exception(er) + try: + msg = await event.client.edit_message( + await event.get_input_chat(), + event.message_id, + f"**• [{title}]({URL}) •**", + buttons=buttons, + thumb=thumb, + file=n_file, + ) + except Exception as er: + os.remove(thumb) + LOGS.exception(er) + return await event.edit(f"**ERROR**: `{er}`", buttons=buttons) + if msg and hasattr(msg, "media"): + FD_MEDIA.update({uri: msg.media}) + os.remove(thumb) diff --git a/assistant/games.py b/assistant/games.py new file mode 100644 index 0000000000000000000000000000000000000000..0542f60f8c4d96dd4f0eef786ab4ac71e79f890c --- /dev/null +++ b/assistant/games.py @@ -0,0 +1,331 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +• `{i}akinator` | `/akinator` + Start akinator game from Userbot/Assistant + +• `/startgame` + Open Portal for Games +""" + +import asyncio +import re, uuid, operator +from random import choice, shuffle + +from akipy.async_akipy import Akinator +from telethon.errors.rpcerrorlist import BotMethodInvalidError +from telethon.events import Raw +from telethon.tl.types import InputMediaPoll, Poll, PollAnswer, UpdateMessagePollVote + +from pyUltroid._misc._decorators import ultroid_cmd +from logging import getLogger +from html import unescape +from telethon.tl.types import TextWithEntities +from pyUltroid.fns.helper import inline_mention +from pyUltroid.fns.tools import async_searcher +from telethon.errors import ChatSendStickersForbiddenError + +from . import * # Ensure this import matches your project structure + +games = {} +aki_photo = "https://graph.org/file/3cc8825c029fd0cab9edc.jpg" + +akipyLOGS = getLogger("akipy") + +@ultroid_cmd(pattern="akinator") +async def akina(e): + sta = Akinator() + games[e.chat_id] = {e.id: sta} + LOGS.info(f"Game started for chat {e.chat_id} with ID {e.id}.") + try: + m = await e.client.inline_query(asst.me.username, f"aki_{e.chat_id}_{e.id}") + await m[0].click(e.chat_id) + akipyLOGS.info(f"Clicked inline result for chat {e.chat_id}") + except BotMethodInvalidError as err: + akipyLOGS.error(f"BotMethodInvalidError: {err}") + await asst.send_file( + e.chat_id, + aki_photo, + buttons=Button.inline(get_string("aki_2"), data=f"aki_{e.chat_id}_{e.id}"), + ) + except Exception as er: + akipyLOGS.error(f"Unexpected error: {er}") + return await e.eor(f"ERROR : {er}") + if e.out: + await e.delete() + + +@asst_cmd(pattern="akinator", owner=True) +async def _akokk(e): + await akina(e) + + +@callback(re.compile("aki_(.*)"), owner=True) +async def doai(e): + adt = e.pattern_match.group(1).strip().decode("utf-8") + dt = adt.split("_") + ch = int(dt[0]) + mid = int(dt[1]) + await e.edit(get_string("com_1")) + try: + await games[ch][mid].start_game(child_mode=False) + bts = [Button.inline(o, f"aka_{adt}_{o}") for o in ["Yes", "No", "Idk"]] + cts = [Button.inline(o, f"aka_{adt}_{o}") for o in ["Probably", "Probably Not"]] + bts = [bts, cts] + await e.edit(f"Q. {games[ch][mid].question}", buttons=bts) + except KeyError: + return await e.answer(get_string("aki_1"), alert=True) + + +@callback(re.compile("aka_(.*)"), owner=True) +async def okah(e): + try: + mk = e.pattern_match.group(1).decode("utf-8").split("_") + #akipyLOGS.info(f"Parsed values: {mk}") + + if len(mk) < 3: + akipyLOGS.error("Pattern match did not return enough parts.") + return await e.answer("Invalid data received.", alert=True) + + ch = int(mk[0]) + mid = int(mk[1]) + ans = mk[2] + + gm = games[ch][mid] + await gm.answer(ans) + + # Check for the final guess in the API response + if gm.name_proposition and gm.description_proposition: + gm.win = True + text = f"It's {gm.name_proposition}\n{gm.description_proposition}" + await e.edit(text, file=gm.photo) + else: + # Game is not won yet, continue asking questions + buttons = [ + [Button.inline(o, f"aka_{ch}_{mid}_{o}") for o in ["Yes", "No", "Idk"]], + [Button.inline(o, f"aka_{ch}_{mid}_{o}") for o in ["Probably", "Probably Not"]], + ] + await e.edit(gm.question, buttons=buttons) + + except KeyError: + await e.answer(get_string("aki_3")) + except Exception as ex: + akipyLOGS.error(f"An unexpected error occurred: {ex}") + + +@in_pattern(re.compile("aki_?(.*)"), owner=True) +async def eiagx(e): + bts = Button.inline(get_string("aki_2"), data=e.text) + ci = types.InputWebDocument(aki_photo, 0, "image/jpeg", []) + ans = [ + await e.builder.article( + "Akinator", + type="photo", + content=ci, + text="Akinator", + thumb=ci, + buttons=bts, + include_media=True, + ) + ] + await e.answer(ans) + + +# ----------------------- Main Command ------------------- # + +GIMAGE = "https://graph.org/file/1c51015bae5205a65fd69.jpg" + +@asst_cmd(pattern="startgame", owner=True) +async def magic(event): + buttons = [ + [Button.inline("Trivia Quiz", "trzia")], + [Button.inline("Cancel ❌", "delit")], + ] + await event.reply( + get_string("games_1"), + file=GIMAGE, + buttons=buttons, + ) + + +# -------------------------- Trivia ----------------------- # + +TR_BTS = {} +DIFI_KEYS = ["Easy", "Medium", "Hard"] +TRIVIA_CHATS = {} +POLLS = {} +CONGO_STICKER = [ + "CAADAgADSgIAAladvQrJasZoYBh68AI", + "CAADAgADXhIAAuyZKUl879mlR_dkOwI", + "CAADAgADpQAD9wLID-xfZCDwOI5LAg", + "CAADAgADjAADECECEFZM-SrKO9GgAg", + "CAADAgADSwIAAj-VzArAzNCDiGWAHAI", + "CAADAgADhQADwZxgDIuMHR9IU10iAg", + "CAADAgADiwMAAsSraAuoe2BwYu1sdQI", +] + + +@callback("delit", owner=True) +async def delete_it(event): + await event.delete() + + +@callback(re.compile("ctdown(.*)"), owner=True) +async def ct_spam(e): + n = e.data_match.group(1).decode("utf-8") + await e.answer(f"Wait {n} seconds..", alert=True) + + +@callback(re.compile("trzia(.*)"), owner=True) +async def choose_cata(event): + match = event.data_match.group(1).decode("utf-8") + if not match: + if TR_BTS.get("category"): + buttons = TR_BTS["category"] + else: + req = ( + await async_searcher( + "https://opentdb.com/api_category.php", re_json=True + ) + )["trivia_categories"] + btt = [] + for i in req: + name = i["name"] + if ":" in name: + name = name.split(":")[1] + btt.append(Button.inline(name, f"trziad_{i['id']}")) + buttons = list(zip(btt[::2], btt[1::2])) + if len(btt) % 2 == 1: + buttons.append((btt[-1],)) + buttons.append([Button.inline("Cancel ❌", "delit")]) + TR_BTS.update({"category": buttons}) + text = get_string("games_2") + elif match[0] == "d": + cat = match[1:] + buttons = [[Button.inline(i, f"trziac{cat}_{i}") for i in DIFI_KEYS]] + buttons.append(get_back_button("trzia")) + text = get_string("games_3") + elif match[0] == "c": + m = match[1:] + buttons = [[Button.inline(str(i), f"trziat{m}_{i}") for i in range(10, 70, 20)]] + text = get_string("games_4") + elif match[0] == "t": + m_ = match[1:] + buttons = [ + [Button.inline(str(i), f"trzias{m_}_{i}") for i in [10, 30, 60, 120]] + ] + text = get_string("games_5") + elif match[0] == "s": + chat = event.chat_id + cat, le, nu, in_ = match[2:].split("_") + msg = await event.edit(get_string("games_6").format(le, nu)) + for i in reversed(range(5)): + msg = await msg.edit(buttons=Button.inline(f"{i} ⏰", f"ctdown{i}")) + await asyncio.sleep(1) + await msg.edit( + msg.text + "\n\n• Send /cancel to stop the Quiz...", buttons=None + ) + qsss = await async_searcher( + f"https://opentdb.com/api.php?amount={nu}&category={cat}&difficulty={le.lower()}", + re_json=True, + ) + qs = qsss["results"] + if not qs: + await event.respond("Sorry, No Question Found for the given Criteria..") + await event.delete() + return + TRIVIA_CHATS.update({chat: {}}) + for copper, q in enumerate(qs): + if TRIVIA_CHATS[chat].get("cancel") is not None: + break + ansi = str(uuid.uuid1()).split("-")[0].encode() + opts = [PollAnswer(TextWithEntities(unescape(q["correct_answer"]), entities=[]), ansi)] + [ + opts.append( + PollAnswer(TextWithEntities(unescape(a), entities=[]), str(uuid.uuid1()).split("-")[0].encode()) + ) + for a in q["incorrect_answers"] + ] + shuffle(opts) + poll = InputMediaPoll( + Poll( + 0, + TextWithEntities( + f"[{copper+1}]. " + unescape(q["question"]), + entities=[] + ), + answers=opts, + public_voters=True, + quiz=True, + close_period=int(in_), + ), + correct_answers=[ansi], + solution="Join @TeamUltroid", + solution_entities=[], + ) + m_ = await event.client.send_message(chat, file=poll) + POLLS.update({m_.poll.poll.id: {"chat": m_.chat_id, "answer": ansi}}) + await asyncio.sleep(int(in_)) + if not TRIVIA_CHATS[chat]: + await event.respond( + "No-One Got Any Score in the Quiz!\nBetter Luck Next Time!" + ) + else: + try: + await event.respond(file=choice(CONGO_STICKER)) + except ChatSendStickersForbiddenError: + pass + LBD = "🎯 **Scoreboard of the Quiz.**\n\n" + TRC = TRIVIA_CHATS[chat] + if "cancel" in TRC.keys(): + del TRC["cancel"] + for userid, user_score in dict( + sorted(TRC.items(), key=operator.itemgetter(1), reverse=True) + ).items(): + user = inline_mention(await event.client.get_entity(userid)) + LBD += f"••• {user} - {user_score}\n" + await event.respond(LBD) + del TRIVIA_CHATS[chat] + list_ = list(POLLS.copy().keys()) + for key in list_: + if POLLS[key]["chat"] == chat: + del POLLS[key] + return + await event.edit(text, buttons=buttons) + + +@asst.on( + Raw(UpdateMessagePollVote, func=lambda x: TRIVIA_CHATS and POLLS.get(x.poll_id)) +) +async def pollish(eve: UpdateMessagePollVote): + if POLLS.get(eve.poll_id)["chat"] not in TRIVIA_CHATS.keys(): + return + if not eve.options: + # Consider as correct answer if no options selected + chat = POLLS.get(eve.poll_id)["chat"] + user = eve.peer.user_id + if not TRIVIA_CHATS.get(chat, {}).get(user): + TRIVIA_CHATS[chat][user] = 1 + else: + TRIVIA_CHATS[chat][user] += 1 + return + if POLLS[eve.poll_id]["answer"] != eve.options[0]: + return + chat = POLLS.get(eve.poll_id)["chat"] + user = eve.peer.user_id + if not TRIVIA_CHATS.get(chat, {}).get(user): + TRIVIA_CHATS[chat][user] = 1 + else: + TRIVIA_CHATS[chat][user] += 1 + + +@asst_cmd("cancel", owner=True, func=lambda x: TRIVIA_CHATS.get(x.chat_id)) +async def cancelish(event): + chat = TRIVIA_CHATS.get(event.chat_id) + chat.update({"cancel": True}) + await event.respond("Cancelled!") diff --git a/assistant/initial.py b/assistant/initial.py new file mode 100644 index 0000000000000000000000000000000000000000..abf334b2827859034f7a3eb9a0c2a154f015dc23 --- /dev/null +++ b/assistant/initial.py @@ -0,0 +1,84 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import re + +from . import * + +STRINGS = { + 1: """🎇 **Thanks for Deploying Ultroid Userbot!** + +• Here, are the Some Basic stuff from, where you can Know, about its Usage.""", + 2: """🎉** About Ultroid** + +🧿 Ultroid is Pluggable and powerful Telethon Userbot, made in Python from Scratch. It is Aimed to Increase Security along with Addition of Other Useful Features. + +❣ Made by **@TeamUltroid**""", + 3: """**💡• FAQs •** + +-> [Username Tracker](https://t.me/UltroidUpdates/24) +-> [Keeping Custom Addons Repo](https://t.me/UltroidUpdates/28) +-> [Disabling Deploy message](https://t.me/UltroidUpdates/27) +-> [Setting up TimeZone](https://t.me/UltroidUpdates/22) +-> [About Inline PmPermit](https://t.me/UltroidUpdates/21) +-> [About Dual Mode](https://t.me/UltroidUpdates/18) +-> [Custom Thumbnail](https://t.me/UltroidUpdates/13) +-> [About FullSudo](https://t.me/UltroidUpdates/11) +-> [Setting Up PmBot](https://t.me/UltroidUpdates/2) +-> [Also Check](https://t.me/UltroidUpdates/14) + +**• To Know About Updates** + - Join @TeamUltroid.""", + 4: f"""• `To Know All Available Commands` + + - `{HNDLR}help` + - `{HNDLR}cmds`""", + 5: """• **For Any Other Query or Suggestion** + - Move to **@UltroidSupportChat**. + +• Thanks for Reaching till END.""", +} + + +@callback(re.compile("initft_(\\d+)")) +async def init_depl(e): + CURRENT = int(e.data_match.group(1)) + if CURRENT == 5: + return await e.edit( + STRINGS[5], + buttons=Button.inline("<< Back", "initbk_4"), + link_preview=False, + ) + + await e.edit( + STRINGS[CURRENT], + buttons=[ + Button.inline("<<", f"initbk_{str(CURRENT - 1)}"), + Button.inline(">>", f"initft_{str(CURRENT + 1)}"), + ], + link_preview=False, + ) + + +@callback(re.compile("initbk_(\\d+)")) +async def ineiq(e): + CURRENT = int(e.data_match.group(1)) + if CURRENT == 1: + return await e.edit( + STRINGS[1], + buttons=Button.inline("Start Back >>", "initft_2"), + link_preview=False, + ) + + await e.edit( + STRINGS[CURRENT], + buttons=[ + Button.inline("<<", f"initbk_{str(CURRENT - 1)}"), + Button.inline(">>", f"initft_{str(CURRENT + 1)}"), + ], + link_preview=False, + ) diff --git a/assistant/inlinestuff.py b/assistant/inlinestuff.py new file mode 100644 index 0000000000000000000000000000000000000000..5c157a6bb2692c81f32c9c6d12e14a99991e7002 --- /dev/null +++ b/assistant/inlinestuff.py @@ -0,0 +1,623 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import base64 +import inspect +from datetime import datetime +from html import unescape +from random import choice +from re import compile as re_compile + +from bs4 import BeautifulSoup as bs +from telethon import Button +from telethon.tl.alltlobjects import LAYER, tlobjects +from telethon.tl.types import DocumentAttributeAudio as Audio +from telethon.tl.types import InputWebDocument as wb + +from pyUltroid.fns.misc import google_search +from pyUltroid.fns.tools import ( + _webupload_cache, + async_searcher, + get_ofox, + saavn_search, + webuploader, +) + +from . import * +from . import _ult_cache + +SUP_BUTTONS = [ + [ + Button.url("• Repo •", url="https://github.com/TeamUltroid/Ultroid"), + Button.url("• Support •", url="t.me/UltroidSupportChat"), + ], +] + +ofox = "https://graph.org/file/231f0049fcd722824f13b.jpg" +gugirl = "https://graph.org/file/0df54ae4541abca96aa11.jpg" +ultpic = "https://graph.org/file/4136aa1650bc9d4109cc5.jpg" + +apis = [ + "QUl6YVN5QXlEQnNZM1dSdEI1WVBDNmFCX3c4SkF5NlpkWE5jNkZV", + "QUl6YVN5QkYwenhMbFlsUE1wOXh3TVFxVktDUVJxOERnZHJMWHNn", + "QUl6YVN5RGRPS253blB3VklRX2xiSDVzWUU0Rm9YakFLSVFWMERR", +] + + +@in_pattern("ofox", owner=True) +async def _(e): + try: + match = e.text.split(" ", maxsplit=1)[1] + except IndexError: + kkkk = e.builder.article( + title="Enter Device Codename", + thumb=wb(ofox, 0, "image/jpeg", []), + text="**OFᴏx🦊Rᴇᴄᴏᴠᴇʀʏ**\n\nYou didn't search anything", + buttons=Button.switch_inline("Sᴇᴀʀᴄʜ Aɢᴀɪɴ", query="ofox ", same_peer=True), + ) + return await e.answer([kkkk]) + device, releases = await get_ofox(match) + if device.get("detail") is None: + fox = [] + fullname = device["full_name"] + codename = device["codename"] + str(device["supported"]) + maintainer = device["maintainer"]["name"] + link = f"https://orangefox.download/device/{codename}" + for data in releases["data"]: + release = data["type"] + version = data["version"] + size = humanbytes(data["size"]) + release_date = datetime.utcfromtimestamp(data["date"]).strftime("%Y-%m-%d") + text = f"[­]({ofox})**OrangeFox Recovery For**\n\n" + text += f"` Full Name: {fullname}`\n" + text += f"` Codename: {codename}`\n" + text += f"` Maintainer: {maintainer}`\n" + text += f"` Build Type: {release}`\n" + text += f"` Version: {version}`\n" + text += f"` Size: {size}`\n" + text += f"` Build Date: {release_date}`" + fox.append( + await e.builder.article( + title=f"{fullname}", + description=f"{version}\n{release_date}", + text=text, + thumb=wb(ofox, 0, "image/jpeg", []), + link_preview=True, + buttons=[ + Button.url("Dᴏᴡɴʟᴏᴀᴅ", url=f"{link}"), + Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", query="ofox ", same_peer=True + ), + ], + ) + ) + await e.answer( + fox, switch_pm="OrangeFox Recovery Search.", switch_pm_param="start" + ) + else: + await e.answer( + [], switch_pm="OrangeFox Recovery Search.", switch_pm_param="start" + ) + + +@in_pattern("fl2lnk ?(.*)", owner=True) +async def _(e): + match = e.pattern_match.group(1) + chat_id, msg_id = match.split(":") + filename = _webupload_cache[int(chat_id)][int(msg_id)] + if "/" in filename: + filename = filename.split("/")[-1] + __cache = f"{chat_id}:{msg_id}" + buttons = [ + [ + Button.inline("anonfiles", data=f"flanonfiles//{__cache}"), + Button.inline("transfer", data=f"fltransfer//{__cache}"), + ], + [ + Button.inline("bayfiles", data=f"flbayfiles//{__cache}"), + Button.inline("x0.at", data=f"flx0.at//{__cache}"), + ], + [ + Button.inline("file.io", data=f"flfile.io//{__cache}"), + Button.inline("siasky", data=f"flsiasky//{__cache}"), + ], + ] + try: + lnk = [ + await e.builder.article( + title=f"Upload {filename}", + text=f"**File:**\n{filename}", + buttons=buttons, + ) + ] + except BaseException as er: + LOGS.exception(er) + lnk = [ + await e.builder.article( + title="fl2lnk", + text="File not found", + ) + ] + await e.answer(lnk, switch_pm="File to Link.", switch_pm_param="start") + + +@callback( + re_compile( + "fl(.*)", + ), + owner=True, +) +async def _(e): + t = (e.data).decode("UTF-8") + data = t[2:] + host = data.split("//")[0] + chat_id, msg_id = data.split("//")[1].split(":") + filename = _webupload_cache[int(chat_id)][int(msg_id)] + if "/" in filename: + filename = filename.split("/")[-1] + await e.edit(f"Uploading `{filename}` on {host}") + link = (await webuploader(chat_id, msg_id, host)).strip().replace("\n", "") + await e.edit(f"Uploaded `{filename}` on {host}.", buttons=Button.url("View", link)) + + +@in_pattern("repo", owner=True) +async def repo(e): + res = [ + await e.builder.article( + title="Ultroid Userbot", + description="Userbot | Telethon", + thumb=wb(ultpic, 0, "image/jpeg", []), + text="• **ULTROID USERBOT** •", + buttons=SUP_BUTTONS, + ), + ] + await e.answer(res, switch_pm="Ultroid Repo.", switch_pm_param="start") + + +@in_pattern("go", owner=True) +async def gsearch(q_event): + try: + match = q_event.text.split(maxsplit=1)[1] + except IndexError: + return await q_event.answer( + [], switch_pm="Google Search. Enter a query!", switch_pm_param="start" + ) + searcher = [] + gresults = await google_search(match) + for i in gresults: + try: + title = i["title"] + link = i["link"] + desc = i["description"] + searcher.append( + await q_event.builder.article( + title=title, + description=desc, + thumb=wb(gugirl, 0, "image/jpeg", []), + text=f"**Google Search**\n\n**• Title •**\n`{title}`\n\n**• Description •**\n`{desc}`", + link_preview=False, + buttons=[ + [Button.url("Link", url=f"{link}")], + [ + Button.switch_inline( + "Search Again", + query="go ", + same_peer=True, + ), + Button.switch_inline( + "Share", + query=f"go {match}", + same_peer=False, + ), + ], + ], + ), + ) + except IndexError: + break + await q_event.answer(searcher, switch_pm="Google Search.", switch_pm_param="start") + + +@in_pattern("mods", owner=True) +async def _(e): + try: + quer = e.text.split(" ", maxsplit=1)[1] + except IndexError: + return await e.answer( + [], switch_pm="Mod Apps Search. Enter app name!", switch_pm_param="start" + ) + start = 0 * 3 + 1 + da = base64.b64decode(choice(apis)).decode("ascii") + url = f"https://www.googleapis.com/customsearch/v1?key={da}&cx=25b3b50edb928435b&q={quer}&start={start}" + data = await async_searcher(url, re_json=True) + search_items = data.get("items", []) + modss = [] + for a in search_items: + title = a.get("title") + desc = a.get("snippet") + link = a.get("link") + text = f"**••Tɪᴛʟᴇ••** `{title}`\n\n" + text += f"**Dᴇsᴄʀɪᴘᴛɪᴏɴ** `{desc}`" + modss.append( + await e.builder.article( + title=title, + description=desc, + text=text, + link_preview=True, + buttons=[ + [Button.url("Dᴏᴡɴʟᴏᴀᴅ", url=f"{link}")], + [ + Button.switch_inline( + "Mᴏʀᴇ Mᴏᴅs", + query="mods ", + same_peer=True, + ), + Button.switch_inline( + "Sʜᴀʀᴇ", + query=f"mods {quer}", + same_peer=False, + ), + ], + ], + ), + ) + await e.answer(modss, switch_pm="Search Mod Applications.", switch_pm_param="start") + + +APP_CACHE = {} +RECENTS = {} +PLAY_API = "https://googleplay.onrender.com/api/apps?q=" + + +@in_pattern("app", owner=True) +async def _(e): + try: + f = e.text.split(maxsplit=1)[1].lower() + except IndexError: + get_string("instu_1") + res = [] + if APP_CACHE and RECENTS.get(e.sender_id): + res.extend( + APP_CACHE[a][0] for a in RECENTS[e.sender_id] if APP_CACHE.get(a) + ) + return await e.answer( + res, switch_pm=get_string("instu_2"), switch_pm_param="start" + ) + try: + return await e.answer( + APP_CACHE[f], switch_pm="Application Searcher.", switch_pm_param="start" + ) + except KeyError: + pass + foles = [] + url = PLAY_API + f.replace(" ", "+") + aap = await async_searcher(url, re_json=True) + for z in aap["results"][:50]: + url = "https://play.google.com/store/apps/details?id=" + z["appId"] + name = z["title"] + desc = unescape(z["summary"])[:300].replace("
", "\n") + "..." + dev = z["developer"]["devId"] + text = f"**• App Name •** [{name}]({url})\n" + text += f"**• Developer •** `{dev}`\n" + text += f"**• Description •**\n`{desc}`" + foles.append( + await e.builder.article( + title=name, + description=dev, + thumb=wb(z["icon"], 0, "image/jpeg", []), + text=text, + link_preview=True, + buttons=[ + [Button.url("Link", url=url)], + [ + Button.switch_inline( + "More Apps", + query="app ", + same_peer=True, + ), + Button.switch_inline( + "Sʜᴀʀᴇ", + query=f"app {f}", + same_peer=False, + ), + ], + ], + ), + ) + APP_CACHE.update({f: foles}) + if RECENTS.get(e.sender_id): + RECENTS[e.sender_id].append(f) + else: + RECENTS.update({e.sender_id: [f]}) + await e.answer(foles, switch_pm="Application Searcher.", switch_pm_param="start") + + +PISTON_URI = "https://emkc.org/api/v2/piston/" +PISTON_LANGS = {} + + +@in_pattern("run", owner=True) +async def piston_run(event): + try: + lang = event.text.split()[1] + code = event.text.split(maxsplit=2)[2] + except IndexError: + result = await event.builder.article( + title="Bad Query", + description="Usage: [Language] [code]", + thumb=wb( + "https://graph.org/file/e33c57fc5f1044547e4d8.jpg", 0, "image/jpeg", [] + ), + text=f'**Inline Usage**\n\n`@{asst.me.username} run python print("hello world")`\n\n[Language List](https://graph.org/Ultroid-09-01-6)', + ) + return await event.answer([result]) + if not PISTON_LANGS: + se = await async_searcher(f"{PISTON_URI}runtimes", re_json=True) + PISTON_LANGS.update({lang.pop("language"): lang for lang in se}) + if lang in PISTON_LANGS.keys(): + version = PISTON_LANGS[lang]["version"] + else: + result = await event.builder.article( + title="Unsupported Language", + description="Usage: [Language] [code]", + thumb=wb( + "https://graph.org/file/e33c57fc5f1044547e4d8.jpg", 0, "image/jpeg", [] + ), + text=f'**Inline Usage**\n\n`@{asst.me.username} run python print("hello world")`\n\n[Language List](https://graph.org/Ultroid-09-01-6)', + ) + return await event.answer([result]) + output = await async_searcher( + f"{PISTON_URI}execute", + post=True, + json={ + "language": lang, + "version": version, + "files": [{"content": code}], + }, + re_json=True, + ) + + output = output["run"]["output"] or get_string("instu_4") + if len(output) > 3000: + output = f"{output[:3000]}..." + result = await event.builder.article( + title="Result", + description=output, + text=f"• **Language:**\n`{lang}`\n\n• **Code:**\n`{code}`\n\n• **Result:**\n`{output}`", + thumb=wb( + "https://graph.org/file/871ee4a481f58117dccc4.jpg", 0, "image/jpeg", [] + ), + buttons=Button.switch_inline("Fork", query=event.text, same_peer=True), + ) + await event.answer([result], switch_pm="• Piston •", switch_pm_param="start") + + +FDROID_ = {} + + +@in_pattern("fdroid", owner=True) +async def do_magic(event): + try: + match = event.text.split(" ", maxsplit=1)[1].lower() + except IndexError: + return await event.answer( + [], switch_pm="Enter Query to Search", switch_pm_param="start" + ) + if FDROID_.get(match): + return await event.answer( + FDROID_[match], switch_pm=f"• Results for {match}", switch_pm_param="start" + ) + link = "https://search.f-droid.org/?q=" + match.replace(" ", "+") + content = await async_searcher(link, re_content=True) + BSC = bs(content, "html.parser", from_encoding="utf-8") + ress = [] + for dat in BSC.find_all("a", "package-header")[:10]: + image = dat.find("img", "package-icon")["src"] + if image.endswith("/"): + image = "https://graph.org/file/a8dd4a92c5a53a89d0eff.jpg" + title = dat.find("h4", "package-name").text.strip() + desc = dat.find("span", "package-summary").text.strip() + text = f"• **Name :** `{title}`\n\n" + text += f"• **Description :** `{desc}`\n" + text += f"• **License :** `{dat.find('span', 'package-license').text.strip()}`" + imga = wb(image, 0, "image/jpeg", []) + ress.append( + await event.builder.article( + title=title, + type="photo", + description=desc, + text=text, + content=imga, + thumb=imga, + include_media=True, + buttons=[ + Button.inline( + "• Download •", "fd" + dat["href"].split("packages/")[-1] + ), + Button.switch_inline("• Share •", query=event.text), + ], + ) + ) + msg = f"Showing {len(ress)} Results!" if ress else "No Results Found" + FDROID_.update({match: ress}) + await event.answer(ress, switch_pm=msg, switch_pm_param="start") + + +# Thanks to OpenSource +_bearer_collected = [ + "AAAAAAAAAAAAAAAAAAAAALIKKgEAAAAA1DRuS%2BI7ZRKiagD6KHYmreaXomo%3DP5Vaje4UTtEkODg0fX7nCh5laSrchhtLxeyEqxXpv0w9ZKspLD", + "AAAAAAAAAAAAAAAAAAAAAL5iUAEAAAAAmo6FYRjqdKlI3cNziIm%2BHUQB9Xs%3DS31pj0mxARMTOk2g9dvQ1yP9wknvY4FPBPUlE00smJcncw4dPR", + "AAAAAAAAAAAAAAAAAAAAAN6sVgEAAAAAMMjMMWrwgGyv7YQOWN%2FSAsO5SGM%3Dg8MG9Jq93Rlllaok6eht7HvRCruN4Vpzp4NaVsZaaHHWSTzKI8", +] + + +@in_pattern("twitter", owner=True) +async def twitter_search(event): + try: + match = event.text.split(maxsplit=1)[1].lower() + except IndexError: + return await event.answer( + [], switch_pm="Enter Query to Search", switch_pm_param="start" + ) + try: + return await event.answer( + _ult_cache["twitter"][match], + switch_pm="• Twitter Search •", + switch_pm_param="start", + ) + except KeyError: + pass + headers = {"Authorization": f"bearer {choice(_bearer_collected)}"} + res = await async_searcher( + f"https://api.twitter.com/1.1/users/search.json?q={match}", + headers=headers, + re_json=True, + ) + reso = [] + for user in res: + thumb = wb(user["profile_image_url_https"], 0, "image/jpeg", []) + if user.get("profile_banner_url"): + url = user["profile_banner_url"] + text = f"[\xad]({url})• **Name :** `{user['name']}`\n" + else: + text = f"• **Name :** `{user['name']}`\n" + text += f"• **Description :** `{user['description']}`\n" + text += f"• **Username :** `@{user['screen_name']}`\n" + text += f"• **Followers :** `{user['followers_count']}` • **Following :** `{user['friends_count']}`\n" + pro_ = "https://twitter.com/" + user["screen_name"] + text += f"• **Link :** [Click Here]({pro_})\n_" + reso.append( + await event.builder.article( + title=user["name"], + description=user["description"], + url=pro_, + text=text, + thumb=thumb, + ) + ) + swi_ = f"🐦 Showing {len(reso)} Results!" if reso else "No User Found :(" + await event.answer(reso, switch_pm=swi_, switch_pm_param="start") + if _ult_cache.get("twitter"): + _ult_cache["twitter"].update({match: reso}) + else: + _ult_cache.update({"twitter": {match: reso}}) + + +_savn_cache = {} + + +@in_pattern("saavn", owner=True) +async def savn_s(event): + try: + query = event.text.split(maxsplit=1)[1].lower() + except IndexError: + return await event.answer( + [], switch_pm="Enter Query to search 🔍", switch_pm_param="start" + ) + if query in _savn_cache: + return await event.answer( + _savn_cache[query], + switch_pm=f"Showing Results for {query}", + switch_pm_param="start", + ) + results = await saavn_search(query) + swi = "🎵 Saavn Search" if results else "No Results Found!" + res = [] + for song in results: + thumb = wb(song["image"], 0, "image/jpeg", []) + text = f"• **Title :** {song['title']}" + text += f"\n• **Year :** {song['year']}" + text += f"\n• **Lang :** {song['language']}" + text += f"\n• **Artist :** {song['artists']}" + text += f"\n• **Release Date :** {song['release_date']}" + res.append( + await event.builder.article( + title=song["title"], + description=song["artists"], + type="audio", + text=text, + include_media=True, + buttons=Button.switch_inline( + "Search Again 🔍", query="saavn", same_peer=True + ), + thumb=thumb, + content=wb( + song["url"], + 0, + "audio/mp4", + [ + Audio( + title=song["title"], + duration=int(song["duration"]), + performer=song["artists"], + ) + ], + ), + ) + ) + await event.answer(res, switch_pm=swi, switch_pm_param="start") + _savn_cache.update({query: res}) + + +@in_pattern("tl", owner=True) +async def inline_tl(ult): + try: + match = ult.text.split(maxsplit=1)[1] + except IndexError: + text = f"**Telegram TlObjects Searcher.**\n__(Don't use if you don't know what it is!)__\n\n• Example Usage\n`@{asst.me.username} tl GetFullUserRequest`" + return await ult.answer( + [ + await ult.builder.article( + title="How to Use?", + description="Tl Searcher by Ultroid", + url="https://t.me/TeamUltroid", + text=text, + ) + ], + switch_pm="Tl Search 🔍", + switch_pm_param="start", + ) + res = [] + for key in tlobjects.values(): + if match.lower() in key.__name__.lower(): + tyyp = "Function" if "tl.functions." in str(key) else "Type" + text = f"**Name:** `{key.__name__}`\n" + text += f"**Category:** `{tyyp}`\n" + text += f"\n`from {key.__module__} import {key.__name__}`\n\n" + if args := str(inspect.signature(key))[1:][:-1]: + text += "**Parameter:**\n" + for para in args.split(","): + text += " " * 4 + "`" + para + "`\n" + text += f"\n**Layer:** `{LAYER}`" + res.append( + await ult.builder.article( + title=key.__name__, + description=tyyp, + url="https://t.me/TeamUltroid", + text=text[:4000], + ) + ) + mo = f"Showing {len(res)} results!" if res else f"No Results for {match}!" + await ult.answer(res[:50], switch_pm=mo, switch_pm_param="start") + + +InlinePlugin.update( + { + "Pʟᴀʏ Sᴛᴏʀᴇ Aᴘᴘs": "app telegram", + "Mᴏᴅᴅᴇᴅ Aᴘᴘs": "mods minecraft", + "Sᴇᴀʀᴄʜ Oɴ Gᴏᴏɢʟᴇ": "go TeamUltroid", + "WʜɪSᴘᴇʀ": "wspr @username Hello🎉", + "YᴏᴜTᴜʙᴇ Dᴏᴡɴʟᴏᴀᴅᴇʀ": "yt Ed Sheeran Perfect", + "Piston Eval": "run javascript console.log('Hello Ultroid')", + "OʀᴀɴɢᴇFᴏx🦊": "ofox beryllium", + "Tᴡɪᴛᴛᴇʀ Usᴇʀ": "twitter theultroid", + "Fᴅʀᴏɪᴅ Sᴇᴀʀᴄʜ": "fdroid telegram", + "Sᴀᴀᴠɴ sᴇᴀʀᴄʜ": "saavn", + "Tʟ Sᴇᴀʀᴄʜ": "tl", + } +) diff --git a/assistant/localization.py b/assistant/localization.py new file mode 100644 index 0000000000000000000000000000000000000000..7abbdadef60a61e2106b5cc0c286d08ed0fcd5de --- /dev/null +++ b/assistant/localization.py @@ -0,0 +1,47 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import re + +from . import ( + Button, + ULTConfig, + callback, + get_back_button, + get_languages, + get_string, + udB, +) + + +@callback("lang", owner=True) +async def setlang(event): + languages = get_languages() + tultd = [ + Button.inline( + f"{languages[ult]['natively']} [{ult.lower()}]", + data=f"set_{ult}", + ) + for ult in languages + ] + buttons = list(zip(tultd[::2], tultd[1::2])) + if len(tultd) % 2 == 1: + buttons.append((tultd[-1],)) + buttons.append([Button.inline("« Back", data="mainmenu")]) + await event.edit(get_string("ast_4"), buttons=buttons) + + +@callback(re.compile(b"set_(.*)"), owner=True) +async def settt(event): + lang = event.data_match.group(1).decode("UTF-8") + languages = get_languages() + ULTConfig.lang = lang + udB.del_key("language") if lang == "en" else udB.set_key("language", lang) + await event.edit( + f"Your language has been set to {languages[lang]['natively']} [{lang}].", + buttons=get_back_button("lang"), + ) diff --git a/assistant/manager/__init__.py b/assistant/manager/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e16967cd56d3ec7cc5474a4825bd5c396404384a --- /dev/null +++ b/assistant/manager/__init__.py @@ -0,0 +1,11 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from pyUltroid._misc._decorators import ultroid_cmd +from pyUltroid.fns.helper import inline_mention + +from .. import * diff --git a/assistant/manager/_help.py b/assistant/manager/_help.py new file mode 100644 index 0000000000000000000000000000000000000000..caf0701822cec6f2a8322d966afb022f9d998c30 --- /dev/null +++ b/assistant/manager/_help.py @@ -0,0 +1,134 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import * + +START = """ +🪅 **Help Menu** 🪅 + +✘ /start : Check I am Alive or not. +✘ /help : Get This Message. +✘ /repo : Get Bot's Repo.. + +🧑‍💻 Join **@TeamUltroid** +""" + +ADMINTOOLS = """✘ **AdminTools** ✘ + +• /pin : Pins the Replied Message +• /pinned : Get Pinned message in chat. +• /unpin : Unpin the Replied message +• /unpin all : Unpin all Pinned Messages. + +• /ban (username/id/reply) : Ban the User +• /unban (username/id/reply) : UnBan the User. + +• /mute (username/id/reply) : Mute the User. +• /unmute (username/id/reply) : Unmute the User. + +• /tban (username/id/reply) (time) : Temporary ban a user +• /tmute (username/id/reply) (time) : temporary Mutes a User. + +• /purge (purge messages) + +• /setgpic (reply photo) : keep Chat Photo of Group. +• /delgpic : remove current chat Photo.""" + +UTILITIES = """ +✘ ** Utilities ** ✘ + +• /info (reply/username/id) : get detailed info of user. +• /id : get chat/user id. +• /tr : Translate Languages.. +• /q : Create Quotes. + +• /paste (reply file/text) : paste content on Spaceb.in +• /meaning (text) : Get Meaning of that Word. +• /google (query) : Search Something on Google.. + +• /suggest (query/reply) : Creates a Yes / No Poll. +""" + +LOCKS = """ +✘ ** Locks ** ✘ + +• /lock (query) : lock particular content in chat. +• /unlock (query) : Unlock some content. + +• All Queries +- `msgs` : for messages. +- `inlines` : for inline queries. +- `media` : for all medias. +- `games` : for games. +- `sticker` : for stickers. +- `polls` : for polls. +- `gif` : for gifs. +- `pin` : for pins. +- `changeinfo` : for change info right. +""" + +MISC = """ +✘ **Misc** ✘ + +• /joke : Get Random Jokes. +• /decide : Decide Something.. + +**✘ Stickertools ✘** +• /kang : add sticker to your pack. +• /listpack : get all of yours pack.. +""" + +STRINGS = {"Admintools": ADMINTOOLS, "locks": LOCKS, "Utils": UTILITIES, "Misc": MISC} + +MNGE = udB.get_key("MNGR_EMOJI") or "•" + + +def get_buttons(): + BTTS = [] + keys = STRINGS.copy() + while keys: + BT = [] + for i in list(keys)[:2]: + text = f"{MNGE} {i} {MNGE}" + BT.append(Button.inline(text, f"hlp_{i}")) + del keys[i] + BTTS.append(BT) + url = f"https://t.me/{asst.me.username}?startgroup=true" + BTTS.append([Button.url("Add me to Group", url)]) + return BTTS + + +@asst_cmd(pattern="help") +async def helpish(event): + if not event.is_private: + url = f"https://t.me/{asst.me.username}?start=start" + return await event.reply( + "Contact me in PM for help!", buttons=Button.url("Click me for Help", url) + ) + if str(event.sender_id) in owner_and_sudos() and ( + udB.get_key("DUAL_MODE") and (udB.get_key("DUAL_HNDLR") == "/") + ): + return + await event.reply(START, buttons=get_buttons()) + + +@callback("mngbtn", owner=True) +async def ehwhshd(e): + buttons = get_buttons() + buttons.append([Button.inline("<< Back", "open")]) + await e.edit(buttons=buttons) + + +@callback("mnghome") +async def home_aja(e): + await e.edit(START, buttons=get_buttons()) + + +@callback(re.compile("hlp_(.*)")) +async def do_something(event): + match = event.pattern_match.group(1).strip().decode("utf-8") + await event.edit(STRINGS[match], buttons=Button.inline("<< Back", "mnghome")) diff --git a/assistant/manager/_on_adds.py b/assistant/manager/_on_adds.py new file mode 100644 index 0000000000000000000000000000000000000000..22b4adad9d6034da7dfc2134a3eb5e774117030e --- /dev/null +++ b/assistant/manager/_on_adds.py @@ -0,0 +1,23 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from telethon import events + +from . import * + + +@asst.on(events.ChatAction(func=lambda x: x.user_added)) +async def dueha(e): + user = await e.get_user() + if not user.is_self: + return + sm = udB.get_key("ON_MNGR_ADD") + if sm == "OFF": + return + if not sm: + sm = "Thanks for Adding me :)" + await e.reply(sm, link_preview=False) diff --git a/assistant/manager/admins.py b/assistant/manager/admins.py new file mode 100644 index 0000000000000000000000000000000000000000..ebf6f9bd36b4539c4c2a39f3e22523c27adb1e47 --- /dev/null +++ b/assistant/manager/admins.py @@ -0,0 +1,50 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import re + +from telethon.errors.rpcerrorlist import UserNotParticipantError + +from pyUltroid import _ult_cache + +from . import * + + +@ultroid_cmd(pattern="d(kick|ban)", manager=True, require="ban_users") +async def dowj(e): + replied = await e.get_reply_message() + if replied: + user = replied.sender_id + else: + return await e.eor("Reply to a message...") + try: + await replied.delete() + if e.pattern_match.group(1).strip() == "kick": + await e.client.kick_participant(e.chat_id, user) + te = "Kicked" + else: + await e.client.edit_permissions(e.chat_id, user, view_messages=False) + te = "Banned" + await e.eor(f"{te} Successfully!") + except Exception as E: + await e.eor(str(E)) + + +@callback(re.compile("cc_(.*)"), func=_ult_cache.get("admin_callback")) +async def callback_(event): + data = event.data_match.group(1).decode("utf-8") + if data not in _ult_cache.get("admin_callback", {}): + return + try: + perm = await event.client.get_permissions(event.chat_id, event.sender_id) + except UserNotParticipantError: + return await event.answer("Join the Group First!", alert=True) + if not perm.is_admin: + return await event.answer("You are not an Admin!", alert=True) + _ult_cache["admin_callback"].update({data: (event.sender, perm)}) + await event.answer("Verification Done!") + await event.delete() diff --git a/assistant/manager/afk.py b/assistant/manager/afk.py new file mode 100644 index 0000000000000000000000000000000000000000..c371d37fa81909d6bb722e121f9bab3706bd2f80 --- /dev/null +++ b/assistant/manager/afk.py @@ -0,0 +1,110 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from datetime import datetime as dt + +from telethon.events import NewMessage +from telethon.tl.types import ( + Message, + MessageEntityMention, + MessageEntityMentionName, + User, +) +from telethon.utils import get_display_name + +from pyUltroid.fns.helper import inline_mention, time_formatter + +from . import asst, asst_cmd + +AFK = {} + + +@asst_cmd(pattern="afk", func=lambda x: not x.is_private) +async def go_afk(event): + sender = await event.get_sender() + if (not isinstance(sender, User)) or sender.bot: + return + try: + reason = event.text.split(" ", maxsplit=1)[1] + except IndexError: + reason = None + if event.is_reply and not reason: + replied = await event.get_reply_message() + if not reason and replied.text and not replied.media: + reason = replied.text + else: + reason = replied + time_ = dt.now() + if AFK.get(event.chat_id): + AFK[event.chat_id].update({event.sender_id: {"reason": reason, "time": time_}}) + else: + AFK.update( + {event.chat_id: {event.sender_id: {"reason": reason, "time": time_}}} + ) + mention = inline_mention(sender) + msg = f"**{mention} went AFK Now!**" + if reason and not isinstance(reason, str): + await event.reply(reason) + else: + msg += f"\n\n**Reason : ** `{reason}`" + await event.reply(msg) + + +@asst.on(NewMessage(func=lambda x: AFK.get(x.chat_id) and not x.is_private)) +async def make_change(event): + if event.text.startswith("/afk"): + return + sender = await event.get_sender() + if (not isinstance(sender, User)) or sender.bot: + return + chat_ = AFK[event.chat_id] + if event.sender_id in chat_.keys(): + name = get_display_name(event.sender) + cha_send = chat_[event.sender_id] + time_ = time_formatter((dt.now() - cha_send["time"]).seconds * 1000) + msg = f"**{name}** is No Longer AFK!\n**Was AFK for** {time_}" + await event.reply(msg) + del chat_[event.sender_id] + if not chat_: + del AFK[event.chat_id] + ST_SPAM = [] + replied = await event.get_reply_message() + if replied: + name = get_display_name(replied.sender) + if replied.sender_id in chat_.keys(): + s_der = chat_[replied.sender_id] + res_ = s_der["reason"] + time_ = time_formatter((dt.now() - s_der["time"]).seconds * 1000) + msg = f"**{name}** is AFK Currently!\n**From :** {time_}" + if res_ and isinstance(res_, str): + msg += f"\n**Reason :** {res_}" + elif res_ and isinstance(res_, Message): + await event.reply(res_) + await event.reply(msg) + ST_SPAM.append(replied.sender_id) + for ent, text in event.get_entities_text(): + dont_send, entity = None, None + if isinstance(ent, MessageEntityMentionName): + c_id = ent.user_id + elif isinstance(ent, MessageEntityMention): + c_id = text + else: + c_id = None + if c_id: + entity = await event.client.get_entity(c_id) + if entity and entity.id in chat_.keys() and entity.id not in ST_SPAM: + ST_SPAM.append(entity.id) + s_der = chat_[entity.id] + name = get_display_name(entity) + res_ = s_der["reason"] + time_ = time_formatter((dt.now() - s_der["time"]).seconds * 1000) + msg = f"**{name}** is AFK Currently!\n**From :** {time_}" + if res_ and isinstance(res_, str): + msg += f"\n**Reason :** {res_}" + elif res_ and isinstance(res_, Message): + await event.reply(res_) + await event.reply(msg) diff --git a/assistant/manager/misc.py b/assistant/manager/misc.py new file mode 100644 index 0000000000000000000000000000000000000000..6bb98a392033dc210eda8bf79419eec8f14a20de --- /dev/null +++ b/assistant/manager/misc.py @@ -0,0 +1,64 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +import random + +import aiohttp + +from pyUltroid.dB import DEVLIST +from pyUltroid.fns.admins import admin_check + +from . import * + + +@asst_cmd(pattern="decide") +async def dheh(e): + text = ["Yes", "NoU", "Maybe", "IDK"] + text = random.choice(text) + ri = e.reply_to_msg_id or e.id + await e.client.send_message(e.chat_id, text, reply_to=ri) + + +@asst_cmd(pattern="echo( (.*)|$)") +async def oqha(e): + if not await admin_check(e): + return + if match := e.pattern_match.group(1).strip(): + text = match + reply_to = e + elif e.is_reply: + text = (await e.get_reply_message()).text + reply_to = e.reply_to_msg_id + else: + return await e.eor("What to Echo?", time=5) + try: + await e.delete() + except BaseException as ex: + LOGS.error(ex) + await e.client.send_message(e.chat_id, text, reply_to=reply_to) + + +@asst_cmd(pattern="kickme$") +async def doit(e): + if e.sender_id in DEVLIST: + return await eod(e, "`I will Not Kick You, my Developer..`") + try: + await e.client.kick_participant(e.chat_id, e.sender_id) + except Exception as Fe: + return await e.eor(str(Fe), time=5) + await e.eor("Yes, You are right, get out.", time=5) + + +@asst_cmd(pattern="joke$") +async def do_joke(e): + e = await e.get_reply_message() if e.is_reply else e + link = "https://v2.jokeapi.dev/joke/Any?blacklistFlags=nsfw,religious,political,racist,sexist,explicit&type=single" + async with aiohttp.ClientSession() as ses: + async with ses.get(link) as out: + out = await out.json() + await e.reply(out["joke"]) diff --git a/assistant/manager/stickermanager.py b/assistant/manager/stickermanager.py new file mode 100644 index 0000000000000000000000000000000000000000..6f0d6f3b7b2058242bb5de8213d5f07c21ea24b6 --- /dev/null +++ b/assistant/manager/stickermanager.py @@ -0,0 +1,180 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import random + +from telethon import errors +from telethon.errors.rpcerrorlist import StickersetInvalidError +from telethon.tl.functions.messages import GetStickerSetRequest as GetSticker +from telethon.tl.functions.messages import UploadMediaRequest +from telethon.tl.functions.stickers import AddStickerToSetRequest as AddSticker +from telethon.tl.functions.stickers import CreateStickerSetRequest +from telethon.tl.types import InputPeerSelf +from telethon.tl.types import InputStickerSetItem as SetItem +from telethon.tl.types import InputStickerSetShortName, User +from telethon.utils import get_display_name, get_input_document + +from pyUltroid.fns.misc import Quotly +from pyUltroid.fns.tools import TgConverter + +from . import LOGS, asst, asst_cmd, udB + + +@asst_cmd( + pattern="kang", +) +async def kang_cmd(ult): + sender = await ult.get_sender() + if not isinstance(sender, User): + return + if not ult.is_reply: + return await ult.eor("`Reply to a sticker/photo..`", time=5) + reply = await ult.get_reply_message() + if sender.username: + pre = sender.username[:4] + else: + pre = random.random_string(length=3) + animated, dl, video = None, None, None + try: + emoji = ult.text.split(maxsplit=1)[1] + except IndexError: + emoji = None + if reply.sticker: + file = get_input_document(reply.sticker) + emoji = emoji or reply.file.emoji + name = reply.file.name + if name.endswith(".tgs"): + animated = True + dl = await reply.download_media() + elif name.endswith(".webm"): + video = True + dl = await reply.download_media() + elif reply.photo: + dl = await reply.download_media() + name = "sticker.webp" + image = TgConverter.resize_photo_sticker(dl) + image.save(name, "WEBP") + elif reply.text: + dl = await Quotly().create_quotly(reply) + else: + return await ult.eor("`Reply to sticker or text to add it in your pack...`") + if not emoji: + emoji = "🏵" + if dl: + upl = await ult.client.upload_file(dl) + file = get_input_document( + await ult.client(UploadMediaRequest(InputPeerSelf(), upl)) + ) + get_ = udB.get_key("STICKERS") or {} + type_ = "anim" if animated else "static" + if not get_.get(ult.sender_id) or not get_.get(ult.sender_id, {}).get(type_): + sn = f"{pre}_{ult.sender_id}" + title = f"{get_display_name(sender)}'s Kang Pack" + if animated: + type_ = "anim" + sn += "_anim" + title += " (Animated)" + elif video: + type_ = "vid" + sn += "_vid" + title += " (Video)" + sn += f"_by_{asst.me.username}" + try: + await asst(GetSticker(InputStickerSetShortName(sn), hash=0)) + sn = sn.replace(str(ult.sender_id), f"{ult.sender_id}_{ult.id}") + except StickersetInvalidError: + pass + try: + pack = await ult.client( + CreateStickerSetRequest( + user_id=sender.id, + title=title, + short_name=sn, + stickers=[SetItem(file, emoji=emoji)], + videos=video, + animated=animated, + software="@TeamUltroid", + ) + ) + except Exception as er: + return await ult.eor(str(er)) + sn = pack.set.short_name + if not get_.get(ult.sender_id): + get_.update({ult.sender_id: {type_: [sn]}}) + else: + get_[ult.sender_id].update({type_: [sn]}) + udB.set_key("STICKERS", get_) + return await ult.reply( + f"**Kanged Successfully!\nEmoji :** {emoji}\n**Link :** [Click Here](https://t.me/addstickers/{sn})" + ) + name = get_[ult.sender_id][type_][-1] + try: + await asst(GetSticker(InputStickerSetShortName(name), hash=0)) + except StickersetInvalidError: + get_[ult.sender_id][type_].remove(name) + try: + await asst( + AddSticker(InputStickerSetShortName(name), SetItem(file, emoji=emoji)) + ) + except (errors.StickerpackStickersTooMuchError, errors.StickersTooMuchError): + sn = f"{pre}{ult.sender_id}_{ult.id}" + title = f"{get_display_name(sender)}'s Kang Pack" + if animated: + sn += "_anim" + title += " (Animated)" + elif video: + sn += "_vid" + title += "(Video)" + sn += f"_by_{asst.me.username}" + try: + pack = await ult.client( + CreateStickerSetRequest( + user_id=sender.id, + title=title, + short_name=sn, + stickers=[SetItem(file, emoji=emoji)], + animated=animated, + ) + ) + except Exception as er: + return await ult.eor(str(er)) + get_[ult.sender_id][type_].append(pack.set.short_name) + udB.set_key("STICKERS", get_) + return await ult.reply( + f"**Created New Kang Pack!\nEmoji :** {emoji}\n**Link :** [Click Here](https://t.me/addstickers/{sn})" + ) + except Exception as er: + LOGS.exception(er) + return await ult.reply(str(er)) + await ult.reply( + f"Sticker Added to Pack Successfully\n**Link :** [Click Here](https://t.me/addstickers/{name})" + ) + + +@asst_cmd(pattern="listpack") +async def do_magic(ult): + ko = udB.get_key("STICKERS") or {} + if not ko.get(ult.sender_id): + return await ult.reply("No Sticker Pack Found!") + al_ = [] + ul = ko[ult.sender_id] + for _ in ul.keys(): + al_.extend(ul[_]) + msg = "• **Stickers Owned by You!**\n\n" + for _ in al_: + try: + pack = await ult.client(GetSticker(InputStickerSetShortName(_), hash=0)) + msg += f"• [{pack.set.title}](https://t.me/addstickers/{_})\n" + except StickerSetInvalidError: + if ul.get("anim") and _ in ul["anim"]: + ul["anim"].remove(_) + elif ul.get("vid") and _ in ul["vid"]: + ul["vid"].remove(_) + else: + ul["static"].remove(_) + udB.set_key("STICKERS", ko) + await ult.reply(msg) diff --git a/assistant/pmbot.py b/assistant/pmbot.py new file mode 100644 index 0000000000000000000000000000000000000000..e061e3afa8ba9fb6ed6857459719afafb9015163 --- /dev/null +++ b/assistant/pmbot.py @@ -0,0 +1,150 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +# https://github.com/xditya/TeleBot/blob/master/telebot/plugins/mybot/pmbot/incoming.py + +# --------------------------------------- Imports -------------------------------------------- # + +import os + +from telethon.errors.rpcerrorlist import UserNotParticipantError +from telethon.tl.custom import Button +from telethon.tl.functions.channels import GetFullChannelRequest +from telethon.tl.functions.messages import GetFullChatRequest +from telethon.tl.types import Channel, Chat +from telethon.utils import get_display_name + +from pyUltroid.dB.base import KeyManager +from pyUltroid.dB.botchat_db import * +from pyUltroid.fns.helper import inline_mention + +from . import * + +botb = KeyManager("BOTBLS", cast=list) +FSUB = udB.get_key("PMBOT_FSUB") +CACHE = {} +# --------------------------------------- Incoming -------------------------------------------- # + + +@asst_cmd( + load=AST_PLUGINS, + incoming=True, + func=lambda e: e.is_private and not botb.contains(e.sender_id), +) +async def on_new_mssg(event): + who = event.sender_id + # doesn't reply to that user anymore + if event.text.startswith("/") or who == OWNER_ID: + return + if FSUB: + MSG = "" + BTTS = [] + for chat in FSUB: + try: + await event.client.get_permissions(chat, event.sender_id) + except UserNotParticipantError: + if not MSG: + MSG += get_string("pmbot_1") + try: + uri = "" + TAHC_ = await event.client.get_entity(chat) + if hasattr(TAHC_, "username") and TAHC_.username: + uri = f"t.me/{TAHC_.username}" + elif CACHE.get(chat): + uri = CACHE[chat] + else: + if isinstance(TAHC_, Channel): + FUGB = await event.client(GetFullChannelRequest(chat)) + elif isinstance(TAHC_, Chat): + FUGB = await event.client(GetFullChatRequest(chat)) + else: + return + if FUGB.full_chat.exported_invite: + CACHE[chat] = FUGB.full_chat.exported_invite.link + uri = CACHE[chat] + BTTS.append(Button.url(get_display_name(TAHC_), uri)) + except Exception as er: + LOGS.exception(f"Error On PmBot Force Sub!\n - {chat} \n{er}") + if MSG and BTTS: + return await event.reply(MSG, buttons=BTTS) + xx = await event.forward_to(OWNER_ID) + if event.fwd_from: + await xx.reply(f"From {inline_mention(event.sender)} [`{event.sender_id}`]") + add_stuff(xx.id, who) + + +# --------------------------------------- Outgoing -------------------------------------------- # + + +@asst_cmd( + load=AST_PLUGINS, + from_users=[OWNER_ID], + incoming=True, + func=lambda e: e.is_private and e.is_reply, +) +async def on_out_mssg(event): + x = event.reply_to_msg_id + to_user = get_who(x) + if event.text.startswith("/who"): + try: + k = await asst.get_entity(to_user) + photu = await event.client.download_profile_photo(k.id) + await event.reply( + f"• **Name :** {get_display_name(k)}\n• **ID :** `{k.id}`\n• **Link :** {inline_mention(k)}", + file=photu, + ) + if photu: + os.remove(photu) + return + except BaseException as er: + return await event.reply(f"**ERROR : **{str(er)}") + elif event.text.startswith("/"): + return + if to_user: + await asst.send_message(to_user, event.message) + + +# --------------------------------------- Ban/Unban -------------------------------------------- # + + +@asst_cmd( + pattern="ban", + load=AST_PLUGINS, + from_users=[OWNER_ID], + func=lambda x: x.is_private, +) +async def banhammer(event): + if not event.is_reply: + return await event.reply(get_string("pmbot_2")) + target = get_who(event.reply_to_msg_id) + if botb.contains(target): + return await event.reply(get_string("pmbot_3")) + + botb.add(target) + await event.reply(f"#BAN\nUser : {target}") + await asst.send_message(target, get_string("pmbot_4")) + + +@asst_cmd( + pattern="unban", + load=AST_PLUGINS, + from_users=[OWNER_ID], + func=lambda x: x.is_private, +) +async def unbanhammer(event): + if not event.is_reply: + return await event.reply(get_string("pmbot_5")) + target = get_who(event.reply_to_msg_id) + if not botb.contains(target): + return await event.reply(get_string("pmbot_6")) + + botb.remove(target) + await event.reply(f"#UNBAN\nUser : {target}") + await asst.send_message(target, get_string("pmbot_7")) + + +# --------------------------------------- END -------------------------------------------- # diff --git a/assistant/start.py b/assistant/start.py new file mode 100644 index 0000000000000000000000000000000000000000..4464bdcb24d603432db22982f10de17f6047c09c --- /dev/null +++ b/assistant/start.py @@ -0,0 +1,248 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from datetime import datetime + +try: + from pytz import timezone as tz +except ImportError: + tz = None +from telethon import Button, events +from telethon.errors.rpcerrorlist import MessageDeleteForbiddenError +from telethon.utils import get_display_name + +from pyUltroid._misc import SUDO_M, owner_and_sudos +from pyUltroid.dB.base import KeyManager +from pyUltroid.fns.helper import inline_mention +from strings import get_string + +from . import * + +Owner_info_msg = udB.get_key("BOT_INFO_START") +custom_info = True +if Owner_info_msg is None: + custom_info = False + Owner_info_msg = f""" +**Owner** - {OWNER_NAME} +**OwnerID** - `{OWNER_ID}` + +**Message Forwards** - {udB.get_key("PMBOT")} + +**Ultroid [v{ultroid_version}](https://github.com/TeamUltroid/Ultroid), powered by @TeamUltroid** +""" + + +_settings = [ + [ + Button.inline("API Keys", data="cbs_apiset"), + Button.inline("PM Bot", data="cbs_chatbot"), + ], + [ + Button.inline("Alive", data="cbs_alvcstm"), + Button.inline("PMPermit", data="cbs_ppmset"), + ], + [ + Button.inline("Features", data="cbs_otvars"), + Button.inline("VC Song Bot", data="cbs_vcb"), + ], + [Button.inline("« Back", data="mainmenu")], +] + +_start = [ + [ + Button.inline("Language 🌐", data="lang"), + Button.inline("Settings ⚙️", data="setter"), + ], + [ + Button.inline("Sᴛᴀᴛs ✨", data="stat"), + Button.inline("Bʀᴏᴀᴅᴄᴀsᴛ 📻", data="bcast"), + ], + [Button.inline("TɪᴍᴇZᴏɴᴇ 🌎", data="tz")], +] + + +@callback("ownerinfo") +async def own(event): + msg = Owner_info_msg.format( + mention=event.sender.mention, me=inline_mention(ultroid_bot.me) + ) + if custom_info: + msg += "\n\n• Powered by **@TeamUltroid**" + await event.edit( + msg, + buttons=[Button.inline("Close", data="closeit")], + link_preview=False, + ) + + +@callback("closeit") +async def closet(lol): + try: + await lol.delete() + except MessageDeleteForbiddenError: + await lol.answer("MESSAGE_TOO_OLD", alert=True) + + +@asst_cmd(pattern="start( (.*)|$)", forwards=False, func=lambda x: not x.is_group) +async def ultroid(event): + args = event.pattern_match.group(1).strip() + keym = KeyManager("BOT_USERS", cast=list) + if not keym.contains(event.sender_id) and event.sender_id not in owner_and_sudos(): + keym.add(event.sender_id) + kak_uiw = udB.get_key("OFF_START_LOG") + if not kak_uiw or kak_uiw != True: + msg = f"{inline_mention(event.sender)} `[{event.sender_id}]` started your [Assistant bot](@{asst.me.username})." + buttons = [[Button.inline("Info", "itkkstyo")]] + if event.sender.username: + buttons[0].append( + Button.mention( + "User", await event.client.get_input_entity(event.sender_id) + ) + ) + await event.client.send_message( + udB.get_key("LOG_CHANNEL"), msg, buttons=buttons + ) + if event.sender_id not in SUDO_M.fullsudos: + ok = "" + me = inline_mention(ultroid_bot.me) + mention = inline_mention(event.sender) + if args and args != "set": + await get_stored_file(event, args) + if not udB.get_key("STARTMSG"): + if udB.get_key("PMBOT"): + ok = "You can contact my master using this bot!!\n\nSend your Message, I will Deliver it To Master." + await event.reply( + f"Hey there {mention}, this is Ultroid Assistant of {me}!\n\n{ok}", + file=udB.get_key("STARTMEDIA"), + buttons=[Button.inline("Info.", data="ownerinfo")] + if Owner_info_msg + else None, + ) + else: + await event.reply( + udB.get_key("STARTMSG").format(me=me, mention=mention), + file=udB.get_key("STARTMEDIA"), + buttons=[Button.inline("Info.", data="ownerinfo")] + if Owner_info_msg + else None, + ) + else: + name = get_display_name(event.sender) + if args == "set": + await event.reply( + "Choose from the below options -", + buttons=_settings, + ) + elif args: + await get_stored_file(event, args) + else: + await event.reply( + get_string("ast_3").format(name), + buttons=_start, + ) + + +@callback("itkkstyo", owner=True) +async def ekekdhdb(e): + text = f"When New Visitor will visit your Assistant Bot. You will get this log message!\n\nTo Disable : {HNDLR}setdb OFF_START_LOG True" + await e.answer(text, alert=True) + + +@callback("mainmenu", owner=True, func=lambda x: not x.is_group) +async def ultroid(event): + await event.edit( + get_string("ast_3").format(OWNER_NAME), + buttons=_start, + ) + + +@callback("stat", owner=True) +async def botstat(event): + ok = len(udB.get_key("BOT_USERS") or []) + msg = """Ultroid Assistant - Stats +Total Users - {}""".format( + ok, + ) + await event.answer(msg, cache_time=0, alert=True) + + +@callback("bcast", owner=True) +async def bdcast(event): + keym = KeyManager("BOT_USERS", cast=list) + total = keym.count() + await event.edit(f"• Broadcast to {total} users.") + async with event.client.conversation(OWNER_ID) as conv: + await conv.send_message( + "Enter your broadcast message.\nUse /cancel to stop the broadcast.", + ) + response = await conv.get_response() + if response.message == "/cancel": + return await conv.send_message("Cancelled!!") + success = 0 + fail = 0 + await conv.send_message(f"Starting a broadcast to {total} users...") + start = datetime.now() + for i in keym.get(): + try: + await asst.send_message(int(i), response) + success += 1 + except BaseException: + fail += 1 + end = datetime.now() + time_taken = (end - start).seconds + await conv.send_message( + f""" +**Broadcast completed in {time_taken} seconds.** +Total Users in Bot - {total} +**Sent to** : `{success} users.` +**Failed for** : `{fail} user(s).`""", + ) + + +@callback("setter", owner=True) +async def setting(event): + await event.edit( + "Choose from the below options -", + buttons=_settings, + ) + + +@callback("tz", owner=True) +async def timezone_(event): + await event.delete() + if tz is None: + return await event.client.send_message( + event.sender_id, + f"`pytz` is not installed, TimeZone feature is unavailable.\n\nInstall it via `{HNDLR}update` or `{HNDLR}bash pip install pytz`.", + ) + pru = event.sender_id + var = "TIMEZONE" + name = "Timezone" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "Send Your TimeZone From This List [Check From Here](http://www.timezoneconverter.com/cgi-bin/findzone.tzc)" + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message( + "Cancelled!!", + buttons=get_back_button("mainmenu"), + ) + try: + tz(themssg) + await setit(event, var, themssg) + await conv.send_message( + f"{name} changed to {themssg}\n", + buttons=get_back_button("mainmenu"), + ) + except BaseException: + await conv.send_message( + "Wrong TimeZone, Try again", + buttons=get_back_button("mainmenu"), + ) diff --git a/assistant/ytdl.py b/assistant/ytdl.py new file mode 100644 index 0000000000000000000000000000000000000000..2cc8660428c02421584e6320eaafda251669c025 --- /dev/null +++ b/assistant/ytdl.py @@ -0,0 +1,307 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +import os +import re + +try: + from PIL import Image +except ImportError: + Image = None +from telethon import Button +from telethon.errors.rpcerrorlist import FilePartLengthInvalidError, MediaEmptyError +from telethon.tl.types import DocumentAttributeAudio, DocumentAttributeVideo +from telethon.tl.types import InputWebDocument as wb + +from pyUltroid.fns.helper import ( + bash, + fast_download, + humanbytes, + numerize, + time_formatter, +) +from pyUltroid.fns.ytdl import dler, get_buttons, get_formats + +from . import LOGS, asst, callback, in_pattern, udB + +try: + from youtubesearchpython import VideosSearch +except ImportError: + LOGS.info("'youtubesearchpython' not installed!") + VideosSearch = None + + +ytt = "https://graph.org/file/afd04510c13914a06dd03.jpg" +_yt_base_url = "https://www.youtube.com/watch?v=" +BACK_BUTTON = {} + + +@in_pattern("yt", owner=True) +async def _(event): + try: + string = event.text.split(" ", maxsplit=1)[1] + except IndexError: + fuk = event.builder.article( + title="Search Something", + thumb=wb(ytt, 0, "image/jpeg", []), + text="**YᴏᴜTᴜʙᴇ Sᴇᴀʀᴄʜ**\n\nYou didn't search anything", + buttons=Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", + query="yt ", + same_peer=True, + ), + ) + await event.answer([fuk]) + return + results = [] + search = VideosSearch(string, limit=50) + nub = search.result() + nibba = nub["result"] + for v in nibba: + ids = v["id"] + link = _yt_base_url + ids + title = v["title"] + duration = v["duration"] + views = v["viewCount"]["short"] + publisher = v["channel"]["name"] + published_on = v["publishedTime"] + description = ( + v["descriptionSnippet"][0]["text"] + if v.get("descriptionSnippet") + and len(v["descriptionSnippet"][0]["text"]) < 500 + else "None" + ) + thumb = f"https://i.ytimg.com/vi/{ids}/hqdefault.jpg" + text = f"**Title: [{title}]({link})**\n\n" + text += f"`Description: {description}\n\n" + text += f"「 Duration: {duration} 」\n" + text += f"「 Views: {views} 」\n" + text += f"「 Publisher: {publisher} 」\n" + text += f"「 Published on: {published_on} 」`" + desc = f"{title}\n{duration}" + file = wb(thumb, 0, "image/jpeg", []) + buttons = [ + [ + Button.inline("Audio", data=f"ytdl:audio:{ids}"), + Button.inline("Video", data=f"ytdl:video:{ids}"), + ], + [ + Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", + query="yt ", + same_peer=True, + ), + Button.switch_inline( + "Sʜᴀʀᴇ", + query=f"yt {string}", + same_peer=False, + ), + ], + ] + BACK_BUTTON.update({ids: {"text": text, "buttons": buttons}}) + results.append( + await event.builder.article( + type="photo", + title=title, + description=desc, + thumb=file, + content=file, + text=text, + include_media=True, + buttons=buttons, + ), + ) + await event.answer(results[:50]) + + +@callback( + re.compile( + "ytdl:(.*)", + ), + owner=True, +) +async def _(e): + _e = e.pattern_match.group(1).strip().decode("UTF-8") + _lets_split = _e.split(":") + _ytdl_data = await dler(e, _yt_base_url + _lets_split[1]) + _data = get_formats(_lets_split[0], _lets_split[1], _ytdl_data) + _buttons = get_buttons(_data) + _text = ( + "`Select Your Format.`" + if _buttons + else "`Error downloading from YouTube.\nTry Restarting your bot.`" + ) + + await e.edit(_text, buttons=_buttons) + + +@callback( + re.compile( + "ytdownload:(.*)", + ), + owner=True, +) +async def _(event): + url = event.pattern_match.group(1).strip().decode("UTF-8") + lets_split = url.split(":") + vid_id = lets_split[2] + link = _yt_base_url + vid_id + format = lets_split[1] + try: + ext = lets_split[3] + except IndexError: + ext = "mp3" + if lets_split[0] == "audio": + opts = { + "format": "bestaudio", + "addmetadata": True, + "key": "FFmpegMetadata", + "prefer_ffmpeg": True, + "geo_bypass": True, + "outtmpl": f"%(id)s.{ext}", + "logtostderr": False, + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": ext, + "preferredquality": format, + }, + {"key": "FFmpegMetadata"}, + ], + } + + ytdl_data = await dler(event, link, opts, True) + title = ytdl_data["title"] + if ytdl_data.get("artist"): + artist = ytdl_data["artist"] + elif ytdl_data.get("creator"): + artist = ytdl_data["creator"] + elif ytdl_data.get("channel"): + artist = ytdl_data["channel"] + views = numerize(ytdl_data.get("view_count")) or 0 + thumb, _ = await fast_download(ytdl_data["thumbnail"], filename=f"{vid_id}.jpg") + + likes = numerize(ytdl_data.get("like_count")) or 0 + duration = ytdl_data.get("duration") or 0 + description = ( + ytdl_data["description"] + if len(ytdl_data["description"]) < 100 + else ytdl_data["description"][:100] + ) + description = description or "None" + filepath = f"{vid_id}.{ext}" + if not os.path.exists(filepath): + filepath = f"{filepath}.{ext}" + size = os.path.getsize(filepath) + file, _ = await event.client.fast_uploader( + filepath, + filename=f"{title}.{ext}", + show_progress=True, + event=event, + to_delete=True, + ) + + attributes = [ + DocumentAttributeAudio( + duration=int(duration), + title=title, + performer=artist, + ), + ] + elif lets_split[0] == "video": + opts = { + "format": str(format), + "addmetadata": True, + "key": "FFmpegMetadata", + "prefer_ffmpeg": True, + "geo_bypass": True, + "outtmpl": f"%(id)s.{ext}", + "logtostderr": False, + "postprocessors": [{"key": "FFmpegMetadata"}], + } + + ytdl_data = await dler(event, link, opts, True) + title = ytdl_data["title"] + if ytdl_data.get("artist"): + artist = ytdl_data["artist"] + elif ytdl_data.get("creator"): + artist = ytdl_data["creator"] + elif ytdl_data.get("channel"): + artist = ytdl_data["channel"] + views = numerize(ytdl_data.get("view_count")) or 0 + thumb, _ = await fast_download(ytdl_data["thumbnail"], filename=f"{vid_id}.jpg") + + try: + Image.open(thumb).save(thumb, "JPEG") + except Exception as er: + LOGS.exception(er) + thumb = None + description = ( + ytdl_data["description"] + if len(ytdl_data["description"]) < 100 + else ytdl_data["description"][:100] + ) + likes = numerize(ytdl_data.get("like_count")) or 0 + hi, wi = ytdl_data.get("height") or 720, ytdl_data.get("width") or 1280 + duration = ytdl_data.get("duration") or 0 + filepath = f"{vid_id}.mkv" + if not os.path.exists(filepath): + filepath = f"{filepath}.webm" + size = os.path.getsize(filepath) + file, _ = await event.client.fast_uploader( + filepath, + filename=f"{title}.mkv", + show_progress=True, + event=event, + to_delete=True, + ) + + attributes = [ + DocumentAttributeVideo( + duration=int(duration), + w=wi, + h=hi, + supports_streaming=True, + ), + ] + description = description if description != "" else "None" + text = f"**Title: [{title}]({_yt_base_url}{vid_id})**\n\n" + text += f"`📝 Description: {description}\n\n" + text += f"「 Duration: {time_formatter(int(duration)*1000)} 」\n" + text += f"「 Artist: {artist} 」\n" + text += f"「 Views: {views} 」\n" + text += f"「 Likes: {likes} 」\n" + text += f"「 Size: {humanbytes(size)} 」`" + button = Button.switch_inline("Search More", query="yt ", same_peer=True) + try: + await event.edit( + text, + file=file, + buttons=button, + attributes=attributes, + thumb=thumb, + ) + except (FilePartLengthInvalidError, MediaEmptyError): + file = await asst.send_message( + udB.get_key("LOG_CHANNEL"), + text, + file=file, + buttons=button, + attributes=attributes, + thumb=thumb, + ) + await event.edit(text, file=file.media, buttons=button) + await bash(f"rm {vid_id}.jpg") + + +@callback(re.compile("ytdl_back:(.*)"), owner=True) +async def ytdl_back(event): + id_ = event.data_match.group(1).decode("utf-8") + if not BACK_BUTTON.get(id_): + return await event.answer("Query Expired! Search again 🔍") + await event.edit(**BACK_BUTTON[id_]) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..a35d69622415dcf1b34a253e18f6ec16fcf6d0cc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + worker: + build: . + environment: + REDIS_URI: $REDIS_URI + REDIS_PASSWORD: $REDIS_PASSWORD + SESSION: $SESSION + API_ID: $API_ID # defaults to None + API_HASH: $API_HASH # defaults to None + MONGO_URI: $MONGO_URI # defaults to None + BOT_TOKEN: $BOT_TOKEN # Not mandatory + LOG_CHANNEL: $LOG_CHANNEL # Not mandatory + DATABASE_URL: $DATABASE_URL # defaults to None + OKTETO_TOKEN: $OKTETO_TOKEN diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index 4de5557f9ab19e1cbacbb73c8062c29fbd5133b0..0000000000000000000000000000000000000000 --- a/entrypoint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -if [ -n "$MACHINE_ID_RAW" ]; then - printf '%s\n' "$MACHINE_ID_RAW" > /etc/machine-id - printf '%s\n' "$MACHINE_ID_RAW" > /var/lib/dbus/machine-id 2>/dev/null || true -fi - -# Execute the command as the current user (root) -exec "$@" diff --git a/heroku.yml b/heroku.yml new file mode 100644 index 0000000000000000000000000000000000000000..06f0fb74674aba55490fd175ca23549f0e13b079 --- /dev/null +++ b/heroku.yml @@ -0,0 +1,3 @@ +build: + docker: + ultroid: Dockerfile diff --git a/hf_space/.gitattributes b/hf_space/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..a6344aac8c09253b3b630fb776ae94478aa0275b --- /dev/null +++ b/hf_space/.gitattributes @@ -0,0 +1,35 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text diff --git a/install-termux b/install-termux new file mode 100644 index 0000000000000000000000000000000000000000..3c09a9e7387e553c9da6e1a46a14abc674d680cb --- /dev/null +++ b/install-termux @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in . + +if [ -d "resources" ] +then + echo "Current directory Identified.." +else + apt install git -y + git clone https://github.com/TeamUltroid/Ultroid + cd Ultroid +fi +bash resources/startup/termux.sh diff --git a/installer.sh b/installer.sh new file mode 100644 index 0000000000000000000000000000000000000000..51b43d102f87bd479f5b989890a99ada7adbd452 --- /dev/null +++ b/installer.sh @@ -0,0 +1,241 @@ +#!/usr/bin/env bash + +REPO="https://github.com/TeamUltroid/Ultroid.git" +CURRENT_DIR="$(pwd)" +ENV_FILE_PATH=".env" +DIR="/root/TeamUltroid" + +while [ $# -gt 0 ]; do + case "$1" in + --dir=*) + DIR="${1#*=}" || DIR="/root/TeamUltroid" + ;; + --branch=*) + BRANCH="${1#*=}" || BRANCH="main" + ;; + --env-file=*) + ENV_FILE_PATH="${1#*=}" || ENV_FILE_PATH=".env" + ;; + --no-root) + NO_ROOT=true + ;; + *) + echo "Unknown parameter passed: $1" + exit 1 + ;; + esac + shift +done + +check_dependencies() { + # check if debian + echo "Checking dependencies..." + # read file with root access + if ! [[ $(ls -l "/etc/sudoers" | cut -d " " -f1) =~ "r" ]]; then + # check dependencies if installed + echo -e "Root access not found. Checking if dependencies are installed." >&2 + if ! [ -x "$(command -v python3)" ] || ! [ -x "$(command -v python)" ]; then + echo -e "Python3 isn't installed. Please install python3.10 or higher to run this bot." >&2 + exit 1 + fi + if [ $(python3 -c "import sys; print(sys.version_info[1])") -lt 10 ] || [ $(python -c "import sys; print(sys.version_info[1])") -lt 10 ]; then + echo -e "Python 3.10 or higher is required to run this bot." >&2 + exit 1 + fi + # check if any of ffmpeg, mediainfo, neofetch, git is not installed + if ! command -v ffmpeg &>/dev/null || ! command -v mediainfo &>/dev/null || ! command -v neofetch &>/dev/null || ! command -v git &>/dev/null; then + echo -e "Some dependencies aren't installed. Please install ffmpeg, mediainfo, neofetch and git to run this bot." >&2 + exit 1 + fi + fi + if [ -x "$(command -v apt-get)" ]; then + echo -e "Installing dependencies..." + # check if any of ffmpeg, mediainfo, neofetch, git is not installed via dpkg + if dpkg -l | grep -q ffmpeg || dpkg -l | grep -q mediainfo || dpkg -l | grep -q neofetch || dpkg -l | grep -q git; then + sudo apt-get -qq -o=Dpkg::Use-Pty=0 update + sudo apt-get install -qq -o=Dpkg::Use-Pty=0 python3 python3-pip ffmpeg mediainfo neofetch git -y + fi + elif [ -x "$(command -v pacman)" ]; then + echo -e "Installing dependencies..." + if pacman -Q | grep -q ffmpeg || pacman -Q | grep -q mediainfo || pacman -Q | grep -q neofetch || pacman -Q | grep -q git; then + sudo pacman -Sy python python-pip git ffmpeg mediainfo neofetch --noconfirm + fi + else + echo -e "Unknown OS. Checking if dependecies are installed" >&2 + if ! [ -x "$(command -v python3)" ] || ! [ -x "$(command -v python)" ]; then + echo -e "Python3 isn't installed. Please install python3.10 or higher to run this bot." >&2 + exit 1 + fi + if [ $(python3 -c "import sys; print(sys.version_info[1])") -lt 10 ] || [ $(python -c "import sys; print(sys.version_info[1])") -lt 10 ]; then + echo -e "Python 3.10 or higher is required to run this bot." >&2 + exit 1 + fi + if ! command -v ffmpeg &>/dev/null || ! command -v mediainfo &>/dev/null || ! command -v neofetch &>/dev/null || ! command -v git &>/dev/null; then + echo -e "Some dependencies aren't installed. Please install ffmpeg, mediainfo, neofetch and git to run this bot." >&2 + exit 1 + fi + fi +} + +check_python() { + # check if python is installed + if ! command -v python3 &>/dev/null; then + echo -e "Python3 isn't installed. Please install python3.10 or higher to run this bot." + exit 1 + elif ! command -v python &>/dev/null; then + echo -e "Python3 isn't installed. Please install python3.10 or higher to run this bot." + exit 1 + fi + if [ $(python3 -c "import sys; print(sys.version_info[1])") -lt 10 ]; then + echo -e "Python 3.10 or higher is required to run this bot." + exit 1 + elif [ $(python -c "import sys; print(sys.version_info[1])") -lt 3 ]; then + if [ $(python -c "import sys; print(sys.version_info[1])") -lt 10 ]; then + echo -e "Python 3.10 or higher is required to run this bot." + exit 1 + fi + fi +} + +clone_repo() { + # check if pyultroid, startup, plugins folders exist + cd $DIR + if [ -d $DIR ]; then + if [ -d $DIR/.git ]; then + echo -e "Updating Ultroid ${BRANCH}... " + cd $DIR + git pull + currentbranch="$(git rev-parse --abbrev-ref HEAD)" + if [ ! $BRANCH ]; then + export BRANCH=$currentbranch + fi + case $currentbranch in + $BRANCH) + # do nothing + ;; + *) + echo -e "Switching to branch ${BRANCH}... " + echo -e $currentbranch + git checkout $BRANCH + ;; + esac + else + rm -rf $DIR + exit 1 + fi + if [ -d "addons" ]; then + cd addons + git pull + fi + return + else + if [ ! $BRANCH ]; then + export BRANCH="main" + fi + mkdir -p $DIR + echo -e "Cloning Ultroid ${BRANCH}... " + git clone -b $BRANCH $REPO $DIR + fi +} + +install_requirements() { + pip3 install -q --upgrade pip + echo -e "\n\nInstalling requirements... " + pip3 install -q --no-cache-dir -r $DIR/requirements.txt + pip3 install -q -r $DIR/resources/startup/optional-requirements.txt +} + +railways_dep() { + if [ $RAILWAY_STATIC_URL ]; then + echo -e "Installing YouTube dependency... " + pip3 install -q yt-dlp + fi +} + +misc_install() { + if [ $SETUP_PLAYWRIGHT ] + then + echo -e "Installing playwright." + pip3 install playwright + playwright install + fi + if [ $OKTETO_TOKEN ]; then + echo -e "Installing Okteto-CLI... " + curl https://get.okteto.com -sSfL | sh + elif [ $VCBOT ]; then + if [ -d $DIR/vcbot ]; then + cd $DIR/vcbot + git pull + else + echo -e "Cloning VCBOT.." + git clone https://github.com/TeamUltroid/VcBot $DIR/vcbot + fi + pip3 install pytgcalls==3.0.0.dev23 && pip3 install av -q --no-binary av + fi +} + +dep_install() { + echo -e "\n\nInstalling DB Requirement..." + if [ $MONGO_URI ]; then + echo -e " Installing MongoDB Requirements..." + pip3 install -q pymongo[srv] + elif [ $DATABASE_URL ]; then + echo -e " Installing PostgreSQL Requirements..." + pip3 install -q psycopg2-binary + elif [ $REDIS_URI ]; then + echo -e " Installing Redis Requirements..." + pip3 install -q redis hiredis + fi +} + +main() { + echo -e "Starting Ultroid Setup..." + if [ -d "pyUltroid" ] && [ -d "resources" ] && [ -d "plugins" ]; then + DIR=$CURRENT_DIR + fi + if [ -f $ENV_FILE_PATH ] + then + set -a + source <(cat $ENV_FILE_PATH | sed -e '/^#/d;/^\s*$/d' -e "s/'/'\\\''/g" -e "s/=\(.*\)/='\1'/g") + set +a + cp $ENV_FILE_PATH .env + fi + (check_dependencies) + (check_python) + (clone_repo) + (install_requirements) + (railways_dep) + (dep_install) + (misc_install) + echo -e "\n\nSetup Completed." +} + +if [ $NO_ROOT ]; then + echo -e "Running with non root" + main + return 0 +elif [ -t 0 ]; then + unameOut="$(uname -s)" + case "${unameOut}" in + Linux*) machine=Linux;; + Darwin*) machine=Mac;; + CYGWIN*) machine=Cygwin;; + MINGW*) machine=MinGw;; + *) machine="UNKNOWN:${unameOut}" + esac + if machine != "Linux"; then + echo -e "This script is only for Linux. Please use the Windows installer." + exit 1 + fi + # check if sudo is installed + if ! command -v sudo &>/dev/null; then + echo -e "Sudo isn't installed. Please install sudo to run this bot." + exit 1 + fi + sudo echo "Sudo permission granted." + main +else + echo "Not an interactive terminal, skipping sudo." + # run main function + main +fi diff --git a/multi_client.py b/multi_client.py new file mode 100644 index 0000000000000000000000000000000000000000..a53cfe7800d5fd3cbea7d61da161230003712ab1 --- /dev/null +++ b/multi_client.py @@ -0,0 +1,39 @@ +import asyncio +import os +import subprocess +import sys + +vars = ["API_ID", "API_HASH", "SESSION"] + +def _check(z): + new = [] + for var in vars: + ent = os.environ.get(var + z) + if not ent: + return False, new + new.append(ent) + return True, new + +for z in range(5): + n = str(z + 1) + if z == 0: + z = "" + fine, out = _check(str(z)) + if fine: + subprocess.Popen( + [sys.executable, "-m", "pyUltroid", out[0], out[1], out[2], out[3], out[4], n], + stdin=None, + stderr=None, + stdout=None, + cwd=None, + ) + +loop = asyncio.get_event_loop() + +try: + loop.run_forever() +except Exception as er: + print(er) +finally: + loop.close() + diff --git a/okteto-pipeline.yml b/okteto-pipeline.yml new file mode 100644 index 0000000000000000000000000000000000000000..4b883dbc2b6decb81569662360a454c867316733 --- /dev/null +++ b/okteto-pipeline.yml @@ -0,0 +1,3 @@ +icon: https://raw.githubusercontent.com/TeamUltroid/Ultroid/main/resources/extras/logo_readme.jpg +deploy: + - okteto deploy --build -f docker-compose.yml diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 5007b9780e9fe8df410f74ec3b410f6253193f44..0000000000000000000000000000000000000000 --- a/package-lock.json +++ /dev/null @@ -1,776 +0,0 @@ -{ - "name": "shellular-web", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "shellular-web", - "version": "1.0.0", - "dependencies": { - "express": "^4.19.2" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.15.1", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 484cff8349d5193d41692e90f1a8ac5fd4c97116..0000000000000000000000000000000000000000 --- a/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "shellular-web", - "version": "1.0.0", - "description": "Web UI for Shellular — scan QR code to connect your VPS remotely", - "type": "module", - "main": "app.js", - "scripts": { - "start": "node app.js", - "dev": "node --watch app.js", - "build": "mkdir -p dist && echo hello > dist/index.html" - }, - "dependencies": { - "express": "^4.19.2" - }, - "engines": { - "node": ">=20" - } -} diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..41b26eb0aa35bfa9f9970b20c0ce4006dd984da5 --- /dev/null +++ b/plugins/__init__.py @@ -0,0 +1,105 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import asyncio +import os +import time +from random import choice + +import requests +from telethon import Button, events +from telethon.tl import functions, types # pylint:ignore + +from pyUltroid import * +from pyUltroid._misc._assistant import asst_cmd, callback, in_pattern +from pyUltroid._misc._decorators import ultroid_cmd +from pyUltroid._misc._wrappers import eod, eor +from pyUltroid.dB import DEVLIST, ULTROID_IMAGES +from pyUltroid.fns.helper import * +from pyUltroid.fns.misc import * +from pyUltroid.fns.tools import * +from pyUltroid.startup._database import _BaseDatabase as Database +from pyUltroid.version import __version__, ultroid_version +from strings import get_help, get_string +from catbox import CatboxUploader + +udB: Database + +Redis = udB.get_key +con = TgConverter +quotly = Quotly() +OWNER_NAME = ultroid_bot.full_name +OWNER_ID = ultroid_bot.uid + +ultroid_bot: UltroidClient +asst: UltroidClient + +LOG_CHANNEL = udB.get_key("LOG_CHANNEL") + + +def inline_pic(): + INLINE_PIC = udB.get_key("INLINE_PIC") + if INLINE_PIC is None: + INLINE_PIC = choice(ULTROID_IMAGES) + elif INLINE_PIC == False: + INLINE_PIC = None + return INLINE_PIC + + +Telegraph = telegraph_client() +cat_uploader = CatboxUploader() + +upload_file = cat_uploader.upload_file + +List = [] +Dict = {} +InlinePlugin = {} +N = 0 +cmd = ultroid_cmd +STUFF = {} + +# Chats, which needs to be ignore for some cases +# Considerably, there can be many +# Feel Free to Add Any other... + +NOSPAM_CHAT = [ + -1001361294038, # UltroidSupportChat + -1001387666944, # PyrogramChat + -1001109500936, # TelethonChat + -1001050982793, # Python + -1001256902287, # DurovsChat + -1001473548283, # SharingUserbot +] + +KANGING_STR = [ + "Using Witchery to kang this sticker...", + "Plagiarising hehe...", + "Inviting this sticker over to my pack...", + "Kanging this sticker...", + "Hey that's a nice sticker!\nMind if I kang?!..", + "Hehe me stel ur stiker...", + "Ay look over there (☉。☉)!→\nWhile I kang this...", + "Roses are red violets are blue, kanging this sticker so my pack looks cool", + "Imprisoning this sticker...", + "Mr.Steal-Your-Sticker is stealing this sticker... ", +] + + +ATRA_COL = [ + "DarkCyan", + "DeepSkyBlue", + "DarkTurquoise", + "Cyan", + "LightSkyBlue", + "Turquoise", + "MediumVioletRed", + "Aquamarine", + "Lightcyan", + "Azure", + "Moccasin", + "PowderBlue", +] diff --git a/plugins/_chatactions.py b/plugins/_chatactions.py new file mode 100644 index 0000000000000000000000000000000000000000..cc7b3e018c078f9d05d87003a799023cc3d8e700 --- /dev/null +++ b/plugins/_chatactions.py @@ -0,0 +1,253 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import asyncio + +from telethon import events +from telethon.errors.rpcerrorlist import UserNotParticipantError +from telethon.tl.functions.channels import GetParticipantRequest +from telethon.utils import get_display_name + +from pyUltroid.dB import stickers +from pyUltroid.dB.echo_db import check_echo +from pyUltroid.dB.forcesub_db import get_forcesetting +from pyUltroid.dB.gban_mute_db import is_gbanned +from pyUltroid.dB.greetings_db import get_goodbye, get_welcome, must_thank +from pyUltroid.dB.nsfw_db import is_profan +from pyUltroid.fns.helper import inline_mention +from pyUltroid.fns.tools import async_searcher, create_tl_btn, get_chatbot_reply + +try: + from ProfanityDetector import detector +except ImportError: + detector = None +from . import LOG_CHANNEL, LOGS, asst, get_string, types, udB, ultroid_bot +from ._inline import something + + +@ultroid_bot.on(events.ChatAction()) +async def Function(event): + try: + await DummyHandler(event) + except Exception as er: + LOGS.exception(er) + + +async def DummyHandler(ult): + # clean chat actions + key = udB.get_key("CLEANCHAT") or [] + if ult.chat_id in key: + try: + await ult.delete() + except BaseException: + pass + + # thank members + if must_thank(ult.chat_id): + chat_count = (await ult.client.get_participants(ult.chat_id, limit=0)).total + if chat_count % 100 == 0: + stik_id = chat_count / 100 - 1 + sticker = stickers[stik_id] + await ult.respond(file=sticker) + # force subscribe + if ( + udB.get_key("FORCESUB") + and ((ult.user_joined or ult.user_added)) + and get_forcesetting(ult.chat_id) + ): + user = await ult.get_user() + if not user.bot: + joinchat = get_forcesetting(ult.chat_id) + try: + await ultroid_bot(GetParticipantRequest(int(joinchat), user.id)) + except UserNotParticipantError: + await ultroid_bot.edit_permissions( + ult.chat_id, user.id, send_messages=False + ) + res = await ultroid_bot.inline_query( + asst.me.username, f"fsub {user.id}_{joinchat}" + ) + await res[0].click(ult.chat_id, reply_to=ult.action_message.id) + + if ult.user_joined or ult.added_by: + user = await ult.get_user() + chat = await ult.get_chat() + # gbans and @UltroidBans checks + if udB.get_key("ULTROID_BANS"): + try: + is_banned = await async_searcher( + "https://bans.ultroid.tech/api/status", + json={"userId": user.id}, + post=True, + re_json=True, + ) + if is_banned["is_banned"]: + await ult.client.edit_permissions( + chat.id, + user.id, + view_messages=False, + ) + await ult.respond( + f'**@UltroidBans:** Banned user detected and banned!\n`{str(is_banned)}`.\nBan reason: {is_banned["reason"]}', + ) + + except BaseException: + pass + reason = is_gbanned(user.id) + if reason and chat.admin_rights: + try: + await ult.client.edit_permissions( + chat.id, + user.id, + view_messages=False, + ) + gban_watch = get_string("can_1").format(inline_mention(user), reason) + await ult.reply(gban_watch) + except Exception as er: + LOGS.exception(er) + + # greetings + elif get_welcome(ult.chat_id): + user = await ult.get_user() + chat = await ult.get_chat() + title = chat.title or "this chat" + count = ( + chat.participants_count + or (await ult.client.get_participants(chat, limit=0)).total + ) + mention = inline_mention(user) + name = user.first_name + fullname = get_display_name(user) + uu = user.username + username = f"@{uu}" if uu else mention + wel = get_welcome(ult.chat_id) + msgg = wel["welcome"] + med = wel["media"] or None + userid = user.id + msg = None + if msgg: + msg = msgg.format( + mention=mention, + group=title, + count=count, + name=name, + fullname=fullname, + username=username, + userid=userid, + ) + if wel.get("button"): + btn = create_tl_btn(wel["button"]) + await something(ult, msg, med, btn) + elif msg: + send = await ult.reply( + msg, + file=med, + ) + await asyncio.sleep(150) + await send.delete() + else: + await ult.reply(file=med) + elif (ult.user_left or ult.user_kicked) and get_goodbye(ult.chat_id): + user = await ult.get_user() + chat = await ult.get_chat() + title = chat.title or "this chat" + count = ( + chat.participants_count + or (await ult.client.get_participants(chat, limit=0)).total + ) + mention = inline_mention(user) + name = user.first_name + fullname = get_display_name(user) + uu = user.username + username = f"@{uu}" if uu else mention + wel = get_goodbye(ult.chat_id) + msgg = wel["goodbye"] + med = wel["media"] + userid = user.id + msg = None + if msgg: + msg = msgg.format( + mention=mention, + group=title, + count=count, + name=name, + fullname=fullname, + username=username, + userid=userid, + ) + if wel.get("button"): + btn = create_tl_btn(wel["button"]) + await something(ult, msg, med, btn) + elif msg: + send = await ult.reply( + msg, + file=med, + ) + await asyncio.sleep(150) + await send.delete() + else: + await ult.reply(file=med) + + +@ultroid_bot.on(events.NewMessage(incoming=True)) +async def chatBot_replies(e): + sender = await e.get_sender() + if not isinstance(sender, types.User) or sender.bot: + return + if check_echo(e.chat_id, e.sender_id): + try: + await e.respond(e.message) + except Exception as er: + LOGS.exception(er) + key = udB.get_key("CHATBOT_USERS") or {} + if e.text and key.get(e.chat_id) and sender.id in key[e.chat_id]: + msg = await get_chatbot_reply(e.message.message) + if msg: + sleep = udB.get_key("CHATBOT_SLEEP") or 1.5 + await asyncio.sleep(sleep) + await e.reply(msg) + chat = await e.get_chat() + if e.is_group and sender.username: + await uname_stuff(e.sender_id, sender.username, sender.first_name) + elif e.is_private and chat.username: + await uname_stuff(e.sender_id, chat.username, chat.first_name) + if detector and is_profan(e.chat_id) and e.text: + x, y = detector(e.text) + if y: + await e.delete() + + +@ultroid_bot.on(events.Raw(types.UpdateUserName)) +async def uname_change(e): + await uname_stuff(e.user_id, e.usernames[0] if e.usernames else None, e.first_name) + + +async def uname_stuff(id, uname, name): + if udB.get_key("USERNAME_LOG"): + old_ = udB.get_key("USERNAME_DB") or {} + old = old_.get(id) + # Ignore Name Logs + if old and old == uname: + return + if old and uname: + await asst.send_message( + LOG_CHANNEL, + get_string("can_2").format(old, uname), + ) + elif old: + await asst.send_message( + LOG_CHANNEL, + get_string("can_3").format(f"[{name}](tg://user?id={id})", old), + ) + elif uname: + await asst.send_message( + LOG_CHANNEL, + get_string("can_4").format(f"[{name}](tg://user?id={id})", uname), + ) + + old_[id] = uname + udB.set_key("USERNAME_DB", old_) diff --git a/plugins/_help.py b/plugins/_help.py new file mode 100644 index 0000000000000000000000000000000000000000..4bcbb449615bb022d2d1c08ed88d7f61778b35e2 --- /dev/null +++ b/plugins/_help.py @@ -0,0 +1,136 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from telethon.errors.rpcerrorlist import ( + BotInlineDisabledError, + BotMethodInvalidError, + BotResponseTimeoutError, +) +from telethon.tl.custom import Button + +from pyUltroid.dB._core import HELP, LIST +from pyUltroid.fns.tools import cmd_regex_replace + +from . import HNDLR, LOGS, OWNER_NAME, asst, get_string, inline_pic, udB, ultroid_cmd + +_main_help_menu = [ + [ + Button.inline(get_string("help_4"), data="uh_Official_"), + Button.inline(get_string("help_5"), data="uh_Addons_"), + ], + [ + Button.inline(get_string("help_6"), data="uh_VCBot_"), + Button.inline(get_string("help_7"), data="inlone"), + ], + [ + Button.inline(get_string("help_8"), data="ownr"), + Button.url( + get_string("help_9"), url=f"https://t.me/{asst.me.username}?start=set" + ), + ], + [Button.inline(get_string("help_10"), data="close")], +] + + +@ultroid_cmd(pattern="help( (.*)|$)") +async def _help(ult): + plug = ult.pattern_match.group(1).strip() + chat = await ult.get_chat() + if plug: + try: + if plug in HELP["Official"]: + output = f"**Plugin** - `{plug}`\n" + for i in HELP["Official"][plug]: + output += i + output += "\n© @TeamUltroid" + await ult.eor(output) + elif HELP.get("Addons") and plug in HELP["Addons"]: + output = f"**Plugin** - `{plug}`\n" + for i in HELP["Addons"][plug]: + output += i + output += "\n© @TeamUltroid" + await ult.eor(output) + elif HELP.get("VCBot") and plug in HELP["VCBot"]: + output = f"**Plugin** - `{plug}`\n" + for i in HELP["VCBot"][plug]: + output += i + output += "\n© @TeamUltroid" + await ult.eor(output) + else: + try: + x = get_string("help_11").format(plug) + for d in LIST[plug]: + x += HNDLR + d + x += "\n" + x += "\n© @TeamUltroid" + await ult.eor(x) + except BaseException: + file = None + compare_strings = [] + for file_name in LIST: + compare_strings.append(file_name) + value = LIST[file_name] + for j in value: + j = cmd_regex_replace(j) + compare_strings.append(j) + if j.strip() == plug: + file = file_name + break + if not file: + # the enter command/plugin name is not found + text = f"`{plug}` is not a valid plugin!" + best_match = None + for _ in compare_strings: + if plug in _ and not _.startswith("_"): + best_match = _ + break + if best_match: + text += f"\nDid you mean `{best_match}`?" + return await ult.eor(text) + output = f"**Command** `{plug}` **found in plugin** - `{file}`\n" + if file in HELP["Official"]: + for i in HELP["Official"][file]: + output += i + elif HELP.get("Addons") and file in HELP["Addons"]: + for i in HELP["Addons"][file]: + output += i + elif HELP.get("VCBot") and file in HELP["VCBot"]: + for i in HELP["VCBot"][file]: + output += i + output += "\n© @TeamUltroid" + await ult.eor(output) + except BaseException as er: + LOGS.exception(er) + await ult.eor("Error 🤔 occured.") + else: + try: + results = await ult.client.inline_query(asst.me.username, "ultd") + except BotMethodInvalidError: + z = [] + for x in LIST.values(): + z.extend(x) + cmd = len(z) + 10 + if udB.get_key("MANAGER") and udB.get_key("DUAL_HNDLR") == "/": + _main_help_menu[2:3] = [[Button.inline("• Manager Help •", "mngbtn")]] + return await ult.reply( + get_string("inline_4").format( + OWNER_NAME, + len(HELP["Official"]), + len(HELP["Addons"] if "Addons" in HELP else []), + cmd, + ), + file=inline_pic(), + buttons=_main_help_menu, + ) + except BotResponseTimeoutError: + return await ult.eor( + get_string("help_2").format(HNDLR), + ) + except BotInlineDisabledError: + return await ult.eor(get_string("help_3")) + await results[0].click(chat.id, reply_to=ult.reply_to_msg_id, hide_via=True) + await ult.delete() diff --git a/plugins/_inline.py b/plugins/_inline.py new file mode 100644 index 0000000000000000000000000000000000000000..93321f1ef292de4de9541fa0bb83b5228df9f23f --- /dev/null +++ b/plugins/_inline.py @@ -0,0 +1,455 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import re +import time +from datetime import datetime +from os import remove + +from git import Repo +from telethon import Button +from telethon.tl.types import InputWebDocument, Message +from telethon.utils import resolve_bot_file_id + +from pyUltroid._misc._assistant import callback, in_pattern +from pyUltroid.dB._core import HELP, LIST +from pyUltroid.fns.helper import gen_chlog, time_formatter, updater +from pyUltroid.fns.misc import split_list + +from . import ( + HNDLR, + LOGS, + OWNER_NAME, + InlinePlugin, + asst, + get_string, + inline_pic, + split_list, + start_time, + udB, +) +from ._help import _main_help_menu + +# ================================================# + +helps = get_string("inline_1") + +add_ons = udB.get_key("ADDONS") + +zhelps = get_string("inline_3") if add_ons is False else get_string("inline_2") +PLUGINS = HELP.get("Official", []) +ADDONS = HELP.get("Addons", []) +upage = 0 +# ============================================# + +# --------------------BUTTONS--------------------# + +SUP_BUTTONS = [ + [ + Button.url("• Repo •", url="https://github.com/TeamUltroid/Ultroid"), + Button.url("• Support •", url="t.me/UltroidSupportChat"), + ], +] + +# --------------------BUTTONS--------------------# + + +@in_pattern(owner=True, func=lambda x: not x.text) +async def inline_alive(o): + TLINK = inline_pic() or "https://graph.org/file/74d6259983e0642923fdb.jpg" + MSG = "• **Ultroid Userbot •**" + WEB0 = InputWebDocument( + "https://graph.org/file/acd4f5d61369f74c5e7a7.jpg", 0, "image/jpg", [] + ) + RES = [ + await o.builder.article( + type="photo", + text=MSG, + include_media=True, + buttons=SUP_BUTTONS, + title="Ultroid Userbot", + description="Userbot | Telethon", + url=TLINK, + thumb=WEB0, + content=InputWebDocument(TLINK, 0, "image/jpg", []), + ) + ] + await o.answer( + RES, + private=True, + cache_time=300, + switch_pm="👥 ULTROID PORTAL", + switch_pm_param="start", + ) + + +@in_pattern("ultd", owner=True) +async def inline_handler(event): + z = [] + for x in LIST.values(): + z.extend(x) + text = get_string("inline_4").format( + OWNER_NAME, + len(HELP.get("Official", [])), + len(HELP.get("Addons", [])), + len(z), + ) + if inline_pic(): + result = await event.builder.photo( + file=inline_pic(), + link_preview=False, + text=text, + buttons=_main_help_menu, + ) + else: + result = await event.builder.article( + title="Ultroid Help Menu", text=text, buttons=_main_help_menu + ) + await event.answer([result], private=True, cache_time=300, gallery=True) + + +@in_pattern("pasta", owner=True) +async def _(event): + ok = event.text.split("-")[1] + if not ok.startswith("http"): + link = f"https://spaceb.in/{ok}" + raw = f"https://spaceb.in/api/v1/documents/{ok}/raw" + else: + link = ok + raw = f"{ok}/raw" + result = await event.builder.article( + title="Paste", + text="Pasted to Spacebin 🌌", + buttons=[ + [ + Button.url("SpaceBin", url=link), + Button.url("Raw", url=raw), + ], + ], + ) + await event.answer([result]) + + +@callback("ownr", owner=True) +async def setting(event): + z = [] + for x in LIST.values(): + z.extend(x) + await event.edit( + get_string("inline_4").format( + OWNER_NAME, + len(HELP.get("Official", [])), + len(HELP.get("Addons", [])), + len(z), + ), + file=inline_pic(), + link_preview=False, + buttons=[ + [ + Button.inline("•Pɪɴɢ•", data="pkng"), + Button.inline("•Uᴘᴛɪᴍᴇ•", data="upp"), + ], + [ + Button.inline("•Stats•", data="alive"), + Button.inline("•Uᴘᴅᴀᴛᴇ•", data="doupdate"), + ], + [Button.inline("« Bᴀᴄᴋ", data="open")], + ], + ) + + +_strings = {"Official": helps, "Addons": zhelps, "VCBot": get_string("inline_6")} + + +@callback(re.compile("uh_(.*)"), owner=True) +async def help_func(ult): + key, count = ult.data_match.group(1).decode("utf-8").split("_") + if key == "VCBot" and HELP.get("VCBot") is None: + return await ult.answer(get_string("help_12"), alert=True) + elif key == "Addons" and HELP.get("Addons") is None: + return await ult.answer(get_string("help_13").format(HNDLR), alert=True) + if "|" in count: + _, count = count.split("|") + count = int(count) if count else 0 + text = _strings.get(key, "").format(OWNER_NAME, len(HELP.get(key))) + await ult.edit(text, buttons=page_num(count, key), link_preview=False) + + +@callback(re.compile("uplugin_(.*)"), owner=True) +async def uptd_plugin(event): + key, file = event.data_match.group(1).decode("utf-8").split("_") + index = None + if "|" in file: + file, index = file.split("|") + key_ = HELP.get(key, []) + hel_p = f"Plugin Name - `{file}`\n" + help_ = "" + try: + for i in key_[file]: + help_ += i + except BaseException: + if file in LIST: + help_ = get_string("help_11").format(file) + for d in LIST[file]: + help_ += HNDLR + d + help_ += "\n" + if not help_: + help_ = f"{file} has no Detailed Help!" + help_ += "\n© @TeamUltroid" + buttons = [] + if inline_pic(): + data = f"sndplug_{key}_{file}" + if index is not None: + data += f"|{index}" + buttons.append( + [ + Button.inline( + "« Sᴇɴᴅ Pʟᴜɢɪɴ »", + data=data, + ) + ] + ) + data = f"uh_{key}_" + if index is not None: + data += f"|{index}" + buttons.append( + [ + Button.inline("« Bᴀᴄᴋ", data=data), + ] + ) + try: + await event.edit(help_, buttons=buttons) + except Exception as er: + LOGS.exception(er) + help = f"Do `{HNDLR}help {key}` to get list of commands." + await event.edit(help, buttons=buttons) + + +@callback(data="doupdate", owner=True) +async def _(event): + if not await updater(): + return await event.answer(get_string("inline_9"), cache_time=0, alert=True) + if not inline_pic(): + return await event.answer(f"Do '{HNDLR}update' to update..") + repo = Repo.init() + changelog, tl_chnglog = await gen_chlog( + repo, f"HEAD..upstream/{repo.active_branch}" + ) + changelog_str = changelog + "\n\n" + get_string("inline_8") + if len(changelog_str) > 1024: + await event.edit(get_string("upd_4")) + with open("ultroid_updates.txt", "w+") as file: + file.write(tl_chnglog) + await event.edit( + get_string("upd_5"), + file="ultroid_updates.txt", + buttons=[ + [Button.inline("• Uᴘᴅᴀᴛᴇ Nᴏᴡ •", data="updatenow")], + [Button.inline("« Bᴀᴄᴋ", data="ownr")], + ], + ) + remove("ultroid_updates.txt") + else: + await event.edit( + changelog_str, + buttons=[ + [Button.inline("Update Now", data="updatenow")], + [Button.inline("« Bᴀᴄᴋ", data="ownr")], + ], + parse_mode="html", + ) + + +@callback(data="pkng", owner=True) +async def _(event): + start = datetime.now() + end = datetime.now() + ms = (end - start).microseconds + pin = f"🌋Pɪɴɢ = {ms} microseconds" + await event.answer(pin, cache_time=0, alert=True) + + +@callback(data="upp", owner=True) +async def _(event): + uptime = time_formatter((time.time() - start_time) * 1000) + pin = f"🙋Uᴘᴛɪᴍᴇ = {uptime}" + await event.answer(pin, cache_time=0, alert=True) + + +@callback(data="inlone", owner=True) +async def _(e): + _InButtons = [ + Button.switch_inline(_, query=InlinePlugin[_], same_peer=True) + for _ in list(InlinePlugin.keys()) + ] + InButtons = split_list(_InButtons, 2) + + button = InButtons.copy() + button.append( + [ + Button.inline("« Bᴀᴄᴋ", data="open"), + ], + ) + await e.edit(buttons=button, link_preview=False) + + +@callback(data="open", owner=True) +async def opner(event): + z = [] + for x in LIST.values(): + z.extend(x) + await event.edit( + get_string("inline_4").format( + OWNER_NAME, + len(HELP.get("Official", [])), + len(HELP.get("Addons", [])), + len(z), + ), + buttons=_main_help_menu, + link_preview=False, + ) + + +@callback(data="close", owner=True) +async def on_plug_in_callback_query_handler(event): + await event.edit( + get_string("inline_5"), + buttons=Button.inline("Oᴘᴇɴ Aɢᴀɪɴ", data="open"), + ) + + +def page_num(index, key): + rows = udB.get_key("HELP_ROWS") or 5 + cols = udB.get_key("HELP_COLUMNS") or 2 + loaded = HELP.get(key, []) + emoji = udB.get_key("EMOJI_IN_HELP") or "✘" + List = [ + Button.inline(f"{emoji} {x} {emoji}", data=f"uplugin_{key}_{x}|{index}") + for x in sorted(loaded) + ] + all_ = split_list(List, cols) + fl_ = split_list(all_, rows) + try: + new_ = fl_[index] + except IndexError: + new_ = fl_[0] if fl_ else [] + index = 0 + if index == 0 and len(fl_) == 1: + new_.append([Button.inline("« Bᴀᴄᴋ »", data="open")]) + else: + new_.append( + [ + Button.inline( + "« Pʀᴇᴠɪᴏᴜs", + data=f"uh_{key}_{index-1}", + ), + Button.inline("« Bᴀᴄᴋ »", data="open"), + Button.inline( + "Nᴇxᴛ »", + data=f"uh_{key}_{index+1}", + ), + ] + ) + return new_ + + +# --------------------------------------------------------------------------------- # + + +STUFF = {} + + +@in_pattern("stf(.*)", owner=True) +async def ibuild(e): + n = e.pattern_match.group(1).strip() + builder = e.builder + if not (n and n.isdigit()): + return + ok = STUFF.get(int(n)) + txt = ok.get("msg") + pic = ok.get("media") + btn = ok.get("button") + if not (pic or txt): + txt = "Hey!" + if pic: + try: + include_media = True + mime_type, _pic = None, None + cont, results = None, None + try: + ext = str(pic).split(".")[-1].lower() + except BaseException: + ext = None + if ext in ["img", "jpg", "png"]: + _type = "photo" + mime_type = "image/jpg" + elif ext in ["mp4", "mkv", "gif"]: + mime_type = "video/mp4" + _type = "gif" + else: + try: + if "telethon.tl.types" in str(type(pic)): + _pic = pic + else: + _pic = resolve_bot_file_id(pic) + except BaseException: + pass + if _pic: + results = [ + await builder.document( + _pic, + title="Ultroid Op", + text=txt, + description="@TeamUltroid", + buttons=btn, + link_preview=False, + ) + ] + else: + _type = "article" + include_media = False + if not results: + if include_media: + cont = InputWebDocument(pic, 0, mime_type, []) + results = [ + await builder.article( + title="Ultroid Op", + type=_type, + text=txt, + description="@TeamUltroid", + include_media=include_media, + buttons=btn, + thumb=cont, + content=cont, + link_preview=False, + ) + ] + return await e.answer(results) + except Exception as er: + LOGS.exception(er) + result = [ + await builder.article("Ultroid Op", text=txt, link_preview=False, buttons=btn) + ] + await e.answer(result) + + +async def something(e, msg, media, button, reply=True, chat=None): + if e.client._bot: + return await e.reply(msg, file=media, buttons=button) + num = len(STUFF) + 1 + STUFF.update({num: {"msg": msg, "media": media, "button": button}}) + try: + res = await e.client.inline_query(asst.me.username, f"stf{num}") + return await res[0].click( + chat or e.chat_id, + reply_to=bool(isinstance(e, Message) and reply), + hide_via=True, + silent=True, + ) + + except Exception as er: + LOGS.exception(er) diff --git a/plugins/_ultroid.py b/plugins/_ultroid.py new file mode 100644 index 0000000000000000000000000000000000000000..afe3cf8a7225df88c8cf75a0a9c2bfca7a3d0600 --- /dev/null +++ b/plugins/_ultroid.py @@ -0,0 +1,66 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from telethon.errors import ( + BotMethodInvalidError, + ChatSendInlineForbiddenError, + ChatSendMediaForbiddenError, +) + +from . import LOG_CHANNEL, LOGS, Button, asst, eor, get_string, ultroid_cmd + +REPOMSG = """ +• **ULTROID USERBOT** •\n +• Repo - [Click Here](https://github.com/TeamUltroid/Ultroid) +• Addons - [Click Here](https://github.com/TeamUltroid/UltroidAddons) +• Support - @UltroidSupportChat +""" + +RP_BUTTONS = [ + [ + Button.url(get_string("bot_3"), "https://github.com/TeamUltroid/Ultroid"), + Button.url("Addons", "https://github.com/TeamUltroid/UltroidAddons"), + ], + [Button.url("Support Group", "t.me/UltroidSupportChat")], +] + +ULTSTRING = """🎇 **Thanks for Deploying Ultroid Userbot!** + +• Here, are the Some Basic stuff from, where you can Know, about its Usage.""" + + +@ultroid_cmd( + pattern="repo$", + manager=True, +) +async def repify(e): + try: + q = await e.client.inline_query(asst.me.username, "") + await q[0].click(e.chat_id) + return await e.delete() + except ( + ChatSendInlineForbiddenError, + ChatSendMediaForbiddenError, + BotMethodInvalidError, + ): + pass + except Exception as er: + LOGS.info(f"Error while repo command : {str(er)}") + await e.eor(REPOMSG) + + +@ultroid_cmd(pattern="ultroid$") +async def useUltroid(rs): + button = Button.inline("Start >>", "initft_2") + msg = await asst.send_message( + LOG_CHANNEL, + ULTSTRING, + file="https://graph.org/file/54a917cc9dbb94733ea5f.jpg", + buttons=button, + ) + if not (rs.chat_id == LOG_CHANNEL and rs.client._bot): + await eor(rs, f"**[Click Here]({msg.message_link})**") diff --git a/plugins/_userlogs.py b/plugins/_userlogs.py new file mode 100644 index 0000000000000000000000000000000000000000..cc6470ce0d2549f201200852dca056845212eb60 --- /dev/null +++ b/plugins/_userlogs.py @@ -0,0 +1,299 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import os +import re + +from telethon.errors.rpcerrorlist import ( + ChannelPrivateError, + ChatWriteForbiddenError, + MediaCaptionTooLongError, + MediaEmptyError, + MessageTooLongError, + PeerIdInvalidError, + UserNotParticipantError, +) +from telethon.tl.types import MessageEntityMention, MessageEntityMentionName, User +from telethon.utils import get_display_name + +from pyUltroid.dB.botchat_db import tag_add, who_tag + +from . import ( + LOG_CHANNEL, + LOGS, + Button, + asst, + callback, + events, + get_string, + inline_mention, + udB, + ultroid_bot, +) + +CACHE_SPAM = {} +TAG_EDITS = {} + + +@ultroid_bot.on( + events.NewMessage( + incoming=True, + func=lambda e: (e.mentioned), + ), +) +async def all_messages_catcher(e): + x = await e.get_sender() + if isinstance(x, User) and (x.bot or x.verified): + return + if not udB.get_key("TAG_LOG"): + return + NEEDTOLOG = udB.get_key("TAG_LOG") + if e.chat_id == NEEDTOLOG: + return + buttons = await parse_buttons(e) + try: + sent = await asst.send_message(NEEDTOLOG, e.message, buttons=buttons) + if TAG_EDITS.get(e.chat_id): + TAG_EDITS[e.chat_id].update({e.id: {"id": sent.id, "msg": e}}) + else: + TAG_EDITS.update({e.chat_id: {e.id: {"id": sent.id, "msg": e}}}) + tag_add(sent.id, e.chat_id, e.id) + except MediaEmptyError as er: + LOGS.debug(f"handling {er}.") + try: + msg = await asst.get_messages(e.chat_id, ids=e.id) + sent = await asst.send_message(NEEDTOLOG, msg, buttons=buttons) + if TAG_EDITS.get(e.chat_id): + TAG_EDITS[e.chat_id].update({e.id: {"id": sent.id, "msg": e}}) + else: + TAG_EDITS.update({e.chat_id: {e.id: {"id": sent.id, "msg": e}}}) + tag_add(sent.id, e.chat_id, e.id) + except Exception as me: + if not isinstance(me, (PeerIdInvalidError, ValueError)): + LOGS.exception(me) + if e.photo or e.sticker or e.gif: + try: + media = await e.download_media() + sent = await asst.send_message( + NEEDTOLOG, e.message.text, file=media, buttons=buttons + ) + if TAG_EDITS.get(e.chat_id): + TAG_EDITS[e.chat_id].update({e.id: {"id": sent.id, "msg": e}}) + else: + TAG_EDITS.update({e.chat_id: {e.id: {"id": sent.id, "msg": e}}}) + return os.remove(media) + except Exception as er: + LOGS.exception(er) + await asst.send_message(NEEDTOLOG, get_string("com_4"), buttons=buttons) + except (PeerIdInvalidError, ValueError) as er: + LOGS.exception(er) + try: + CACHE_SPAM[NEEDTOLOG] + except KeyError: + await asst.send_message( + udB.get_key("LOG_CHANNEL"), get_string("userlogs_1") + ) + CACHE_SPAM.update({NEEDTOLOG: True}) + except ChatWriteForbiddenError: + try: + await asst.get_permissions(NEEDTOLOG, "me") + MSG = get_string("userlogs_4") + except UserNotParticipantError: + MSG = get_string("userlogs_2") + try: + CACHE_SPAM[NEEDTOLOG] + except KeyError: + await asst.send_message(LOG_CHANNEL, MSG) + CACHE_SPAM.update({NEEDTOLOG: True}) + except Exception as er: + LOGS.exception(er) + + +if udB.get_key("TAG_LOG"): + + @ultroid_bot.on(events.MessageEdited(func=lambda x: not x.out)) + async def upd_edits(event): + x = event.sender + if isinstance(x, User) and (x.bot or x.verified): + return + if event.chat_id not in TAG_EDITS: + if event.sender_id == udB.get_key("TAG_LOG"): + return + if event.is_private: + return + if entities := event.get_entities_text(): + is_self = False + username = event.client.me.username + if username: + username = username.lower() + for ent, text in entities: + if isinstance(ent, MessageEntityMention): + is_self = text[1:].lower() == username + elif isinstance(ent, MessageEntityMentionName): + is_self = ent.user_id == event.client.me.id + if is_self: + text = f"**#Edited & #Mentioned**\n\n{event.text}" + try: + sent = await asst.send_message( + udB.get_key("TAG_LOG"), + text, + buttons=await parse_buttons(event), + ) + except Exception as er: + return LOGS.exception(er) + if TAG_EDITS.get(event.chat_id): + TAG_EDITS[event.chat_id].update({event.id: {"id": sent.id}}) + else: + TAG_EDITS.update({event.chat_id: {event.id: {"id": sent.id}}}) + return + d_ = TAG_EDITS[event.chat_id] + if not d_.get(event.id): + return + d_ = d_[event.id] + if d_["msg"].text == event.text: + return + msg = None + if d_.get("count"): + d_["count"] += 1 + else: + msg = True + d_.update({"count": 1}) + if d_["count"] > 10: + return # some limit to take edits + try: + MSG = await asst.get_messages(udB.get_key("TAG_LOG"), ids=d_["id"]) + except Exception as er: + return LOGS.exception(er) + TEXT = MSG.text + if msg: + TEXT += "\n\n🖋 **Later Edited to !**" + strf = event.edit_date.strftime("%H:%M:%S") + if "\n" not in event.text: + TEXT += f"\n• `{strf}` : {event.text}" + else: + TEXT += f"\n• `{strf}` :\n-> {event.text}" + if d_["count"] == 10: + TEXT += "\n\n• __Only the first 10 Edits are shown.__" + try: + msg = await MSG.edit(TEXT, buttons=await parse_buttons(event)) + d_["msg"] = msg + except (MessageTooLongError, MediaCaptionTooLongError): + del TAG_EDITS[event.chat_id][event.id] + except Exception as er: + LOGS.exception(er) + + @ultroid_bot.on( + events.NewMessage( + outgoing=True, + chats=[udB.get_key("TAG_LOG")], + func=lambda e: e.reply_to, + ) + ) + async def idk(e): + id = e.reply_to_msg_id + chat, msg = who_tag(id) + if chat and msg: + try: + await ultroid_bot.send_message(chat, e.message, reply_to=msg) + except BaseException as er: + LOGS.exception(er) + + +# log for assistant/user joins/add + + +async def when_added_or_joined(event): + user = await event.get_user() + chat = await event.get_chat() + if not (user and user.is_self): + return + if getattr(chat, "username", None): + chat = f"[{chat.title}](https://t.me/{chat.username}/{event.action_message.id})" + else: + chat = f"[{chat.title}](https://t.me/c/{chat.id}/{event.action_message.id})" + key = "bot" if event.client._bot else "user" + buttons = Button.inline( + get_string("userlogs_3"), data=f"leave_ch_{event.chat_id}|{key}" + ) + if event.user_added: + tmp = event.added_by + text = f"#ADD_LOG\n\n{inline_mention(tmp)} just added {inline_mention(user)} to {chat}." + elif event.from_request: + text = f"#APPROVAL_LOG\n\n{inline_mention(user)} just got Chat Join Approval to {chat}." + else: + text = f"#JOIN_LOG\n\n{inline_mention(user)} just joined {chat}." + await asst.send_message(udB.get_key("LOG_CHANNEL"), text, buttons=buttons) + + +asst.add_event_handler( + when_added_or_joined, events.ChatAction(func=lambda x: x.user_added) +) +ultroid_bot.add_event_handler( + when_added_or_joined, + events.ChatAction(func=lambda x: x.user_added or x.user_joined), +) +_client = {"bot": asst, "user": ultroid_bot} + + +@callback( + re.compile( + "leave_ch_(.*)", + ), + from_users=[ultroid_bot.uid], +) +async def leave_ch_at(event): + cht = event.data_match.group(1).decode("UTF-8") + ch_id, client = cht.split("|") + try: + client = _client[client] + except KeyError: + return + try: + name = (await client.get_entity(int(ch_id))).title + await client.delete_dialog(int(ch_id)) + except UserNotParticipantError: + pass + except ChannelPrivateError: + return await event.edit( + "`[CANT_ACCESS_CHAT]` `Maybe already left or got banned.`" + ) + except Exception as er: + LOGS.exception(er) + return await event.answer(str(er)) + await event.edit(get_string("userlogs_5").format(name)) + + +@callback("do_nothing") +async def _(event): + await event.answer() + + +async def parse_buttons(event): + y, x = event.chat, event.sender + where_n, who_n = get_display_name(y), get_display_name(x) + where_l = event.message_link + buttons = [[Button.url(where_n, where_l)]] + if isinstance(x, User) and x.username: + try: + buttons.append( + [Button.mention(who_n, await asst.get_input_entity(x.username))] + ) + except Exception as er: + LOGS.exception(er) + buttons.append([Button.url(who_n, f"t.me/{x.username}")]) + elif getattr(x, "username"): + buttons.append([Button.url(who_n, f"t.me/{x.username}")]) + else: + buttons.append([Button.url(who_n, where_l)]) + replied = await event.get_reply_message() + if replied and replied.out: + button = Button.url("Replied to", replied.message_link) + if len(who_n) > 7: + buttons.append([button]) + else: + buttons[-1].append(button) + return buttons diff --git a/plugins/_wspr.py b/plugins/_wspr.py new file mode 100644 index 0000000000000000000000000000000000000000..6d717bc2489dd7de2fd7beb148bc9b9200fdb1e3 --- /dev/null +++ b/plugins/_wspr.py @@ -0,0 +1,204 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import re + +from telethon import Button +from telethon.errors.rpcerrorlist import ( + BotInlineDisabledError, + BotResponseTimeoutError, + MessageNotModifiedError, +) +from telethon.tl import types +from telethon.tl.functions.users import GetFullUserRequest as gu + +from . import ( + HNDLR, + LOGS, + asst, + callback, + get_string, + in_pattern, + inline_mention, + ultroid_bot, + ultroid_cmd, +) + +buddhhu = {} + + +@ultroid_cmd( + pattern="wspr( (.*)|$)", +) +async def _(e): + if e.reply_to_msg_id: + okk = await e.get_reply_message() + put = f"@{okk.sender.username}" if okk.sender.username else okk.sender_id + else: + put = e.pattern_match.group(1).strip() + if put: + try: + results = await e.client.inline_query(asst.me.username, f"msg {put}") + except BotResponseTimeoutError: + return await e.eor( + get_string("help_2").format(HNDLR), + ) + except BotInlineDisabledError: + return await e.eor(get_string("help_3")) + await results[0].click(e.chat_id, reply_to=e.reply_to_msg_id, hide_via=True) + return await e.delete() + await e.eor(get_string("wspr_3")) + + +@in_pattern("wspr", owner=True) +async def _(e): + iuser = e.query.user_id + zzz = e.text.split(maxsplit=2) + try: + query = zzz[1] + if query.isdigit(): + query = int(query) + logi = await ultroid_bot.get_entity(query) + if not isinstance(logi, types.User): + raise ValueError("Invalid Username.") + except IndexError: + sur = await e.builder.article( + title="Give Username", + description="You Didn't Type Username or id.", + text="You Didn't Type Username or id.", + ) + return await e.answer([sur]) + except ValueError as er: + LOGS.exception(er) + sur = await e.builder.article( + title="User Not Found", + description="Make sure username or id is correct.", + text="Make sure username or id is correct.", + ) + return await e.answer([sur]) + try: + desc = zzz[2] + except IndexError: + sur = await e.builder.article( + title="Type ur msg", text="You Didn't Type Your Msg" + ) + return await e.answer([sur]) + button = [ + [ + Button.inline("Secret Msg", data=f"dd_{e.id}"), + Button.inline("Delete Msg", data=f"del_{e.id}"), + ], + [ + Button.switch_inline( + "New", query=f"wspr {logi.username or logi.id}", same_peer=True + ) + ], + ] + us = logi.username or logi.first_name + sur = await e.builder.article( + title=logi.first_name, + description=desc, + text=get_string("wspr_1").format(us), + buttons=button, + ) + buddhhu.update({e.id: [logi.id, iuser, desc]}) + await e.answer([sur]) + + +@in_pattern("msg", owner=True) +async def _(e): + zzz = e.text.split(maxsplit=1) + desc = "Touch me" + try: + query = zzz[1] + if query.isdigit(): + query = int(query) + logi = await ultroid_bot(gu(id=query)) + user = logi.users[0] + mention = inline_mention(user) + x = user.status + if isinstance(x, types.UserStatusOnline): + status = "Online" + elif isinstance(x, types.UserStatusOffline): + status = "Offline" + elif isinstance(x, types.UserStatusRecently): + status = "Last Seen Recently" + elif isinstance(x, types.UserStatusLastMonth): + status = "Last seen months ago" + elif isinstance(x, types.UserStatusLastWeek): + status = "Last seen weeks ago" + else: + status = "Can't Tell" + text = f"**Name:** `{user.first_name}`\n" + text += f"**Id:** `{user.id}`\n" + if user.username: + text += f"**Username:** `{user.username}`\n" + url = f"https://t.me/{user.username}" + else: + text += f"**Mention:** `{mention}`\n" + url = f"tg://user?id={user.id}" + text += f"**Status:** `{status}`\n" + text += f"**About:** `{logi.full_user.about}`" + button = [ + Button.url("Private", url=url), + Button.switch_inline( + "Secret msg", + query=f"wspr {query} Hello 👋", + same_peer=True, + ), + ] + sur = e.builder.article( + title=user.first_name, + description=desc, + text=text, + buttons=button, + ) + except IndexError: + sur = e.builder.article( + title="Give Username", + description="You Didn't Type Username or id.", + text="You Didn't Type Username or id.", + ) + except BaseException as er: + LOGS.exception(er) + name = get_string("wspr_4").format(query) + sur = e.builder.article( + title=name, + text=name, + ) + + await e.answer([sur]) + + +@callback( + re.compile( + "dd_(.*)", + ), +) +async def _(e): + ids = int(e.pattern_match.group(1).strip().decode("UTF-8")) + if buddhhu.get(ids): + if e.sender_id in buddhhu[ids]: + await e.answer(buddhhu[ids][-1], alert=True) + else: + await e.answer("Not For You", alert=True) + else: + await e.answer(get_string("wspr_2"), alert=True) + + +@callback(re.compile("del_(.*)")) +async def _(e): + ids = int(e.pattern_match.group(1).strip().decode("UTF-8")) + if buddhhu.get(ids): + if e.sender_id in buddhhu[ids]: + buddhhu.pop(ids) + try: + await e.edit(get_string("wspr_2")) + except MessageNotModifiedError: + pass + else: + await e.answer(get_string("wspr_5"), alert=True) diff --git a/plugins/admintools.py b/plugins/admintools.py new file mode 100644 index 0000000000000000000000000000000000000000..56821d1e51546ca6a76a2e1cf29315fc8b0da7d5 --- /dev/null +++ b/plugins/admintools.py @@ -0,0 +1,505 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_admintools") + +import asyncio + +from telethon.errors import BadRequestError +from telethon.errors.rpcerrorlist import ChatNotModifiedError, UserIdInvalidError +from telethon.tl.functions.channels import EditAdminRequest, GetFullChannelRequest +from telethon.tl.functions.messages import GetFullChatRequest, SetHistoryTTLRequest +from telethon.tl.types import InputMessagesFilterPinned +from telethon.utils import get_display_name + +from pyUltroid.dB import DEVLIST +from pyUltroid.fns.admins import ban_time +from pyUltroid.fns.info import get_uinfo + +from . import HNDLR, LOGS, eod, eor, get_string, inline_mention, types, ultroid_cmd + + +@ultroid_cmd( + pattern="promote( (.*)|$)", + admins_only=True, + manager=True, + require="add_admins", + fullsudo=True, +) +async def prmte(ult): + xx = await ult.eor(get_string("com_1")) + user, rank = await get_uinfo(ult) + rank = rank or "Admin" + FullRight = False + if not user: + return await xx.edit(get_string("pro_1")) + if rank.split()[0] == "-f": + try: + rank = rank.split(maxsplit=1)[1] + except IndexError: + rank = "Admin" + FullRight = True + try: + if FullRight: + await ult.client( + EditAdminRequest(ult.chat_id, user.id, ult.chat.admin_rights, rank) + ) + else: + await ult.client.edit_admin( + ult.chat_id, + user.id, + invite_users=True, + ban_users=True, + delete_messages=True, + pin_messages=True, + manage_call=True, + title=rank, + ) + await eod( + xx, get_string("pro_2").format(inline_mention(user), ult.chat.title, rank) + ) + except Exception as ex: + return await xx.edit(f"`{ex}`") + + +@ultroid_cmd( + pattern="demote( (.*)|$)", + admins_only=True, + manager=True, + require="add_admins", + fullsudo=True, +) +async def dmote(ult): + xx = await ult.eor(get_string("com_1")) + user, rank = await get_uinfo(ult) + if not rank: + rank = "Not Admin" + if not user: + return await xx.edit(get_string("de_1")) + try: + await ult.client.edit_admin( + ult.chat_id, + user.id, + invite_users=None, + ban_users=None, + delete_messages=None, + pin_messages=None, + manage_call=None, + title=rank, + ) + await eod(xx, get_string("de_2").format(inline_mention(user), ult.chat.title)) + except Exception as ex: + return await xx.edit(f"`{ex}`") + + +@ultroid_cmd( + pattern="ban( (.*)|$)", + admins_only=True, + manager=True, + require="ban_users", + fullsudo=True, +) +async def bban(ult): + something = await get_uinfo(ult) + if not something: + return + user, reason = something + if not user: + return await eod(ult, get_string("ban_1")) + if user.id in DEVLIST: + return await eod(ult, get_string("ban_2")) + try: + await ult.client.edit_permissions(ult.chat_id, user.id, view_messages=False) + except UserIdInvalidError: + return await eod(ult, get_string("adm_1")) + except BadRequestError: + return await eod(ult, get_string("ban_3")) + senderme = inline_mention(await ult.get_sender()) + userme = inline_mention(user) + text = get_string("ban_4").format(userme, senderme, ult.chat.title) + if reason: + text += get_string("ban_5").format(reason) + await eod(ult, text) + + +@ultroid_cmd( + pattern="unban( (.*)|$)", + admins_only=True, + manager=True, + require="ban_users", + fullsudo=True, +) +async def uunban(ult): + xx = await ult.eor(get_string("com_1")) + if ult.text[1:].startswith("unbanall"): + return + something = await get_uinfo(ult) + if not something: + return + user, reason = something + if not user: + return await xx.edit(get_string("unban_1")) + try: + await ult.client.edit_permissions(ult.chat_id, user.id, view_messages=True) + except UserIdInvalidError: + return await eod(ult, get_string("adm_1")) + except BadRequestError: + return await xx.edit(get_string("adm_2")) + sender = inline_mention(await ult.get_sender()) + text = get_string("unban_3").format(inline_mention(user), sender, ult.chat.title) + if reason: + text += get_string("ban_5").format(reason) + await xx.edit(text) + + +@ultroid_cmd( + pattern="kick( (.*)|$)", + manager=True, + require="ban_users", + fullsudo=True, +) +async def kck(ult): + if "kickme" in ult.text: + return + if ult.is_private: + return await ult.eor("`Use this in Group/Channel.`", time=5) + ml = ult.text.split(" ", maxsplit=1)[0] + xx = await ult.eor(get_string("com_1")) + something = await get_uinfo(ult) + if not something: + return + user, reason = something + if not user: + return await xx.edit(get_string("adm_1")) + if user.id in DEVLIST: + return await xx.edit(get_string("kick_2")) + if getattr(user, "is_self", False): + return await xx.edit(get_string("kick_3")) + try: + await ult.client.kick_participant(ult.chat_id, user.id) + except BadRequestError as er: + LOGS.info(er) + return await xx.edit(get_string("kick_1")) + except Exception as e: + LOGS.exception(e) + return + text = get_string("kick_4").format( + inline_mention(user), inline_mention(await ult.get_sender()), ult.chat.title + ) + if reason: + text += get_string("ban_5").format(reason) + await xx.edit(text) + + +@ultroid_cmd( + pattern="tban( (.*)|$)", + admins_only=True, + manager=True, + require="ban_users", + fullsudo=True, +) +async def tkicki(e): + huh = e.text.split() + inputt = None + try: + tme = huh[1] + except IndexError: + return await e.eor(get_string("adm_3"), time=15) + try: + inputt = huh[2] + except IndexError: + if e.reply_to_msg_id: + inputt = (await e.get_reply_message()).sender_id + if not inputt: + return await e.eor(get_string("tban_1")) + userid = await e.client.parse_id(inputt) + try: + user = await e.client.get_entity(userid) + except Exception as ex: + return await eor(e, f"`{ex}`") + try: + bun = ban_time(tme) + await e.client.edit_permissions( + e.chat_id, user.id, until_date=bun, view_messages=False + ) + await eod( + e, + get_string("tban_2").format(inline_mention(user), e.chat.title, tme), + time=15, + ) + except Exception as m: + return await e.eor(str(m)) + + +@ultroid_cmd(pattern="pin$", manager=True, require="pin_messages", fullsudo=True) +async def pin(msg): + if not msg.is_reply: + return await eor(msg, get_string("pin_1")) + me = await msg.get_reply_message() + if me.is_private: + text = "`Pinned.`" + else: + text = f"Pinned [This Message]({me.message_link}) !" + try: + await msg.client.pin_message(msg.chat_id, me.id, notify=False) + except BadRequestError: + return await eor(msg, get_string("adm_2")) + except Exception as e: + return await eor(msg, f"**ERROR:**`{e}`") + await eor(msg, text) + + +@ultroid_cmd( + pattern="unpin($| (.*))", + manager=True, + require="pin_messages", + fullsudo=True, +) +async def unp(ult): + xx = await ult.eor(get_string("com_1")) + ch = (ult.pattern_match.group(1).strip()).strip() + msg = None + if ult.is_reply: + msg = ult.reply_to_msg_id + elif ch != "all": + return await xx.edit(get_string("unpin_1").format(HNDLR)) + try: + await ult.client.unpin_message(ult.chat_id, msg) + except BadRequestError: + return await xx.edit(get_string("adm_2")) + except Exception as e: + return await xx.edit(f"**ERROR:**`{e}`") + await xx.edit("`Unpinned!`") + + +@ultroid_cmd( + pattern="tpin( (.*)|$)", + admins_only=True, + manager=True, + require="pin_messages", + fullsudo=True, +) +async def pin_message(ult): + match = ult.pattern_match.group(1).strip() + if not ult.is_reply: + return await ult.eor("`Reply to message..`", time=6) + if not match: + return await ult.eor("`Please provide time..`", time=8) + msg = await ult.eor(get_string("com_1")) + msg_id = ult.reply_to_msg_id + try: + time = ban_time(match) + await ult.client.pin_message(ult.chat_id, msg_id) + await msg.eor(f"`pinned for time` `{time}`", time=8) + except Exception as er: + return await msg.edit(str(er)) + await asyncio.sleep(time) + try: + await ult.client.unpin_message(ult.chat_id, msg_id) + except Exception as er: + LOGS.exception(er) + + +@ultroid_cmd(pattern="purge( (.*)|$)", manager=True, require="delete_messages") +async def fastpurger(purg): + match = purg.pattern_match.group(1).strip() + try: + ABC = purg.text[6] + except IndexError: + ABC = None + if ABC and purg.text[6] in ["m", "a"]: + return + if not purg._client._bot and ( + (match) + or (purg.is_reply and (purg.is_private or isinstance(purg.chat, types.Chat))) + ): + p = 0 + async for msg in purg.client.iter_messages( + purg.chat_id, + limit=int(match) if match else None, + min_id=purg.reply_to_msg_id if purg.is_reply else None, + ): + await msg.delete() + p += 0 + return await eor(purg, f"Purged {p} Messages! ", time=5) + if not purg.reply_to_msg_id: + return await eor(purg, get_string("purge_1"), time=10) + try: + await purg.client.delete_messages( + purg.chat_id, list(range(purg.reply_to_msg_id, purg.id)) + ) + + except Exception as er: + LOGS.info(er) + await purg.eor("__Fast purge complete!__", time=5) + + +@ultroid_cmd( + pattern="purgeme( (.*)|$)", +) +async def fastpurgerme(purg): + if num := purg.pattern_match.group(1).strip(): + try: + nnt = int(num) + except BaseException: + await eor(purg, get_string("com_3"), time=5) + return + mp = 0 + async for mm in purg.client.iter_messages( + purg.chat_id, limit=nnt, from_user="me" + ): + await mm.delete() + mp += 1 + await eor(purg, f"Purged {mp} Messages!", time=5) + return + elif not purg.reply_to_msg_id: + return await eod( + purg, + "`Reply to a message to purge from or use it like ``purgeme `", + time=10, + ) + chat = await purg.get_input_chat() + msgs = [] + async for msg in purg.client.iter_messages( + chat, + from_user="me", + min_id=purg.reply_to_msg_id, + ): + msgs.append(msg) + if msgs: + await purg.client.delete_messages(chat, msgs) + await purg.eor( + "__Fast purge complete!__\n**Purged** `" + str(len(msgs)) + "` **messages.**", + time=5, + ) + + +@ultroid_cmd( + pattern="purgeall$", +) +async def _(e): + if not e.is_reply: + return await eod( + e, + get_string("purgeall_1"), + ) + + msg = await e.get_reply_message() + name = msg.sender + try: + await e.client.delete_messages(e.chat_id, from_user=msg.sender_id) + await e.eor(get_string("purgeall_2").format(name.first_name), time=5) + except Exception as er: + return await e.eor(str(er), time=5) + +@ultroid_cmd(pattern="pinned", manager=True, groups_only=True) +async def djshsh(event): + chat = await event.get_chat() + if isinstance(chat, types.Chat): + FChat = await event.client(GetFullChatRequest(chat.id)) + elif isinstance(chat, types.Channel): + FChat = await event.client(GetFullChannelRequest(chat.id)) + else: + return + msg_id = FChat.full_chat.pinned_msg_id + if not msg_id: + return await event.eor(get_string("pinned_1")) + msg = await event.client.get_messages(chat.id, ids=msg_id) + if msg: + await event.eor(get_string("pinned_2").format(msg.message_link)) + + +@ultroid_cmd( + pattern="listpinned$", +) +async def get_all_pinned(event): + x = await event.eor(get_string("com_1")) + chat_id = (str(event.chat_id)).replace("-100", "") + chat_name = get_display_name(event.chat) + a = "" + c = 1 + async for i in event.client.iter_messages( + event.chat_id, filter=InputMessagesFilterPinned + ): + if i.message: + t = " ".join(i.message.split()[:4]) + txt = f"{t}...." + else: + txt = "Go to message." + a += f"{c}. {txt}\n" + c += 1 + + if c == 1: + m = f"The pinned message in {chat_name}:\n\n" + else: + m = f"List of pinned message(s) in {chat_name}:\n\n" + + if not a: + return await eor(x, get_string("listpin_1"), time=5) + + await x.edit(m + a, parse_mode="html") + + +@ultroid_cmd( + pattern="autodelete( (.*)|$)", + admins_only=True, +) +async def autodelte(ult): + match = ult.pattern_match.group(1).strip() + if not match or match not in ["24h", "7d", "1m", "off"]: + return await ult.eor("`Please Use in Proper Format..`", time=5) + if match == "24h": + tt = 3600 * 24 + elif match == "7d": + tt = 3600 * 24 * 7 + elif match == "1m": + tt = 3600 * 24 * 31 + else: + tt = 0 + try: + await ult.client(SetHistoryTTLRequest(ult.chat_id, period=tt)) + except ChatNotModifiedError: + return await ult.eor( + f"Auto Delete Setting is Already same to `{match}`", time=5 + ) + await ult.eor(f"Auto Delete Status Changed to `{match}` !") + + +@ultroid_cmd( + pattern=r"kickall( (.*)|$)", + admins_only=True, + fullsudo=True, + require="ban_users", +) +async def kickall_cmd(ult): + """Kick all non-admin members from the group.""" + if ult.is_private: + return await ult.eor("`Use this in a Group.`", time=5) + match = ult.pattern_match.group(1).strip() + confirm = match.lower() == "confirm" + if not confirm: + return await ult.eor( + f"`Are you sure? This will kick ALL non-admin members!\nUse` `{HNDLR}kickall confirm` `to proceed.`", + time=10, + ) + xx = await ult.eor("`Starting KickAll...`") + kicked = 0 + failed = 0 + async for member in ult.client.iter_participants(ult.chat_id): + if member.bot or getattr(member.participant, "admin_rights", None) or getattr(member.participant, "creator", False): + continue + try: + await ult.client.kick_participant(ult.chat_id, member.id) + kicked += 1 + await asyncio.sleep(0.5) + except Exception: + failed += 1 + await xx.edit( + f"**KickAll Done!**\n**Kicked:** `{kicked}`\n**Failed:** `{failed}`" + ) diff --git a/plugins/afk.py b/plugins/afk.py new file mode 100644 index 0000000000000000000000000000000000000000..8ba415078c8489ecac93b109e8efaeb8751ab922 --- /dev/null +++ b/plugins/afk.py @@ -0,0 +1,165 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_afk") + + +import asyncio + +from telethon import events + +from pyUltroid.dB.afk_db import add_afk, del_afk, is_afk +from pyUltroid.dB.base import KeyManager + +from . import ( + LOG_CHANNEL, + NOSPAM_CHAT, + Redis, + asst, + get_string, + mediainfo, + udB, + ultroid_bot, + ultroid_cmd, + upload_file +) + +old_afk_msg = [] + +is_approved = KeyManager("PMPERMIT", cast=list).contains + + +@ultroid_cmd(pattern="afk( (.*)|$)", owner_only=True) +async def set_afk(event): + if event.client._bot or is_afk(): + return + text, media, media_type = None, None, None + if event.pattern_match.group(1).strip(): + text = event.text.split(maxsplit=1)[1] + reply = await event.get_reply_message() + if reply: + if reply.text and not text: + text = reply.text + if reply.media: + media_type = mediainfo(reply.media) + if media_type.startswith(("pic", "gif")): + file = await event.client.download_media(reply.media) + media = upload_file(file) + else: + media = reply.file.id + await event.eor("`Done`", time=2) + add_afk(text, media_type, media) + ultroid_bot.add_handler(remove_afk, events.NewMessage(outgoing=True)) + ultroid_bot.add_handler( + on_afk, + events.NewMessage( + incoming=True, func=lambda e: bool(e.mentioned or e.is_private) + ), + ) + msg1, msg2 = None, None + if text and media: + if "sticker" in media_type: + msg1 = await ultroid_bot.send_file(event.chat_id, file=media) + msg2 = await ultroid_bot.send_message( + event.chat_id, get_string("afk_5").format(text) + ) + else: + msg1 = await ultroid_bot.send_message( + event.chat_id, get_string("afk_5").format(text), file=media + ) + elif media: + if "sticker" in media_type: + msg1 = await ultroid_bot.send_file(event.chat_id, file=media) + msg2 = await ultroid_bot.send_message(event.chat_id, get_string("afk_6")) + else: + msg1 = await ultroid_bot.send_message( + event.chat_id, get_string("afk_6"), file=media + ) + elif text: + msg1 = await event.respond(get_string("afk_5").format(text)) + else: + msg1 = await event.respond(get_string("afk_6")) + old_afk_msg.append(msg1) + if msg2: + old_afk_msg.append(msg2) + return await asst.send_message(LOG_CHANNEL, msg2.text) + await asst.send_message(LOG_CHANNEL, msg1.text) + + +async def remove_afk(event): + if event.is_private and udB.get_key("PMSETTING") and not is_approved(event.chat_id): + return + elif "afk" in event.text.lower(): + return + elif event.chat_id in NOSPAM_CHAT: + return + if is_afk(): + _, _, _, afk_time = is_afk() + del_afk() + off = await event.reply(get_string("afk_1").format(afk_time)) + await asst.send_message(LOG_CHANNEL, get_string("afk_2").format(afk_time)) + for x in old_afk_msg: + try: + await x.delete() + except BaseException: + pass + await asyncio.sleep(10) + await off.delete() + + +async def on_afk(event): + if event.is_private and Redis("PMSETTING") and not is_approved(event.chat_id): + return + elif "afk" in event.text.lower(): + return + elif not is_afk(): + return + if event.chat_id in NOSPAM_CHAT: + return + sender = await event.get_sender() + if sender.bot or sender.verified: + return + text, media_type, media, afk_time = is_afk() + msg1, msg2 = None, None + if text and media: + if "sticker" in media_type: + msg1 = await event.reply(file=media) + msg2 = await event.reply(get_string("afk_3").format(afk_time, text)) + else: + msg1 = await event.reply( + get_string("afk_3").format(afk_time, text), file=media + ) + elif media: + if "sticker" in media_type: + msg1 = await event.reply(file=media) + msg2 = await event.reply(get_string("afk_4").format(afk_time)) + else: + msg1 = await event.reply(get_string("afk_4").format(afk_time), file=media) + elif text: + msg1 = await event.reply(get_string("afk_3").format(afk_time, text)) + else: + msg1 = await event.reply(get_string("afk_4").format(afk_time)) + for x in old_afk_msg: + try: + await x.delete() + except BaseException: + pass + old_afk_msg.append(msg1) + if msg2: + old_afk_msg.append(msg2) + + +if udB.get_key("AFK_DB"): + ultroid_bot.add_handler(remove_afk, events.NewMessage(outgoing=True)) + ultroid_bot.add_handler( + on_afk, + events.NewMessage( + incoming=True, func=lambda e: bool(e.mentioned or e.is_private) + ), + ) diff --git a/plugins/aiwrapper.py b/plugins/aiwrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..31264576190733f28d17aa4333785d0d2cdc481b --- /dev/null +++ b/plugins/aiwrapper.py @@ -0,0 +1,445 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}gemini ` + Get response from Google Gemini. + +• `{i}antr ` + Get response from Anthropic Claude. + +• `{i}gpt ` + Get response from OpenAI GPT. + +• `{i}deepseek ` + Get response from DeepSeek AI. + +Set custom models using: + • OPENAI_MODEL: default: gpt-4o-mini + • ANTHROPIC_MODEL: claude-3-opus-20240229 + • GEMINI_MODEL: gemini-1.5-flash + • DEEPSEEK_MODEL: deepseek-chat +""" + +import json +from . import LOGS, eor, get_string, udB, ultroid_cmd, async_searcher +import aiohttp +import asyncio + + +ENDPOINTS = { + "gpt": "https://api.openai.com/v1/chat/completions", + "antr": "https://api.anthropic.com/v1/messages", + "gemini": "https://generativelanguage.googleapis.com/v1beta/chat/completions", + "deepseek": "https://api.deepseek.com/chat/completions" +} + +DEFAULT_MODELS = { + "gpt": "gpt-4o-mini", + "antr": "claude-3-opus-20240229", + "gemini": "gemini-1.5-flash", + "deepseek": "deepseek-chat" +} + + +def get_model(provider): + """Get model name from database or use default""" + model_keys = { + "gpt": "OPENAI_MODEL", + "antr": "ANTHROPIC_MODEL", + "gemini": "GEMINI_MODEL", + "deepseek": "DEEPSEEK_MODEL" + } + return udB.get_key(model_keys[provider]) or DEFAULT_MODELS[provider] + + +async def stream_response(msg, text): + """Stream response by editing message""" + current = "" + # Split into chunks of ~100 characters at word boundaries + words = text.split() + chunks = [] + current_chunk = [] + + for word in words: + current_chunk.append(word) + if len(" ".join(current_chunk)) > 100: + chunks.append(" ".join(current_chunk[:-1])) + current_chunk = [word] + if current_chunk: + chunks.append(" ".join(current_chunk)) + + for chunk in chunks: + current += chunk + " " + try: + await msg.edit(current) + except Exception: + pass + await asyncio.sleep(0.5) + return current + + +async def get_ai_response(provider, prompt, api_key, stream=False): + """Get response from AI provider""" + try: + headers = {"Content-Type": "application/json"} + model = get_model(provider) + + if provider == "gpt": + headers["Authorization"] = f"Bearer {api_key}" + data = { + "model": model, + "messages": [{"role": "user", "content": prompt}], + "stream": stream + } + if not stream: + response = await async_searcher( + ENDPOINTS[provider], + headers=headers, + post=True, + json=data, + re_json=True + ) + yield response["choices"][0]["message"]["content"] + return + + async with aiohttp.ClientSession() as session: + async with session.post( + ENDPOINTS[provider], + headers=headers, + json=data + ) as resp: + async for line in resp.content: + if line: + try: + json_line = json.loads(line.decode('utf-8').strip().strip('data:').strip()) + if 'choices' in json_line and json_line['choices']: + content = json_line['choices'][0].get('delta', {}).get('content', '') + if content: + yield content + except Exception: + continue + + elif provider == "antr": + headers["x-api-key"] = api_key + headers["anthropic-version"] = "2023-06-01" + data = { + "model": model, + "messages": [{"role": "user", "content": prompt}], + "stream": stream + } + if not stream: + response = await async_searcher( + ENDPOINTS[provider], + headers=headers, + post=True, + json=data, + re_json=True + ) + yield response["content"][0]["text"] + return + + async with aiohttp.ClientSession() as session: + async with session.post( + ENDPOINTS[provider], + headers=headers, + json=data + ) as resp: + async for line in resp.content: + if line: + try: + json_line = json.loads(line.decode('utf-8').strip()) + if 'content' in json_line: + content = json_line['content'][0]['text'] + if content: + yield content + except Exception: + continue + + elif provider == "gemini": + headers["Authorization"] = f"Bearer {api_key}" + data = { + "model": model, + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": prompt} + ], + "stream": stream + } + + if not stream: + try: + response = await async_searcher( + ENDPOINTS[provider], + headers=headers, + post=True, + json=data, + re_json=True + ) + if "error" in response: + error = response["error"] + if error.get("code") == 429: + retry_delay = None + for detail in error.get("details", []): + if detail.get("@type") == "type.googleapis.com/google.rpc.RetryInfo": + retry_delay = detail.get("retryDelay", "60s").rstrip("s") + error_msg = f"⚠️ Rate limit exceeded. Please try again in {retry_delay} seconds." + if "free_tier" in str(error): + error_msg += "\nConsider upgrading to a paid tier for higher quotas." + yield error_msg + return + yield f"Error: {error.get('message', 'Unknown error occurred')}" + return + yield response["choices"][0]["message"]["content"] + except Exception as e: + LOGS.exception(e) + yield f"Error: {str(e)}" + return + + async with aiohttp.ClientSession() as session: + try: + async with session.post( + ENDPOINTS[provider], + headers=headers, + json=data + ) as resp: + if resp.status == 429: + error_data = await resp.json() + retry_delay = "60" + for detail in error_data.get("error", {}).get("details", []): + if detail.get("@type") == "type.googleapis.com/google.rpc.RetryInfo": + retry_delay = detail.get("retryDelay", "60s").rstrip("s") + yield f"⚠️ Rate limit exceeded. Please try again in {retry_delay} seconds." + return + + if resp.status != 200: + error_data = await resp.json() + yield f"Error: {error_data.get('error', {}).get('message', 'Unknown error occurred')}" + return + + async for line in resp.content: + if line: + text = line.decode('utf-8').strip() + if text.startswith('data: '): + data = text[6:] # Remove 'data: ' prefix + if data == '[DONE]': + break + try: + json_data = json.loads(data) + if 'choices' in json_data and json_data['choices']: + content = json_data['choices'][0].get('delta', {}).get('content', '') + if content: + yield content + except json.JSONDecodeError: + continue + except Exception as e: + LOGS.exception(e) + yield f"Error: {str(e)}" + + elif provider == "deepseek": + headers["Authorization"] = f"Bearer {api_key}" + data = { + "model": model, + "messages": [{"role": "user", "content": prompt}], + "stream": stream + } + if not stream: + response = await async_searcher( + ENDPOINTS[provider], + headers=headers, + post=True, + json=data, + re_json=True + ) + yield response["choices"][0]["message"]["content"] + return + + async with aiohttp.ClientSession() as session: + async with session.post( + ENDPOINTS[provider], + headers=headers, + json=data + ) as resp: + async for line in resp.content: + if line: + try: + json_line = json.loads(line.decode('utf-8').strip()) + if 'choices' in json_line and json_line['choices']: + content = json_line['choices'][0].get('delta', {}).get('content', '') + if content: + yield content + except Exception: + continue + + except Exception as e: + LOGS.exception(e) + yield f"Error: {str(e)}" + + +@ultroid_cmd(pattern="gemini( (.*)|$)") +async def gemini_ai(event): + """Use Google Gemini""" + prompt = event.pattern_match.group(1).strip() + if not prompt: + return await event.eor("❌ Please provide a prompt!") + + api_key = udB.get_key("GEMINI_API_KEY") + if not api_key: + return await event.eor("⚠️ Please set Gemini API key using `setdb GEMINI_API_KEY your_api_key`") + + msg = await event.eor("🤔 Thinking...") + model = get_model("gemini") + + header = ( + "🤖 **Google Gemini**\n" + f"**Model:** `{model}`\n" + "➖➖➖➖➖➖➖➖➖➖\n\n" + f"**🔍 Prompt:**\n{prompt}\n\n" + "**💡 Response:**\n" + ) + + if event.client.me.bot: + await msg.edit(header) + response = "" + async for chunk in get_ai_response("gemini", prompt, api_key, stream=True): + response += chunk + try: + await msg.edit(header + response) + except Exception: + pass + else: + response = "" + async for chunk in get_ai_response("gemini", prompt, api_key, stream=True): + response += chunk + try: + await msg.edit(header + response) + except Exception: + pass + +@ultroid_cmd(pattern="antr( (.*)|$)") +async def anthropic_ai(event): + """Use Anthropic Claude""" + prompt = event.pattern_match.group(1).strip() + if not prompt: + return await event.eor("❌ Please provide a prompt!") + + api_key = udB.get_key("ANTHROPIC_KEY") + if not api_key: + return await event.eor("⚠️ Please set Anthropic API key using `setdb ANTHROPIC_KEY your_api_key`") + + msg = await event.eor("🤔 Thinking...") + model = get_model("antr") + + formatted_response = ( + "🧠 **Anthropic Claude**\n" + f"**Model:** `{model}`\n" + "➖➖➖➖➖➖➖➖➖➖\n\n" + f"**🔍 Prompt:**\n{prompt}\n\n" + f"**💡 Response:**\n" + ) + + if event.client.me.bot: + await msg.edit(formatted_response) + response = "" + async for chunk in get_ai_response("antr", prompt, api_key, stream=True): + response += chunk + try: + await msg.edit(formatted_response + response) + except Exception: + pass + else: + response = "" + async for chunk in get_ai_response("antr", prompt, api_key, stream=True): + response += chunk + try: + await msg.edit(formatted_response + response) + except Exception: + pass + +@ultroid_cmd(pattern="gpt( (.*)|$)") +async def openai_ai(event): + """Use OpenAI GPT""" + prompt = event.pattern_match.group(1).strip() + if not prompt: + return await event.eor("❌ Please provide a prompt!") + + api_key = udB.get_key("OPENAI_API_KEY") + if not api_key: + return await event.eor("⚠️ Please set GPT API key using `setdb OPENAI_API_KEY your_api_key`") + + msg = await event.eor("🤔 Thinking...") + model = get_model("gpt") + + header = ( + "🌟 **OpenAI GPT**\n" + f"**Model:** `{model}`\n" + "➖➖➖➖➖➖➖➖➖➖\n\n" + f"**🔍 Prompt:**\n{prompt}\n\n" + "**💡 Response:**\n" + ) + + if event.client.me.bot: + await msg.edit(header) + response = "" + async for chunk in get_ai_response("gpt", prompt, api_key, stream=True): + response += chunk + try: + await msg.edit(header + response) + except Exception: + pass + else: + response ="" + async for chunk in get_ai_response("gpt", prompt, api_key, stream=True): + response += chunk + try: + await msg.edit(header + response) + except Exception: + pass + +@ultroid_cmd(pattern="deepseek( (.*)|$)") +async def deepseek_ai(event): + """Use DeepSeek AI""" + prompt = event.pattern_match.group(1).strip() + if not prompt: + return await event.eor("❌ Please provide a prompt!") + + api_key = udB.get_key("DEEPSEEK_API_KEY") + if not api_key: + return await event.eor("⚠️ Please set DeepSeek API key using `setdb DEEPSEEK_API_KEY your_api_key`") + + msg = await event.eor("🤔 Thinking...") + model = get_model("deepseek") + + formatted_response = ( + "🤖 **DeepSeek AI**\n" + f"**Model:** `{model}`\n" + "➖➖➖➖➖➖➖➖➖➖\n\n" + f"**🔍 Prompt:**\n{prompt}\n\n" + f"**💡 Response:**\n" + ) + + if event.client.me.bot: + await msg.edit(formatted_response) + response = "" + async for chunk in get_ai_response("deepseek", prompt, api_key, stream=True): + response += chunk + try: + await msg.edit(formatted_response + response) + except Exception: + pass + else: + response = "" + async for chunk in get_ai_response("deepseek", prompt, api_key, stream=True): + response += chunk + + try: + await msg.edit(formatted_response + response) + except Exception: + pass + diff --git a/plugins/antiflood.py b/plugins/antiflood.py new file mode 100644 index 0000000000000000000000000000000000000000..fa90fb0d97ea50c5c792e4a38bf4f9fc4a6409db --- /dev/null +++ b/plugins/antiflood.py @@ -0,0 +1,121 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_antiflood") + + +import re + +from telethon.events import NewMessage as NewMsg + +from pyUltroid.dB import DEVLIST +from pyUltroid.dB.antiflood_db import get_flood, get_flood_limit, rem_flood, set_flood +from pyUltroid.fns.admins import admin_check + +from . import Button, Redis, asst, callback, eod, get_string, ultroid_bot, ultroid_cmd + +_check_flood = {} + +if Redis("ANTIFLOOD"): + + @ultroid_bot.on( + NewMsg( + chats=list(get_flood().keys()), + ), + ) + async def flood_checm(event): + count = 1 + chat = (await event.get_chat()).title + if event.chat_id in _check_flood.keys(): + if event.sender_id == list(_check_flood[event.chat_id].keys())[0]: + count = _check_flood[event.chat_id][event.sender_id] + _check_flood[event.chat_id] = {event.sender_id: count + 1} + else: + _check_flood[event.chat_id] = {event.sender_id: count} + else: + _check_flood[event.chat_id] = {event.sender_id: count} + if await admin_check(event, silent=True) or getattr(event.sender, "bot", None): + return + if event.sender_id in DEVLIST: + return + if _check_flood[event.chat_id][event.sender_id] >= int( + get_flood_limit(event.chat_id) + ): + try: + name = event.sender.first_name + await event.client.edit_permissions( + event.chat_id, event.sender_id, send_messages=False + ) + del _check_flood[event.chat_id] + await event.reply(f"#AntiFlood\n\n{get_string('antiflood_3')}") + await asst.send_message( + int(Redis("LOG_CHANNEL")), + f"#Antiflood\n\n`Muted `[{name}](tg://user?id={event.sender_id})` in {chat}`", + buttons=Button.inline( + "Unmute", data=f"anti_{event.sender_id}_{event.chat_id}" + ), + ) + except BaseException: + pass + + +@callback( + re.compile( + "anti_(.*)", + ), +) +async def unmuting(e): + ino = (e.data_match.group(1)).decode("UTF-8").split("_") + user = int(ino[0]) + chat = int(ino[1]) + user_name = (await ultroid_bot.get_entity(user)).first_name + chat_title = (await ultroid_bot.get_entity(chat)).title + await ultroid_bot.edit_permissions(chat, user, send_messages=True) + await e.edit( + f"#Antiflood\n\n`Unmuted `[{user_name}](tg://user?id={user})` in {chat_title}`" + ) + + +@ultroid_cmd( + pattern="setflood ?(\\d+)", + admins_only=True, +) +async def setflood(e): + input_ = e.pattern_match.group(1).strip() + if not input_: + return await e.eor("`What?`", time=5) + if not input_.isdigit(): + return await e.eor(get_string("com_3"), time=5) + if m := set_flood(e.chat_id, input_): + return await eod(e, get_string("antiflood_4").format(input_)) + + +@ultroid_cmd( + pattern="remflood$", + admins_only=True, +) +async def remove_flood(e): + hmm = rem_flood(e.chat_id) + try: + del _check_flood[e.chat_id] + except BaseException: + pass + if hmm: + return await e.eor(get_string("antiflood_1"), time=5) + await e.eor(get_string("antiflood_2"), time=5) + + +@ultroid_cmd( + pattern="getflood$", + admins_only=True, +) +async def getflood(e): + if ok := get_flood_limit(e.chat_id): + return await e.eor(get_string("antiflood_5").format(ok), time=5) + await e.eor(get_string("antiflood_2"), time=5) diff --git a/plugins/asstcmd.py b/plugins/asstcmd.py new file mode 100644 index 0000000000000000000000000000000000000000..6b365ed8cfa08f44e29d7a57011645ff28518cda --- /dev/null +++ b/plugins/asstcmd.py @@ -0,0 +1,100 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_asstcmd") + +import os + +from pyUltroid.dB.asstcmd_db import add_cmd, cmd_reply, list_cmds, rem_cmd +from pyUltroid.fns.tools import create_tl_btn, format_btn, get_msg_button +from telethon import events, utils + +from . import asst, get_string, mediainfo, udB, ultroid_cmd, upload_file + + +@ultroid_cmd(pattern="addcmd( (.*)|$)") +async def ac(e): + wrd = (e.pattern_match.group(1).strip()).lower() + wt = await e.get_reply_message() + if not (wt and wrd): + return await e.eor(get_string("asstcmd_1"), time=5) + if "/" in wrd: + wrd = wrd.replace("/", "") + btn = format_btn(wt.buttons) if wt.buttons else None + if wt and wt.media: + wut = mediainfo(wt.media) + if wut.startswith(("pic", "gif")): + dl = await e.client.download_media(wt.media) + m = upload_file(dl) + os.remove(dl) + elif wut == "video": + if wt.media.document.size > 8 * 1000 * 1000: + return await e.eor(get_string("com_4"), time=5) + dl = await e.client.download_media(wt.media) + m = upload_file(dl) + os.remove(dl) + else: + m = utils.pack_bot_file_id(wt.media) + if wt.text: + txt = wt.text + if not btn: + txt, btn = get_msg_button(wt.text) + add_cmd(wrd, txt, m, btn) + else: + add_cmd(wrd, None, m, btn) + else: + txt = wt.text + if not btn: + txt, btn = get_msg_button(wt.text) + add_cmd(wrd, txt, None, btn) + asst.add_handler( + astcmds, + events.NewMessage( + func=lambda x: x.text.startswith("/") and x.text[1:] in list(list_cmds()) + ), + ) + await e.eor(get_string("asstcmd_4").format(wrd)) + + +@ultroid_cmd(pattern="remcmd( (.*)|$)") +async def rc(e): + wrd = (e.pattern_match.group(1).strip()).lower() + if not wrd: + return await e.eor(get_string("asstcmd_2"), time=5) + wrd = wrd.replace("/", "") + rem_cmd(wrd) + await e.eor(get_string("asstcmd_3").format(wrd)) + + +@ultroid_cmd(pattern="listcmd$") +async def lscmd(e): + if list_cmds(): + ok = get_string("asstcmd_6") + for x in list_cmds(): + ok += f"/{x}" + "\n" + return await e.eor(ok) + return await e.eor(get_string("asstcmd_5")) + + +async def astcmds(e): + xx = (e.text.replace("/", "")).lower().split()[0] + if cmd_reply(xx): + msg, media, bt = cmd_reply(xx) + if bt: + bt = create_tl_btn(bt) + await e.reply(msg, file=media, buttons=bt) + + +if udB.get_key("ASST_CMDS"): + asst.add_handler( + astcmds, + events.NewMessage( + func=lambda x: x.text.startswith("/") and x.text[1:] in list(list_cmds()) + ), + ) diff --git a/plugins/audiotools.py b/plugins/audiotools.py new file mode 100644 index 0000000000000000000000000000000000000000..d19cef78e4ddfbb6054fa5db9ac6653b04b3e78e --- /dev/null +++ b/plugins/audiotools.py @@ -0,0 +1,163 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +import os +import time +from datetime import datetime as dt + +from pyUltroid.fns.tools import set_attributes + +from . import ( + LOGS, + ULTConfig, + bash, + downloader, + eod, + eor, + genss, + get_help, + get_string, + humanbytes, + mediainfo, + stdr, + time_formatter, + ultroid_cmd +) + +__doc__ = get_help("help_audiotools") + + +@ultroid_cmd(pattern="makevoice$") +async def vnc(e): + if not e.reply_to: + return await eod(e, get_string("audiotools_1")) + r = await e.get_reply_message() + if not mediainfo(r.media).startswith(("audio", "video")): + return await eod(e, get_string("spcltool_1")) + xxx = await e.eor(get_string("com_1")) + file, _ = await e.client.fast_downloader( + r.document, + ) + await xxx.edit(get_string("audiotools_2")) + await bash( + f"ffmpeg -i '{file.name}' -map 0:a -codec:a libopus -b:a 100k -vbr on out.opus" + ) + try: + await e.client.send_message( + e.chat_id, file="out.opus", force_document=False, reply_to=r + ) + except Exception as er: + LOGS.exception(er) + return await xxx.edit("`Failed to convert in Voice...`") + await xxx.delete() + os.remove(file.name) + os.remove("out.opus") + + +@ultroid_cmd(pattern="atrim( (.*)|$)") +async def trim_aud(e): + sec = e.pattern_match.group(1).strip() + if not sec or "-" not in sec: + return await eod(e, get_string("audiotools_3")) + a, b = sec.split("-") + if int(a) >= int(b): + return await eod(e, get_string("audiotools_4")) + vido = await e.get_reply_message() + if vido and vido.media and mediainfo(vido.media).startswith(("video", "audio")): + if hasattr(vido.media, "document"): + vfile = vido.media.document + name = vido.file.name + else: + vfile = vido.media + name = "" + if not name: + name = dt.now().isoformat("_", "seconds") + ".mp4" + xxx = await e.eor(get_string("audiotools_5")) + c_time = time.time() + file = await downloader( + f"resources/downloads/{name}", + vfile, + xxx, + c_time, + f"Downloading {name}...", + ) + + o_size = os.path.getsize(file.name) + d_time = time.time() + diff = time_formatter((d_time - c_time) * 1000) + file_name = (file.name).split("/")[-1] + out = file_name.replace(file_name.split(".")[-1], "_trimmed.aac") + if int(b) > int(await genss(file.name)): + os.remove(file.name) + return await eod(xxx, get_string("audiotools_6")) + ss, dd = stdr(int(a)), stdr(int(b)) + xxx = await xxx.edit( + f"Downloaded `{file.name}` of `{humanbytes(o_size)}` in `{diff}`.\n\nNow Trimming Audio from `{ss}` to `{dd}`..." + ) + cmd = f'ffmpeg -i "{file.name}" -preset ultrafast -ss {ss} -to {dd} -vn -acodec copy "{out}" -y' + await bash(cmd) + os.remove(file.name) + f_time = time.time() + n_file, _ = await e.client.fast_uploader( + out, show_progress=True, event=e, message="Uploading...", to_delete=True + ) + attributes = await set_attributes(out) + + caption = get_string("audiotools_7").format(ss, dd) + await e.client.send_file( + e.chat_id, + n_file, + thumb=ULTConfig.thumb, + caption=caption, + attributes=attributes, + force_document=False, + reply_to=e.reply_to_msg_id, + ) + await xxx.delete() + else: + await e.eor(get_string("audiotools_1"), time=5) + + +@ultroid_cmd(pattern="extractaudio$") +async def ex_aud(e): + reply = await e.get_reply_message() + if not (reply and reply.media and mediainfo(reply.media).startswith("video")): + return await e.eor(get_string("audiotools_8")) + name = reply.file.name or "video.mp4" + vfile = reply.media.document + msg = await e.eor(get_string("com_1")) + c_time = time.time() + file = await downloader( + f"resources/downloads/{name}", + vfile, + msg, + c_time, + f"Downloading {name}...", + ) + + out_file = f"{file.name}.aac" + cmd = f"ffmpeg -i {file.name} -vn -acodec copy {out_file}" + o, err = await bash(cmd) + os.remove(file.name) + attributes = await set_attributes(out_file) + + f_time = time.time() + try: + n_file, _ = await e.client.fast_uploader( + out_file, show_progress=True, event=e, message="Uploading...", to_delete=True + ) + + except FileNotFoundError: + return await eor(msg, get_string("audiotools_9")) + await e.reply( + get_string("audiotools_10"), + file=n_file, + thumb=ULTConfig.thumb, + attributes=attributes, + ) + await msg.delete() diff --git a/plugins/autoban.py b/plugins/autoban.py new file mode 100644 index 0000000000000000000000000000000000000000..6eb833e0789cf9dc08a29a75ff3656445989b545 --- /dev/null +++ b/plugins/autoban.py @@ -0,0 +1,59 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_autoban") + +from telethon import events + +from pyUltroid.dB.base import KeyManager + +from . import LOGS, asst, ultroid_bot, ultroid_cmd + +Keym = KeyManager("DND_CHATS", cast=list) + + +def join_func(e): + return e.user_joined and Keym.contains(e.chat_id) + + +async def dnd_func(event): + for user in event.users: + try: + await (await event.client.kick_participant(event.chat_id, user)).delete() + except Exception as ex: + LOGS.error("Error in DND:") + LOGS.exception(ex) + await event.delete() + + +@ultroid_cmd( + pattern="autokick (on|off)$", + admins_only=True, + manager=True, + require="ban_users", + fullsudo=True, +) +async def _(event): + match = event.pattern_match.group(1) + if match == "on": + if Keym.contains(event.chat_id): + return await event.eor("`Chat already in do not disturb mode.`", time=3) + Keym.add(event.chat_id) + event.client.add_handler(dnd_func, events.ChatAction(func=join_func)) + await event.eor("`Do not disturb mode activated for this chat.`", time=3) + elif match == "off": + if not Keym.contains(event.chat_id): + return await event.eor("`Chat is not in do not disturb mode.`", time=3) + Keym.remove(event.chat_id) + await event.eor("`Do not disturb mode deactivated for this chat.`", time=3) + + +if Keym.get(): + ultroid_bot.add_handler(dnd_func, events.ChatAction(func=join_func)) + asst.add_handler(dnd_func, events.ChatAction(func=join_func)) diff --git a/plugins/autopic.py b/plugins/autopic.py new file mode 100644 index 0000000000000000000000000000000000000000..1c9e568c9ddd2e89f71a80b3db6edc57e859f702 --- /dev/null +++ b/plugins/autopic.py @@ -0,0 +1,156 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +import asyncio +import os +import random +from random import shuffle +import aiohttp +import re +from telethon.tl.functions.photos import UploadProfilePhotoRequest +from PIL import Image + +from pyUltroid.fns.helper import download_file, fast_download + +from . import LOGS, get_help, get_string, udB, ultroid_bot, ultroid_cmd + +__doc__ = get_help("help_autopic") + + +async def get_google_images(query: str): + """Extract image URLs from Google Images search results with fallbacks""" + + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + } + + search_url = f"https://www.google.com/search?q={query}&tbm=isch" + + # Domains to exclude + excluded_domains = [ + 'gstatic.com', + 'google.com', + 'googleusercontent.com', + 'ssl.google.com' + ] + + def is_valid_url(url): + return not any(domain in url.lower() for domain in excluded_domains) + + try: + async with aiohttp.ClientSession() as session: + async with session.get(search_url, headers=headers) as response: + html = await response.text() + + # Try to extract from search results div first + img_urls = [] + search_pattern = r'' + search_match = re.search(search_pattern, html, re.DOTALL) + if search_match: + search_content = search_match.group(1) + url_pattern = r'https://[^\"]*?\.(?:jpg|jpeg|png|webp)' + url_matches = re.finditer(url_pattern, search_content, re.IGNORECASE) + for url_match in url_matches: + url = url_match.group(0) + if url not in img_urls and is_valid_url(url): + img_urls.append(url) + + # Fallback to tdeeNb div if no results + if not img_urls: + pattern = r'
]*>(.*?)
' + matches = re.finditer(pattern, html, re.DOTALL) + for match in matches: + div_content = match.group(1) + url_pattern = r'https://[^\"]*?\.(?:jpg|jpeg|png|webp)' + url_matches = re.finditer(url_pattern, div_content, re.IGNORECASE) + for url_match in url_matches: + url = url_match.group(0) + if url not in img_urls and is_valid_url(url): + img_urls.append(url) + + # Fallback to general image search if still no results + if not img_urls: + pattern = r"https://[^\"]*?\.(?:jpg|jpeg|png|webp)" + matches = re.finditer(pattern, html, re.IGNORECASE) + for match in matches: + url = match.group(0) + if url not in img_urls and is_valid_url(url): + img_urls.append(url) + + # Final fallback to data URLs if still no results + if not img_urls: + pattern = r'data:image/(?:jpeg|png|webp);base64,[^\"]*' + matches = re.finditer(pattern, html, re.IGNORECASE) + for match in matches: + url = match.group(0) + if url not in img_urls: + img_urls.append(url) + + return img_urls + + except Exception as e: + print(f"Error fetching Google images: {e}") + return [] + + +@ultroid_cmd(pattern="autopic( (.*)|$)") +async def autopic(e): + search = e.pattern_match.group(1).strip() + if udB.get_key("AUTOPIC") and not search: + udB.del_key("AUTOPIC") + return await e.eor(get_string("autopic_5")) + if not search: + return await e.eor(get_string("autopic_1"), time=5) + e = await e.eor(get_string("com_1")) + images = await get_google_images(search) + if not images: + return await e.eor(get_string("autopic_2").format(search), time=5) + await e.eor(get_string("autopic_3").format(search)) + udB.set_key("AUTOPIC", search) + SLEEP_TIME = udB.get_key("SLEEP_TIME") or 1221 + while True: + for lie in images: + if udB.get_key("AUTOPIC") != search: + return + download_path, stime = await fast_download(lie, "resources/downloads/autopic.jpg") + img = Image.open(download_path) + img.save("resources/downloads/autopic.jpg") + file = await e.client.upload_file("resources/downloads/autopic.jpg") + await e.client(UploadProfilePhotoRequest(file=file)) + os.remove("resources/downloads/autopic.jpg") + await asyncio.sleep(SLEEP_TIME) + + shuffle(images) + + +if search := udB.get_key("AUTOPIC"): + images = {} + sleep = udB.get_key("SLEEP_TIME") or 1221 + + async def autopic_func(): + search = udB.get_key("AUTOPIC") + if images.get(search) is None: + images[search] = await get_google_images(search) + if not images.get(search): + return + img = random.choice(images[search]) + filee, stime = await fast_download(img, "resources/downloads/autopic.jpg") + img = Image.open(filee) + img.save("resources/downloads/autopic.jpg") + file = await ultroid_bot.upload_file("resources/downloads/autopic.jpg") + await ultroid_bot(UploadProfilePhotoRequest(file=file)) + os.remove(filee) + + try: + from apscheduler.schedulers.asyncio import AsyncIOScheduler + + schedule = AsyncIOScheduler() + schedule.add_job(autopic_func, "interval", seconds=sleep) + schedule.start() + except ModuleNotFoundError as er: + LOGS.error(f"autopic: '{er.name}' not installed.") diff --git a/plugins/beautify.py b/plugins/beautify.py new file mode 100644 index 0000000000000000000000000000000000000000..733c5d2da4eb850c51b0f7a8e15b68ff2d990a90 --- /dev/null +++ b/plugins/beautify.py @@ -0,0 +1,197 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_beautify") + + +import os +import random + +from telethon.utils import get_display_name +from urllib.parse import urlencode +from . import Carbon, ultroid_cmd, get_string, inline_mention +from secrets import token_hex + +_colorspath = "resources/colorlist.txt" + +if os.path.exists(_colorspath): + with open(_colorspath, "r") as f: + all_col = f.read().split() +else: + all_col = [] + + +@ultroid_cmd( + pattern="(rc|c)arbon", +) +async def cr_bn(event): + xxxx = await event.eor(get_string("com_1")) + te = event.pattern_match.group(1) + col = random.choice(all_col) if te[0] == "r" else "White" + if event.reply_to_msg_id: + temp = await event.get_reply_message() + if temp.media: + b = await event.client.download_media(temp) + with open(b) as a: + code = a.read() + os.remove(b) + else: + code = temp.message + else: + try: + code = event.text.split(" ", maxsplit=1)[1] + except IndexError: + return await xxxx.eor(get_string("carbon_2")) + xx = await Carbon(code=code, file_name="ultroid_carbon", backgroundColor=col) + if isinstance(xx, dict): + await xxxx.edit(f"`{xx}`") + return + await xxxx.delete() + await event.reply( + f"Carbonised by {inline_mention(event.sender)}", + file=xx, + ) + + +@ultroid_cmd( + pattern="ccarbon( (.*)|$)", +) +async def crbn(event): + match = event.pattern_match.group(1).strip() + if not match: + return await event.eor(get_string("carbon_3")) + msg = await event.eor(get_string("com_1")) + if event.reply_to_msg_id: + temp = await event.get_reply_message() + if temp.media: + b = await event.client.download_media(temp) + with open(b) as a: + code = a.read() + os.remove(b) + else: + code = temp.message + else: + try: + match = match.split(" ", maxsplit=1) + code = match[1] + match = match[0] + except IndexError: + return await msg.eor(get_string("carbon_2")) + xx = await Carbon(code=code, backgroundColor=match) + await msg.delete() + await event.reply( + f"Carbonised by {inline_mention(event.sender)}", + file=xx, + ) + + +RaySoTheme = [ + "meadow", + "breeze", + "raindrop", + "candy", + "crimson", + "falcon", + "sunset", + "noir", + "midnight", + "bitmap", + "ice", + "sand", + "forest", + "mono", +] + + +@ultroid_cmd(pattern="rayso") +async def pass_on(ult): + try: + from playwright.async_api import async_playwright + except ImportError: + await ult.eor( + "`playwright` is not installed!\nPlease install it to use this command.." + ) + return + + proc = await ult.eor(get_string("com_1")) + spli = ult.text.split() + theme, dark, title, text = None, True, get_display_name(ult.chat), None + if len(spli) > 1: + if spli[1] in RaySoTheme: + theme = spli[1] + if len(spli) > 2: + text = " ".join(spli[2:]) + else: + text = " ".join(spli[1:]) + if ult.is_reply: + try: + msg = await ult.get_reply_message() + text = msg.message if not text else text + title = get_display_name(msg.sender) + if not theme and spli[1] in RaySoTheme: + theme = spli[1] + except Exception as sam: + LOGS.exception(sam) + if not text: + await proc.eor("No text to beautify!") + return + if not theme: + theme = random.choice(RaySoTheme) + cleaned_text = "\n".join([line.strip() for line in text.splitlines()]) + name = token_hex(8) + ".png" + data = {"darkMode": dark, "theme": theme, "title": title} + url = f"https://ray.so/#{urlencode(data)}" + async with async_playwright() as play: + try: + browser = await play.chromium.launch() + page = await browser.new_page() + await page.goto(url) + await page.wait_for_load_state("networkidle") + try: + await page.wait_for_selector( + "div[class*='Editor_editor__']", timeout=60000 + ) + editor = await page.query_selector("div[class*='Editor_editor__']") + await editor.focus() + await editor.click() + + for line in cleaned_text.split("\n"): + await page.keyboard.type(line) + await page.keyboard.press("Enter") + + await page.evaluate( + """() => { + const button = document.querySelector('button[aria-label="Export as PNG"]'); + button.click(); + }""" + ) + + async with page.expect_download() as download_info: + download = await download_info.value + await download.save_as(name) + except playwright._impl._errors.TimeoutError: + LOGS.error("Timeout error: Selector not found within 60 seconds.") + await proc.eor("Failed to find the editor within 60 seconds.") + return + except Exception as e: + LOGS.error(f"Error occurred during playwright operation: {e}") + await proc.eor("An error occurred during the operation.") + return + finally: + if os.path.exists(name): + try: + await ult.reply(file=name) + await proc.try_delete() + os.remove(name) + except Exception as e: + LOGS.error(f"Error occurred while replying with the file: {e}") + await proc.eor("Failed to send the file.") + else: + LOGS.error(f"Error: File {name} not found or inaccessible.") + await proc.eor("Failed to save the file.") diff --git a/plugins/blacklist.py b/plugins/blacklist.py new file mode 100644 index 0000000000000000000000000000000000000000..875929f8342fd73405dd41e3e6a016f8d40a541a --- /dev/null +++ b/plugins/blacklist.py @@ -0,0 +1,69 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_blacklist") + + +from pyUltroid.dB.blacklist_db import ( + add_blacklist, + get_blacklist, + list_blacklist, + rem_blacklist, +) + +from . import events, get_string, udB, ultroid_bot, ultroid_cmd + + +@ultroid_cmd(pattern="blacklist( (.*)|$)", admins_only=True) +async def af(e): + wrd = e.pattern_match.group(1).strip() + chat = e.chat_id + if not (wrd): + return await e.eor(get_string("blk_1"), time=5) + wrd = e.text[11:] + heh = wrd.split(" ") + for z in heh: + add_blacklist(int(chat), z.lower()) + ultroid_bot.add_handler(blacklist, events.NewMessage(incoming=True)) + await e.eor(get_string("blk_2").format(wrd)) + + +@ultroid_cmd(pattern="remblacklist( (.*)|$)", admins_only=True) +async def rf(e): + wrd = e.pattern_match.group(1).strip() + chat = e.chat_id + if not wrd: + return await e.eor(get_string("blk_3"), time=5) + wrd = e.text[14:] + heh = wrd.split(" ") + for z in heh: + rem_blacklist(int(chat), z.lower()) + await e.eor(get_string("blk_4").format(wrd)) + + +@ultroid_cmd(pattern="listblacklist$", admins_only=True) +async def lsnote(e): + if x := list_blacklist(e.chat_id): + sd = get_string("blk_5") + return await e.eor(sd + x) + await e.eor(get_string("blk_6")) + + +async def blacklist(e): + if x := get_blacklist(e.chat_id): + text = e.text.lower().split() + if any((z in text) for z in x): + try: + await e.delete() + except BaseException: + pass + + +if udB.get_key("BLACKLIST_DB"): + ultroid_bot.add_handler(blacklist, events.NewMessage(incoming=True)) diff --git a/plugins/bot.py b/plugins/bot.py new file mode 100644 index 0000000000000000000000000000000000000000..72550f5804b21aa6b65b625c06841a3757a7c58d --- /dev/null +++ b/plugins/bot.py @@ -0,0 +1,366 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_bot") + +import os +import sys +import time +from platform import python_version as pyver +from random import choice + +from telethon import __version__ +from telethon.errors.rpcerrorlist import ( + BotMethodInvalidError, + ChatSendMediaForbiddenError, +) + +from pyUltroid.version import __version__ as UltVer + +from . import HOSTED_ON, LOGS + +try: + from git import Repo +except ImportError: + LOGS.error("bot: 'gitpython' module not found!") + Repo = None + +from telethon.utils import resolve_bot_file_id + +from . import ( + ATRA_COL, + LOGS, + OWNER_NAME, + ULTROID_IMAGES, + Button, + Carbon, + Telegraph, + Var, + allcmds, + asst, + bash, + call_back, + callback, + def_logs, + eor, + get_string, + heroku_logs, + in_pattern, + inline_pic, + restart, + shutdown, + start_time, + time_formatter, + udB, + ultroid_cmd, + ultroid_version, + updater, +) + + +def ULTPIC(): + return inline_pic() or choice(ULTROID_IMAGES) + + +buttons = [ + [ + Button.url(get_string("bot_3"), "https://github.com/TeamUltroid/Ultroid"), + Button.url(get_string("bot_4"), "t.me/UltroidSupportChat"), + ] +] + +# Will move to strings +alive_txt = """ +The Ultroid Userbot + + ◍ Version - {} + ◍ Py-Ultroid - {} + ◍ Telethon - {} +""" + +in_alive = "{}\n\n🌀 Ultroid Version -> {}\n🌀 PyUltroid -> {}\n🌀 Python -> {}\n🌀 Uptime -> {}\n🌀 Branch ->[ {} ]\n\n• Join @TeamUltroid" + + +@callback("alive") +async def alive(event): + text = alive_txt.format(ultroid_version, UltVer, __version__) + await event.answer(text, alert=True) + + +@ultroid_cmd( + pattern="alive( (.*)|$)", +) +async def lol(ult): + match = ult.pattern_match.group(1).strip() + inline = None + if match in ["inline", "i"]: + try: + res = await ult.client.inline_query(asst.me.username, "alive") + return await res[0].click(ult.chat_id) + except BotMethodInvalidError: + pass + except BaseException as er: + LOGS.exception(er) + inline = True + pic = udB.get_key("ALIVE_PIC") + if isinstance(pic, list): + pic = choice(pic) + uptime = time_formatter((time.time() - start_time) * 1000) + header = udB.get_key("ALIVE_TEXT") or get_string("bot_1") + y = Repo().active_branch + xx = Repo().remotes[0].config_reader.get("url") + rep = xx.replace(".git", f"/tree/{y}") + kk = f" `[{y}]({rep})` " + if inline: + kk = f"{y}" + parse = "html" + als = in_alive.format( + header, + f"{ultroid_version} [{HOSTED_ON}]", + UltVer, + pyver(), + uptime, + kk, + ) + + if _e := udB.get_key("ALIVE_EMOJI"): + als = als.replace("🌀", _e) + else: + parse = "md" + als = (get_string("alive_1")).format( + header, + OWNER_NAME, + f"{ultroid_version} [{HOSTED_ON}]", + UltVer, + uptime, + pyver(), + __version__, + kk, + ) + + if a := udB.get_key("ALIVE_EMOJI"): + als = als.replace("✵", a) + if pic: + try: + await ult.reply( + als, + file=pic, + parse_mode=parse, + link_preview=False, + buttons=buttons if inline else None, + ) + return await ult.try_delete() + except ChatSendMediaForbiddenError: + pass + except BaseException as er: + LOGS.exception(er) + try: + await ult.reply(file=pic) + await ult.reply( + als, + parse_mode=parse, + buttons=buttons if inline else None, + link_preview=False, + ) + return await ult.try_delete() + except BaseException as er: + LOGS.exception(er) + await eor( + ult, + als, + parse_mode=parse, + link_preview=False, + buttons=buttons if inline else None, + ) + + +@ultroid_cmd(pattern="ping$", chats=[], type=["official", "assistant"]) +async def _(event): + start = time.time() + x = await event.eor("Pong !") + end = round((time.time() - start) * 1000) + uptime = time_formatter((time.time() - start_time) * 1000) + await x.edit(get_string("ping").format(end, uptime)) + + +@ultroid_cmd( + pattern="cmds$", +) +async def cmds(event): + await allcmds(event, Telegraph) + + +heroku_api = Var.HEROKU_API + + +@ultroid_cmd( + pattern="restart$", + fullsudo=True, +) +async def restartbt(ult): + ok = await ult.eor(get_string("bot_5")) + call_back() + who = "bot" if ult.client._bot else "user" + udB.set_key("_RESTART", f"{who}_{ult.chat_id}_{ok.id}") + if heroku_api: + return await restart(ok) + await bash("git pull && pip3 install -r requirements.txt") + await bash("pip3 install -r requirements.txt --break-system-packages") + if len(sys.argv) > 1: + os.execl(sys.executable, sys.executable, "main.py") + else: + os.execl(sys.executable, sys.executable, "-m", "pyUltroid") + + +@ultroid_cmd( + pattern="shutdown$", + fullsudo=True, +) +async def shutdownbot(ult): + await shutdown(ult) + + +@ultroid_cmd( + pattern="logs( (.*)|$)", + chats=[], +) +async def _(event): + opt = event.pattern_match.group(1).strip() + file = f"ultroid{sys.argv[-1]}.log" if len(sys.argv) > 1 else "ultroid.log" + if opt == "heroku": + await heroku_logs(event) + elif opt == "carbon" and Carbon: + event = await event.eor(get_string("com_1")) + with open(file, "r") as f: + code = f.read()[-2500:] + file = await Carbon( + file_name="ultroid-logs", + code=code, + backgroundColor=choice(ATRA_COL), + ) + if isinstance(file, dict): + await event.eor(f"`{file}`") + return + await event.reply("**Ultroid Logs.**", file=file) + elif opt == "open": + with open("ultroid.log", "r") as f: + file = f.read()[-4000:] + return await event.eor(f"`{file}`") + elif ( + opt.isdigit() and 5 <= int(opt) <= 100 + ): # Check if input is a number between 10 and 100 + num_lines = int(opt) + with open("ultroid.log", "r") as f: + lines = f.readlines()[-num_lines:] + file = "".join(lines) + return await event.eor(f"`{file}`") + else: + await def_logs(event, file) + await event.try_delete() + + +@in_pattern("alive", owner=True) +async def inline_alive(ult): + pic = udB.get_key("ALIVE_PIC") + if isinstance(pic, list): + pic = choice(pic) + uptime = time_formatter((time.time() - start_time) * 1000) + header = udB.get_key("ALIVE_TEXT") or get_string("bot_1") + y = Repo().active_branch + xx = Repo().remotes[0].config_reader.get("url") + rep = xx.replace(".git", f"/tree/{y}") + kk = f"{y}" + als = in_alive.format( + header, f"{ultroid_version} [{HOSTED_ON}]", UltVer, pyver(), uptime, kk + ) + + if _e := udB.get_key("ALIVE_EMOJI"): + als = als.replace("🌀", _e) + builder = ult.builder + if pic: + try: + if ".jpg" in pic: + results = [ + await builder.photo( + pic, text=als, parse_mode="html", buttons=buttons + ) + ] + else: + if _pic := resolve_bot_file_id(pic): + pic = _pic + buttons.insert( + 0, [Button.inline(get_string("bot_2"), data="alive")] + ) + results = [ + await builder.document( + pic, + title="Inline Alive", + description="@TeamUltroid", + parse_mode="html", + buttons=buttons, + ) + ] + return await ult.answer(results) + except BaseException as er: + LOGS.exception(er) + result = [ + await builder.article( + "Alive", text=als, parse_mode="html", link_preview=False, buttons=buttons + ) + ] + await ult.answer(result) + + +@ultroid_cmd(pattern="update( (.*)|$)") +async def _(e): + xx = await e.eor(get_string("upd_1")) + if e.pattern_match.group(1).strip() and ( + "fast" in e.pattern_match.group(1).strip() + or "soft" in e.pattern_match.group(1).strip() + ): + await bash("git pull -f && pip3 install -r requirements.txt") + await bash("pip3 install -r requirements.txt --break-system-packages") + call_back() + await xx.edit(get_string("upd_7")) + os.execl(sys.executable, "python3", "-m", "pyUltroid") + # return + m = await updater() + branch = (Repo.init()).active_branch + if m: + x = await asst.send_file( + udB.get_key("LOG_CHANNEL"), + ULTPIC(), + caption="• **Update Available** •", + force_document=False, + buttons=Button.inline("Changelogs", data="changes"), + ) + Link = x.message_link + await xx.edit( + f'[ChangeLogs]', + parse_mode="html", + link_preview=False, + ) + else: + await xx.edit( + f'Your BOT is up-to-date with [{branch}]', + parse_mode="html", + link_preview=False, + ) + + +@callback("updtavail", owner=True) +async def updava(event): + await event.delete() + await asst.send_file( + udB.get_key("LOG_CHANNEL"), + ULTPIC(), + caption="• **Update Available** •", + force_document=False, + buttons=Button.inline("Changelogs", data="changes"), + ) diff --git a/plugins/broadcast.py b/plugins/broadcast.py new file mode 100644 index 0000000000000000000000000000000000000000..9734c8ef63d0c9b055400f67057509b292051ee1 --- /dev/null +++ b/plugins/broadcast.py @@ -0,0 +1,261 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from . import get_help + +__doc__ = get_help("help_broadcast") + +import asyncio +import io + +from telethon.utils import get_display_name + +from pyUltroid.dB.base import KeyManager + +from . import HNDLR, LOGS, eor, get_string, udB, ultroid_bot, ultroid_cmd + +KeyM = KeyManager("BROADCAST", cast=list) +BlackM = KeyManager("BROADCAST_BLACKLIST", cast=list) + + +@ultroid_cmd( + pattern=r"addblacklist( (.*)|$)", + allow_sudo=False, +) +async def broadcast_blacklist_add(event): + msgg = event.pattern_match.group(1).strip() + chat_id = int(msgg) if msgg and msgg.lstrip("-").isdigit() else event.chat_id + if BlackM.contains(chat_id): + return await event.eor("`Already in broadcast blacklist.`", time=5) + BlackM.add(chat_id) + await event.eor(f"`Added {chat_id} to broadcast blacklist.`", time=5) + + +@ultroid_cmd( + pattern=r"remblacklist( (.*)|$)", + allow_sudo=False, +) +async def broadcast_blacklist_rem(event): + msgg = event.pattern_match.group(1).strip() + chat_id = int(msgg) if msgg and msgg.lstrip("-").isdigit() else event.chat_id + if not BlackM.contains(chat_id): + return await event.eor("`Not in broadcast blacklist.`", time=5) + BlackM.remove(chat_id) + await event.eor(f"`Removed {chat_id} from broadcast blacklist.`", time=5) + + +@ultroid_cmd( + pattern="listblacklist$", + allow_sudo=False, +) +async def broadcast_blacklist_list(event): + bl = BlackM.get() + if not bl: + return await event.eor("`Broadcast blacklist is empty.`", time=5) + msg = "**Broadcast Blacklist:**\n" + "\n".join(f"• `{c}`" for c in bl) + await event.eor(msg) + + +@ultroid_cmd( + pattern="addch( (.*)|$)", + allow_sudo=False, +) +async def broadcast_adder(event): + msgg = event.pattern_match.group(1).strip() + x = await event.eor(get_string("bd_1")) + if msgg == "all": + await x.edit(get_string("bd_2")) + chats = [ + e.entity + for e in await event.client.get_dialogs() + if (e.is_group or e.is_channel) + ] + new = 0 + for i in chats: + try: + if ( + i.broadcast + and (i.creator or i.admin_rights) + and not KeyM.contains(i.id) + ): + new += 1 + cid = f"-100{i.id}" + KeyM.add(int(cid)) + except Exception as Ex: + LOGS.exception(Ex) + await x.edit(get_string("bd_3").format(KeyM.count(), new)) + return + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + raw_text = previous_message.text + lines = raw_text.split("\n") + length = len(lines) + for line_number in range(1, length - 2): + channel_id = lines[line_number][4:-1] + if not KeyM.contains(channel_id): + KeyM.add(channel_id) + await x.edit(get_string("bd_4")) + await asyncio.sleep(3) + await event.delete() + return + chat_id = event.chat_id + if chat_id == udB.get_key("LOG_CHANNEL"): + return + if KeyM.contains(chat_id): + await x.edit(get_string("bd_6")) + elif xx := KeyM.add(chat_id): + await x.edit(get_string("bd_5")) + else: + await x.edit(get_string("sf_8")) + await asyncio.sleep(3) + await x.delete() + + +@ultroid_cmd( + pattern="remch( (.*)|$)", + allow_sudo=False, +) +async def broadcast_remover(event): + chat_id = event.pattern_match.group(1).strip() or event.chat_id + x = await event.eor(get_string("com_1")) + if chat_id == "all": + await x.edit(get_string("bd_8")) + udB.del_key("BROADCAST") + await x.edit("Database cleared.") + return + if KeyM.contains(chat_id): + KeyM.remove(chat_id) + await x.edit(get_string("bd_7")) + else: + await x.edit(get_string("bd_9")) + await asyncio.sleep(3) + await x.delete() + + +@ultroid_cmd( + pattern="listchannels$", +) +async def list_all(event): + x = await event.eor(get_string("com_1")) + channels = KeyM.get() + num = KeyM.count() + if not channels: + return await eor(x, "No chats were added.", time=5) + msg = "Channels in database:\n" + for channel in channels: + name = "" + try: + name = get_display_name(await event.client.get_entity(channel)) + except ValueError: + name = "" + msg += f"=> **{name}** [`{channel}`]\n" + msg += f"\nTotal {num} channels." + if len(msg) > 4096: + MSG = msg.replace("*", "").replace("`", "") + with io.BytesIO(str.encode(MSG)) as out_file: + out_file.name = "channels.txt" + await event.reply( + "Channels in Database", + file=out_file, + force_document=True, + allow_cache=False, + ) + await x.delete() + else: + await x.edit(msg) + + +@ultroid_cmd( + pattern="forward$", + allow_sudo=False, +) +async def forw(event): + if not event.is_reply: + return await event.eor(get_string("ex_1")) + ultroid_bot = event.client + channels = KeyM.get() + x = await event.eor("Sending...") + if not channels: + return await x.edit(f"Please add channels by using `{HNDLR}add` in them.") + error_count = 0 + sent_count = 0 + previous_message = await event.get_reply_message() + error_count = 0 + blacklist = BlackM.get() or [] + for channel in channels: + if channel in blacklist: + continue + try: + await ultroid_bot.forward_messages(channel, previous_message) + sent_count += 1 + await x.edit( + f"Sent : {sent_count}\nError : {error_count}\nTotal : {len(channels)}", + ) + except Exception: + try: + await ultroid_bot.send_message( + udB.get_key("LOG_CHANNEL"), + f"Error in sending at {channel}.", + ) + except Exception as Em: + LOGS.info(Em) + error_count += 1 + await x.edit( + f"Sent : {sent_count}\nError : {error_count}\nTotal : {len(channels)}", + ) + await x.edit(f"{sent_count} messages sent with {error_count} errors.") + if error_count > 0: + await ultroid_bot.send_message( + udB.get_key("LOG_CHANNEL"), f"{error_count} Errors" + ) + + +@ultroid_cmd( + pattern="broadcast( (.*)|$)", + allow_sudo=False, +) +async def sending(event): + x = await event.eor(get_string("com_1")) + if not event.is_reply: + return await x.edit(get_string("ex_1")) + channels = KeyM.get() + if not channels: + return await x.edit(f"Please add channels by using `{HNDLR}add` in them.") + await x.edit("Sending....") + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + if previous_message.poll: + return await x.edit(f"Reply `{HNDLR}forward` for polls.") + if previous_message: + error_count = 0 + sent_count = 0 + blacklist = BlackM.get() or [] + for channel in channels: + if channel in blacklist: + continue + try: + await ultroid_bot.send_message(channel, previous_message) + sent_count += 1 + await x.edit( + f"Sent : {sent_count}\nError : {error_count}\nTotal : {len(channels)}", + ) + except Exception as error: + await ultroid_bot.send_message( + udB.get_key("LOG_CHANNEL"), + f"Error in sending at {channel}.\n\n{error}", + ) + error_count += 1 + await x.edit( + f"Sent : {sent_count}\nError : {error_count}\nTotal : {len(channels)}", + ) + await x.edit(f"{sent_count} messages sent with {error_count} errors.") + if error_count > 0: + await ultroid_bot.send_message( + udB.get_key("LOG_CHANNEL"), + f"{error_count} Errors", + ) diff --git a/plugins/button.py b/plugins/button.py new file mode 100644 index 0000000000000000000000000000000000000000..a41ef0a78034da6c0c088c3130ff163291d03e2c --- /dev/null +++ b/plugins/button.py @@ -0,0 +1,54 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_button") + +import os + +from . import upload_file as uf +from telethon.utils import pack_bot_file_id + +from pyUltroid.fns.tools import create_tl_btn, get_msg_button + +from . import HNDLR, get_string, mediainfo, ultroid_cmd +from ._inline import something + + +@ultroid_cmd(pattern="button") +async def butt(event): + media, wut, text = None, None, None + if event.reply_to: + wt = await event.get_reply_message() + if wt.text: + text = wt.text + if wt.media: + wut = mediainfo(wt.media) + if wut and wut.startswith(("pic", "gif")): + dl = await wt.download_media() + media = uf(dl) + elif wut == "video": + if wt.media.document.size > 8 * 1000 * 1000: + return await event.eor(get_string("com_4"), time=5) + dl = await wt.download_media() + media = uf(dl) + os.remove(dl) + else: + media = pack_bot_file_id(wt.media) + try: + text = event.text.split(maxsplit=1)[1] + except IndexError: + if not text: + return await event.eor( + f"**Please give some text in correct format.**\n\n`{HNDLR}help button`", + ) + text, buttons = get_msg_button(text) + if buttons: + buttons = create_tl_btn(buttons) + await something(event, text, media, buttons) + await event.delete() diff --git a/plugins/calculator.py b/plugins/calculator.py new file mode 100644 index 0000000000000000000000000000000000000000..5e318e6a3daceac878d31a660967b4986fd14be0 --- /dev/null +++ b/plugins/calculator.py @@ -0,0 +1,153 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from . import get_help + +__doc__ = get_help("help_calculator") + +import re + +from . import Button, asst, callback, get_string, in_pattern, udB, ultroid_cmd + +CALC = {} + +m = [ + "AC", + "C", + "⌫", + "%", + "7", + "8", + "9", + "+", + "4", + "5", + "6", + "-", + "1", + "2", + "3", + "x", + "00", + "0", + ".", + "÷", +] +tultd = [Button.inline(f"{x}", data=f"calc{x}") for x in m] +lst = list(zip(tultd[::4], tultd[1::4], tultd[2::4], tultd[3::4])) +lst.append([Button.inline("=", data="calc=")]) + + +@ultroid_cmd(pattern="calc") +async def icalc(e): + udB.del_key("calc") + if e.client._bot: + return await e.reply(get_string("calc_1"), buttons=lst) + results = await e.client.inline_query(asst.me.username, "calc") + await results[0].click(e.chat_id, silent=True, hide_via=True) + await e.delete() + + +@in_pattern("calc", owner=True) +async def _(e): + calc = e.builder.article("Calc", text=get_string("calc_1"), buttons=lst) + await e.answer([calc]) + + +@callback(re.compile("calc(.*)"), owner=True) +async def _(e): + x = (e.data_match.group(1)).decode() + user = e.query.user_id + get = None + if x == "AC": + if CALC.get(user): + CALC.pop(user) + await e.edit( + get_string("calc_1"), + buttons=[Button.inline(get_string("calc_2"), data="recalc")], + ) + elif x == "C": + if CALC.get(user): + CALC.pop(user) + await e.answer("cleared") + elif x == "⌫": + if CALC.get(user): + get = CALC[user] + if get: + CALC.update({user: get[:-1]}) + await e.answer(str(get[:-1])) + elif x == "%": + if CALC.get(user): + get = CALC[user] + if get: + CALC.update({user: f"{get}/100"}) + await e.answer(str(f"{get}/100")) + elif x == "÷": + if CALC.get(user): + get = CALC[user] + if get: + CALC.update({user: f"{get}/"}) + await e.answer(str(f"{get}/")) + elif x == "x": + if CALC.get(user): + get = CALC[user] + if get: + CALC.update({user: f"{get}*"}) + await e.answer(str(f"{get}*")) + elif x == "=": + if CALC.get(user): + get = CALC[user] + if get: + if get.endswith(("*", ".", "/", "-", "+")): + get = get[:-1] + out = eval(get) + try: + num = float(out) + await e.answer(f"Answer : {num}", cache_time=0, alert=True) + except BaseException: + CALC.pop(user) + await e.answer(get_string("sf_8"), cache_time=0, alert=True) + await e.answer("None") + else: + if CALC.get(user): + get = CALC[user] + if get: + CALC.update({user: get + x}) + return await e.answer(str(get + x)) + CALC.update({user: x}) + await e.answer(str(x)) + + +@callback("recalc", owner=True) +async def _(e): + m = [ + "AC", + "C", + "⌫", + "%", + "7", + "8", + "9", + "+", + "4", + "5", + "6", + "-", + "1", + "2", + "3", + "x", + "00", + "0", + ".", + "÷", + ] + tultd = [Button.inline(f"{x}", data=f"calc{x}") for x in m] + lst = list(zip(tultd[::4], tultd[1::4], tultd[2::4], tultd[3::4])) + lst.append([Button.inline("=", data="calc=")]) + await e.edit(get_string("calc_1"), buttons=lst) diff --git a/plugins/channelhacks.py b/plugins/channelhacks.py new file mode 100644 index 0000000000000000000000000000000000000000..c18ead87c5704e9c92f06838da68dc52dbae896d --- /dev/null +++ b/plugins/channelhacks.py @@ -0,0 +1,224 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +from . import get_help + +__doc__ = get_help("help_channelhacks") + + +import asyncio +import io + +from telethon.errors.rpcerrorlist import FloodWaitError +from telethon.utils import get_display_name, get_peer_id + +from pyUltroid.dB.base import KeyManager + +from . import LOGS, asst, eor, events, get_string, udB, ultroid_bot, ultroid_cmd + +ERROR = {} +SourceM = KeyManager("CH_SOURCE", cast=list) +DestiM = KeyManager("CH_DESTINATIONS", cast=list) + + +async def autopost_func(e): + if not udB.get_key("AUTOPOST"): + return + x = SourceM.get() + th = await e.get_chat() + if get_peer_id(th) not in x: + return + y = DestiM.get() + for ys in y: + try: + await e.client.send_message(int(ys), e.message) + except Exception as ex: + try: + ERROR[str(ex)] + except KeyError: + ERROR.update({str(ex): ex}) + Error = f"**Error on AUTOPOST**\n\n`{ex}`" + await asst.send_message(udB.get_key("LOG_CHANNEL"), Error) + + +@ultroid_cmd(pattern="shift (.*)") +async def _(e): + x = e.pattern_match.group(1).strip() + z = await e.eor(get_string("com_1")) + a, b = x.split("|") + try: + c = await e.client.parse_id(a) + except Exception: + await z.edit(get_string("cha_1")) + return + try: + d = await e.client.parse_id(b) + except Exception as er: + LOGS.exception(er) + await z.edit(get_string("cha_1")) + return + async for msg in e.client.iter_messages(int(c), reverse=True, limit=None): + try: + await asyncio.sleep(2) + await e.client.send_message(int(d), msg) + except FloodWaitError as er: + await asyncio.sleep(er.seconds + 5) + await e.client.send_message(int(d), msg) + except BaseException as er: + LOGS.exception(er) + await z.edit("Done") + + +@ultroid_cmd(pattern="asource (.*)") +async def source(e): + if x := e.pattern_match.group(1).strip(): + try: + y = await e.client.parse_id(x) + except Exception as er: + LOGS.exception(er) + return + else: + y = e.chat_id + if not SourceM.contains(y): + SourceM.add(y) + await e.eor(get_string("cha_2")) + ultroid_bot.add_handler(autopost_func, events.NewMessage()) + else: + await e.eor(get_string("cha_3")) + + +@ultroid_cmd(pattern="dsource( (.*)|$)") +async def dd(event): + chat_id = event.pattern_match.group(1).strip() + x = await event.eor(get_string("com_1")) + if chat_id == "all": + await x.edit(get_string("bd_8")) + udB.del_key("CH_SOURCE") + await x.edit(get_string("cha_4")) + return + if chat_id: + try: + y = await event.client.parse_id(chat_id) + except Exception as er: + LOGS.exception(er) + return + else: + y = event.chat_id + if SourceM.contains(y): + SourceM.remove(y) + await eor(x, get_string("cha_5"), time=5) + else: + await eor(x, "Source channel is already removed from database. ", time=3) + + +@ultroid_cmd(pattern="listsource") +async def list_all(event): + x = await event.eor(get_string("com_1")) + num = SourceM.count() + if not num: + return await eor(x, "No chats were added.", time=5) + msg = get_string("cha_8") + channels = SourceM.get() + for channel in channels: + name = "" + try: + name = get_display_name(await event.client.get_entity(int(channel))) + except BaseException: + name = "" + msg += f"\n=> **{name}** [`{channel}`]" + msg += f"\nTotal {num} channels." + if len(msg) > 4096: + MSG = msg.replace("*", "").replace("`", "") + with io.BytesIO(str.encode(MSG)) as out_file: + out_file.name = "channels.txt" + await event.reply( + "Channels in database", + file=out_file, + force_document=True, + allow_cache=False, + ) + await x.delete() + else: + await x.edit(msg) + + +@ultroid_cmd(pattern="adest (.*)") +async def destination(e): + if x := e.pattern_match.group(1).strip(): + try: + y = await e.client.parse_id(x) + except Exception as er: + LOGS.exception(er) + return + else: + y = e.chat_id + if not DestiM.contains(y): + DestiM.add(y) + await e.eor("Destination added succesfully") + else: + await e.eor("Destination channel already added") + + +@ultroid_cmd(pattern="ddest( (.*)|$)") +async def dd(event): + chat_id = event.pattern_match.group(1).strip() + x = await event.eor(get_string("com_1")) + if chat_id == "all": + await x.edit(get_string("bd_8")) + udB.del_key("CH_DESTINATION") + await x.edit("Destinations database cleared.") + return + if chat_id: + try: + y = await event.client.parse_id(chat_id) + except Exception as er: + LOGS.exception(er) + return + else: + y = event.chat_id + if DestiM.contains(y): + DestiM.remove(y) + await eor(x, "Destination removed from database") + else: + await eor(x, "Destination channel is already removed from database. ", time=5) + + +@ultroid_cmd(pattern="listdest") +async def list_all(event): + ultroid_bot = event.client + x = await event.eor(get_string("com_1")) + channels = DestiM.get() + num = len(channels) + if not num: + return await eor(x, "No chats were added.", time=5) + msg = get_string("cha_7") + for channel in channels: + name = "" + try: + name = get_display_name(await ultroid_bot.get_entity(int(channel))) + except BaseException: + name = "" + msg += f"\n=> **{name}** [`{channel}`]" + msg += f"\nTotal {num} channels." + if len(msg) > 4096: + MSG = msg.replace("*", "").replace("`", "") + with io.BytesIO(str.encode(MSG)) as out_file: + out_file.name = "channels.txt" + await ultroid_bot.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + caption="Destination channels in database", + reply_to=event, + ) + await x.delete() + else: + await x.edit(msg) + + +if udB.get_key("AUTOPOST"): + ultroid_bot.add_handler(autopost_func, events.NewMessage()) diff --git a/plugins/chatbot.py b/plugins/chatbot.py new file mode 100644 index 0000000000000000000000000000000000000000..0e01a9cf2939b3c88eac05e045fb7aaafbdc0b65 --- /dev/null +++ b/plugins/chatbot.py @@ -0,0 +1,89 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_chatbot") + + +from pyUltroid.fns.tools import get_chatbot_reply + +from . import LOGS, eod, get_string, inline_mention, udB, ultroid_cmd + + +@ultroid_cmd(pattern="repai") +async def im_lonely_chat_with_me(event): + if event.reply_to: + message = (await event.get_reply_message()).message + else: + try: + message = event.text.split(" ", 1)[1] + except IndexError: + return await eod(event, get_string("tban_1"), time=10) + reply_ = await get_chatbot_reply(message=message) + await event.eor(reply_) + + +@ultroid_cmd(pattern="addai") +async def add_chatBot(event): + await chat_bot_fn(event, type_="add") + + +@ultroid_cmd(pattern="remai") +async def rem_chatBot(event): + await chat_bot_fn(event, type_="remov") + + +@ultroid_cmd(pattern="listai") +async def lister(event): + key = udB.get_key("CHATBOT_USERS") or {} + users = key.get(event.chat_id, []) + if not users: + return await event.eor(get_string("chab_2"), time=5) + msg = "**Total List Of AI Enabled Users In This Chat :**\n\n" + for i in users: + try: + user = await event.client.get_entity(int(i)) + user = inline_mention(user) + except BaseException: + user = f"`{i}`" + msg += f"• {user}\n" + await event.eor(msg, link_preview=False) + + +async def chat_bot_fn(event, type_): + if event.reply_to: + user_ = (await event.get_reply_message()).sender + else: + temp = event.text.split(maxsplit=1) + try: + user_ = await event.client.get_entity(await event.client.parse_id(temp[1])) + except BaseException as er: + LOGS.exception(er) + user_ = event.chat if event.is_private else None + if not user_: + return await eod( + event, + get_string("chab_1"), + ) + key = udB.get_key("CHATBOT_USERS") or {} + chat = event.chat_id + user = user_.id + if type_ == "add": + if key.get(chat): + if user not in key[chat]: + key[chat].append(user) + else: + key.update({chat: [user]}) + elif type_ == "remov": + if key.get(chat): + if user in key[chat]: + key[chat].remove(user) + if chat in key and not key[chat]: + del key[chat] + udB.set_key("CHATBOT_USERS", key) + await event.eor(f"**ChatBot:**\n{type_}ed {inline_mention(user_)}") diff --git a/plugins/chats.py b/plugins/chats.py new file mode 100644 index 0000000000000000000000000000000000000000..3832650900b10c622a5a7fcfa4cde8e29824f135 --- /dev/null +++ b/plugins/chats.py @@ -0,0 +1,368 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +from . import get_help + +__doc__ = get_help("help_chats") + + +from telethon.errors import ChatAdminRequiredError as no_admin +from telethon.tl.functions.channels import ( + CreateChannelRequest, + DeleteChannelRequest, + EditPhotoRequest, + GetFullChannelRequest, + UpdateUsernameRequest, +) +from telethon.tl.functions.messages import ( + CreateChatRequest, + ExportChatInviteRequest, + GetFullChatRequest, +) +from telethon.tl.types import ( + ChannelParticipantsKicked, + User, + UserStatusEmpty, + UserStatusLastMonth, + UserStatusLastWeek, + UserStatusOffline, + UserStatusOnline, + UserStatusRecently, +) + +from . import HNDLR, LOGS, asst, con, get_string, mediainfo, os, types, udB, ultroid_cmd + + +@ultroid_cmd( + pattern="delchat", + groups_only=True, +) +async def _(e): + xx = await e.eor(get_string("com_1")) + try: + match = e.text.split(" ", maxsplit=1)[1] + chat = await e.client.parse_id(match) + except IndexError: + chat = e.chat_id + try: + await e.client(DeleteChannelRequest(chat)) + except TypeError: + return await xx.eor(get_string("chats_1"), time=10) + except no_admin: + return await xx.eor(get_string("chats_2"), time=10) + await e.client.send_message( + int(udB.get_key("LOG_CHANNEL")), get_string("chats_6").format(e.chat_id) + ) + + +@ultroid_cmd( + pattern="getlink( (.*)|$)", + groups_only=True, + manager=True, +) +async def _(e): + reply = await e.get_reply_message() + match = e.pattern_match.group(1).strip() + if reply and not isinstance(reply.sender, User): + chat = await reply.get_sender() + else: + chat = await e.get_chat() + if hasattr(chat, "username") and chat.username: + return await e.eor(f"Username: @{chat.username}") + request, usage, title, link = None, None, None, None + if match: + split = match.split(maxsplit=1) + request = split[0] in ["r", "request"] + title = "Created by Ultroid" + if len(split) > 1: + match = split[1] + spli = match.split(maxsplit=1) + if spli[0].isdigit(): + usage = int(spli[0]) + if len(spli) > 1: + title = spli[1] + elif not request: + if match.isdigit(): + usage = int(match) + else: + title = match + if request and usage: + usage = 0 + if request or title: + try: + r = await e.client( + ExportChatInviteRequest( + e.chat_id, + request_needed=request, + usage_limit=usage, + title=title, + ), + ) + except no_admin: + return await e.eor(get_string("chats_2"), time=10) + link = r.link + else: + if isinstance(chat, types.Chat): + FC = await e.client(GetFullChatRequest(chat.id)) + elif isinstance(chat, types.Channel): + FC = await e.client(GetFullChannelRequest(chat.id)) + else: + return + Inv = FC.full_chat.exported_invite + if Inv and not Inv.revoked: + link = Inv.link + if link: + return await e.eor(f"Link:- {link}") + await e.eor("`Failed to getlink!\nSeems like link is inaccessible to you...`") + + +@ultroid_cmd( + pattern="create (b|g|c)(?: |$)(.*)", +) +async def _(e): + type_of_group = e.pattern_match.group(1).strip() + group_name = e.pattern_match.group(2) + username = None + if " ; " in group_name: + group_ = group_name.split(" ; ", maxsplit=1) + group_name = group_[0] + username = group_[1] + xx = await e.eor(get_string("com_1")) + if type_of_group == "b": + try: + r = await e.client( + CreateChatRequest( + users=[asst.me.username], + title=group_name, + ), + ) + created_chat_id = r.chats[0].id + result = await e.client( + ExportChatInviteRequest( + peer=created_chat_id, + ), + ) + await xx.edit( + get_string("chats_4").format(group_name, result.link), + link_preview=False, + ) + except Exception as ex: + await xx.edit(str(ex)) + elif type_of_group in ["g", "c"]: + try: + r = await e.client( + CreateChannelRequest( + title=group_name, + about=get_string("chats_5"), + megagroup=type_of_group != "c", + ) + ) + + created_chat_id = r.chats[0].id + if username: + await e.client(UpdateUsernameRequest(created_chat_id, username)) + result = f"https://t.me/{username}" + else: + result = ( + await e.client( + ExportChatInviteRequest( + peer=created_chat_id, + ), + ) + ).link + await xx.edit( + get_string("chats_6").format(f"[{group_name}]({result})"), + link_preview=False, + ) + except Exception as ex: + await xx.edit(str(ex)) + + +# ---------------------------------------------------------------- # + + +@ultroid_cmd( + pattern="setgpic( (.*)|$)", admins_only=True, manager=True, require="change_info" +) +async def _(ult): + if not ult.is_reply: + return await ult.eor("`Reply to a Media..`", time=5) + match = ult.pattern_match.group(1).strip() + if not ult.client._bot and match: + try: + chat = await ult.client.parse_id(match) + except Exception as ok: + return await ult.eor(str(ok)) + else: + chat = ult.chat_id + reply = await ult.get_reply_message() + if reply.photo or reply.sticker or reply.video: + replfile = await reply.download_media() + elif reply.document and reply.document.thumbs: + replfile = await reply.download_media(thumb=-1) + else: + return await ult.eor("Reply to a Photo or Video..") + mediain = mediainfo(reply.media) + if "animated" in mediain: + replfile = await con.convert(replfile, convert_to="mp4") + else: + replfile = await con.convert( + replfile, outname="chatphoto", allowed_formats=["jpg", "png", "mp4"] + ) + file = await ult.client.upload_file(replfile) + try: + if "pic" not in mediain: + file = types.InputChatUploadedPhoto(video=file) + await ult.client(EditPhotoRequest(chat, file)) + await ult.eor("`Group Photo has Successfully Changed !`", time=5) + except Exception as ex: + await ult.eor(f"Error occured.\n`{str(ex)}`", time=5) + os.remove(replfile) + + +@ultroid_cmd( + pattern="delgpic( (.*)|$)", admins_only=True, manager=True, require="change_info" +) +async def _(ult): + match = ult.pattern_match.group(1).strip() + chat = ult.chat_id + if not ult.client._bot and match: + chat = match + try: + await ult.client(EditPhotoRequest(chat, types.InputChatPhotoEmpty())) + text = "`Removed Chat Photo..`" + except Exception as E: + text = str(E) + return await ult.eor(text, time=5) + + +@ultroid_cmd(pattern="unbanall$", manager=True, admins_only=True, require="ban_users") +async def _(event): + xx = await event.eor("Searching Participant Lists.") + p = 0 + title = (await event.get_chat()).title + async for i in event.client.iter_participants( + event.chat_id, + filter=ChannelParticipantsKicked, + aggressive=True, + ): + try: + await event.client.edit_permissions(event.chat_id, i, view_messages=True) + p += 1 + except no_admin: + pass + except BaseException as er: + LOGS.exception(er) + await xx.eor(f"{title}: {p} unbanned", time=5) + + +@ultroid_cmd( + pattern="rmusers( (.*)|$)", + groups_only=True, + admins_only=True, + fullsudo=True, +) +async def _(event): + xx = await event.eor(get_string("com_1")) + input_str = event.pattern_match.group(1).strip() + p, a, b, c, d, m, n, y, w, o, q, r = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + async for i in event.client.iter_participants(event.chat_id): + p += 1 # Total Count + if isinstance(i.status, UserStatusEmpty): + if "empty" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + y += 1 + if isinstance(i.status, UserStatusLastMonth): + if "month" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + m += 1 + if isinstance(i.status, UserStatusLastWeek): + if "week" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + w += 1 + if isinstance(i.status, UserStatusOffline): + if "offline" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + o += 1 + if isinstance(i.status, UserStatusOnline): + if "online" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + q += 1 + if isinstance(i.status, UserStatusRecently): + if "recently" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + r += 1 + if i.bot: + if "bot" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + b += 1 + elif i.deleted: + if "deleted" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + d += 1 + elif i.status is None: + if "none" in input_str: + try: + await event.client.kick_participant(event.chat_id, i) + c += 1 + except BaseException: + pass + else: + n += 1 + if input_str: + required_string = f"**>> Kicked** `{c} / {p}` **users**\n\n" + else: + required_string = f"**>> Total** `{p}` **users**\n\n" + required_string += f" `{HNDLR}rmusers deleted` **••** `{d}`\n" + required_string += f" `{HNDLR}rmusers empty` **••** `{y}`\n" + required_string += f" `{HNDLR}rmusers month` **••** `{m}`\n" + required_string += f" `{HNDLR}rmusers week` **••** `{w}`\n" + required_string += f" `{HNDLR}rmusers offline` **••** `{o}`\n" + required_string += f" `{HNDLR}rmusers online` **••** `{q}`\n" + required_string += f" `{HNDLR}rmusers recently` **••** `{r}`\n" + required_string += f" `{HNDLR}rmusers bot` **••** `{b}`\n" + required_string += f" `{HNDLR}rmusers none` **••** `{n}`" + await xx.eor(required_string) diff --git a/plugins/cleanaction.py b/plugins/cleanaction.py new file mode 100644 index 0000000000000000000000000000000000000000..815b5dde81e4d603ab6b5cde749b5542d0670a0d --- /dev/null +++ b/plugins/cleanaction.py @@ -0,0 +1,48 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_cleanaction") + + +from telethon.utils import get_display_name + +from . import get_string, udB, ultroid_cmd + + +@ultroid_cmd(pattern="addclean$", admins_only=True) +async def _(e): + key = udB.get_key("CLEANCHAT") or [] + if e.chat_id in key: + return await eod(e, get_string("clan_5")) + key.append(e.chat_id) + udB.set_key("CLEANCHAT", key) + await e.eor(get_string("clan_1"), time=5) + + +@ultroid_cmd(pattern="remclean$") +async def _(e): + key = udB.get_key("CLEANCHAT") or [] + if e.chat_id in key: + key.remove(e.chat_id) + udB.set_key("CLEANCHAT", key) + await e.eor(get_string("clan_2"), time=5) + + +@ultroid_cmd(pattern="listclean$") +async def _(e): + if k := udB.get_key("CLEANCHAT"): + o = "" + for x in k: + try: + title = get_display_name(await e.client.get_entity(x)) + except BaseException: + title = get_string("clan_3") + o += f"{x} {title}\n" + return await e.eor(o) + await e.eor(get_string("clan_4"), time=5) diff --git a/plugins/compressor.py b/plugins/compressor.py new file mode 100644 index 0000000000000000000000000000000000000000..2eb7e0e01ff890a225c85a007259805bb324338a --- /dev/null +++ b/plugins/compressor.py @@ -0,0 +1,177 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_compressor") + + +import asyncio +import os +import re +import time +from datetime import datetime as dt + +from telethon.errors.rpcerrorlist import MessageNotModifiedError +from telethon.tl.types import DocumentAttributeVideo + +from pyUltroid.fns.tools import metadata + +from . import ( + ULTConfig, + bash, + downloader, + get_string, + humanbytes, + math, + mediainfo, + time_formatter, + ultroid_cmd, + uploader, +) + + +@ultroid_cmd(pattern="compress( (.*)|$)") +async def _(e): + cr = e.pattern_match.group(1).strip() + crf = 27 + to_stream = False + if cr: + k = e.text.split() + if len(k) == 2: + crf = int(k[1]) if k[1].isdigit() else 27 + elif len(k) > 2: + crf = int(k[1]) if k[1].isdigit() else 27 + to_stream = "stream" in k[2] + vido = await e.get_reply_message() + if vido and vido.media and "video" in mediainfo(vido.media): + if hasattr(vido.media, "document"): + vfile = vido.media.document + name = vido.file.name + else: + vfile = vido.media + name = "" + if not name: + name = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" + xxx = await e.eor(get_string("audiotools_5")) + c_time = time.time() + file = await downloader( + f"resources/downloads/{name}", + vfile, + xxx, + c_time, + f"Downloading {name}...", + ) + + o_size = os.path.getsize(file.name) + d_time = time.time() + diff = time_formatter((d_time - c_time) * 1000) + file_name = (file.name).split("/")[-1] + out = file_name.replace(file_name.split(".")[-1], "compressed.mkv") + await xxx.edit( + f"`Downloaded {file.name} of {humanbytes(o_size)} in {diff}.\nNow Compressing...`" + ) + x, y = await bash( + f'mediainfo --fullscan """{file.name}""" | grep "Frame count"' + ) + if y and y.endswith("NOT_FOUND"): + return await xxx.edit(f"ERROR: `{y}`") + total_frames = x.split(":")[1].split("\n")[0] + progress = f"progress-{c_time}.txt" + with open(progress, "w"): + pass + proce = await asyncio.create_subprocess_shell( + f'ffmpeg -hide_banner -loglevel quiet -progress {progress} -i """{file.name}""" -preset ultrafast -vcodec libx265 -crf {crf} -c:a copy """{out}""" -y', + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + while proce.returncode != 0: + await asyncio.sleep(3) + with open(progress, "r+") as fil: + text = fil.read() + frames = re.findall("frame=(\\d+)", text) + size = re.findall("total_size=(\\d+)", text) + speed = 0 + if len(frames): + elapse = int(frames[-1]) + if len(size): + size = int(size[-1]) + per = elapse * 100 / int(total_frames) + time_diff = time.time() - int(d_time) + speed = round(elapse / time_diff, 2) + if int(speed) != 0: + some_eta = ((int(total_frames) - elapse) / speed) * 1000 + text = f"`Compressing {file_name} at {crf} CRF.\n`" + progress_str = "`[{0}{1}] {2}%\n\n`".format( + "".join("●" for _ in range(math.floor(per / 5))), + "".join("" for _ in range(20 - math.floor(per / 5))), + round(per, 2), + ) + + e_size = f"{humanbytes(size)} of ~{humanbytes((size / per) * 100)}" + eta = f"~{time_formatter(some_eta)}" + try: + await xxx.edit( + text + + progress_str + + "`" + + e_size + + "`" + + "\n\n`" + + eta + + "`" + ) + except MessageNotModifiedError: + pass + os.remove(file.name) + c_size = os.path.getsize(out) + f_time = time.time() + difff = time_formatter((f_time - d_time) * 1000) + await xxx.edit( + f"`Compressed {humanbytes(o_size)} to {humanbytes(c_size)} in {difff}\nTrying to Upload...`" + ) + differ = 100 - ((c_size / o_size) * 100) + caption = f"**Original Size: **`{humanbytes(o_size)}`\n" + caption += f"**Compressed Size: **`{humanbytes(c_size)}`\n" + caption += f"**Compression Ratio: **`{differ:.2f}%`\n" + caption += f"\n**Time Taken To Compress: **`{difff}`" + n_file, _ = await e.client.fast_uploader( + out, show_progress=True, event=e, message="Uploading...", to_delete=True + ) + if to_stream: + data = await metadata(out) + width = data["width"] + height = data["height"] + duration = data["duration"] + attributes = [ + DocumentAttributeVideo( + duration=duration, w=width, h=height, supports_streaming=True + ) + ] + await e.client.send_file( + e.chat_id, + n_file, + thumb=ULTConfig.thumb, + caption=caption, + attributes=attributes, + force_document=False, + reply_to=e.reply_to_msg_id, + ) + else: + await e.client.send_file( + e.chat_id, + n_file, + thumb=ULTConfig.thumb, + caption=caption, + force_document=True, + reply_to=e.reply_to_msg_id, + ) + await xxx.delete() + os.remove(out) + os.remove(progress) + else: + await e.eor(get_string("audiotools_8"), time=5) diff --git a/plugins/converter.py b/plugins/converter.py new file mode 100644 index 0000000000000000000000000000000000000000..db2a8f8b5f215027db097b5e39f3e352fc076118 --- /dev/null +++ b/plugins/converter.py @@ -0,0 +1,196 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_converter") + +import os +import time + +from . import LOGS + +try: + import cv2 +except ImportError: + cv2 = None + +try: + from PIL import Image +except ImportError: + LOGS.info(f"{__file__}: PIL not Installed.") + Image = None + +from . import upload_file as uf + +from . import ( + ULTConfig, + bash, + con, + downloader, + get_paste, + get_string, + udB, + ultroid_cmd, + uploader, +) + +opn = [] + + +@ultroid_cmd( + pattern="thumbnail$", +) +async def _(e): + r = await e.get_reply_message() + if r.photo: + dl = await r.download_media() + elif r.document and r.document.thumbs: + dl = await r.download_media(thumb=-1) + else: + return await e.eor("`Reply to Photo or media with thumb...`") + nn = uf(dl) + os.remove(dl) + udB.set_key("CUSTOM_THUMBNAIL", str(nn)) + await bash(f"wget {nn} -O resources/extras/ultroid.jpg") + await e.eor(get_string("cvt_6").format(nn), link_preview=False) + + +@ultroid_cmd( + pattern="rename( (.*)|$)", +) +async def imak(event): + reply = await event.get_reply_message() + t = time.time() + if not reply: + return await event.eor(get_string("cvt_1")) + inp = event.pattern_match.group(1).strip() + if not inp: + return await event.eor(get_string("cvt_2")) + xx = await event.eor(get_string("com_1")) + if reply.media: + if hasattr(reply.media, "document"): + file = reply.media.document + image = await downloader( + reply.file.name or str(time.time()), + reply.media.document, + xx, + t, + get_string("com_5"), + ) + + file = image.name + else: + file = await event.client.download_media(reply.media) + if os.path.exists(inp): + os.remove(inp) + await bash(f'mv """{file}""" """{inp}"""') + if not os.path.exists(inp) or os.path.exists(inp) and not os.path.getsize(inp): + os.rename(file, inp) + k = time.time() + n_file, _ = await event.client.fast_uploader( + inp, show_progress=True, event=event, message="Uploading...", to_delete=True + ) + await event.reply( + f"`{n_file.name}`", + file=n_file, + force_document=True, + thumb=ULTConfig.thumb, + ) + os.remove(inp) + await xx.delete() + + +conv_keys = { + "img": "png", + "sticker": "webp", + "webp": "webp", + "image": "png", + "webm": "webm", + "gif": "gif", + "json": "json", + "tgs": "tgs", +} + + +@ultroid_cmd( + pattern="convert( (.*)|$)", +) +async def uconverter(event): + xx = await event.eor(get_string("com_1")) + a = await event.get_reply_message() + if a is None: + return await event.eor("`Reply to Photo or media with thumb...`") + input_ = event.pattern_match.group(1).strip() + b = await a.download_media("resources/downloads/") + if not b and (a.document and a.document.thumbs): + b = await a.download_media(thumb=-1) + if not b: + return await xx.edit(get_string("cvt_3")) + try: + convert = conv_keys[input_] + except KeyError: + return await xx.edit(get_string("sts_3").format("gif/img/sticker/webm")) + file = await con.convert(b, outname="ultroid", convert_to=convert) + print(file) + + if file: + await event.client.send_file( + event.chat_id, file, reply_to=event.reply_to_msg_id or event.id + ) + os.remove(file) + else: + await xx.edit("`Failed to convert`") + return + await xx.delete() + +@ultroid_cmd( + pattern="doc( (.*)|$)", +) +async def _(event): + input_str = event.pattern_match.group(1).strip() + if not (input_str and event.is_reply): + return await event.eor(get_string("cvt_1"), time=5) + xx = await event.eor(get_string("com_1")) + a = await event.get_reply_message() + if not a.message: + return await xx.edit(get_string("ex_1")) + with open(input_str, "w") as b: + b.write(str(a.message)) + await xx.edit(f"**Packing into** `{input_str}`") + await event.reply(file=input_str, thumb=ULTConfig.thumb) + await xx.delete() + os.remove(input_str) + + +@ultroid_cmd( + pattern="open( (.*)|$)", +) +async def _(event): + a = await event.get_reply_message() + b = event.pattern_match.group(1).strip() + if not ((a and a.media) or (b and os.path.exists(b))): + return await event.eor(get_string("cvt_7"), time=5) + xx = await event.eor(get_string("com_1")) + rem = None + if not b: + b = await a.download_media() + rem = True + try: + with open(b) as c: + d = c.read() + except UnicodeDecodeError: + return await xx.eor(get_string("cvt_8"), time=5) + try: + await xx.edit(f"```{d}```") + except BaseException: + what, data = await get_paste(d) + await xx.edit( + f"**MESSAGE EXCEEDS TELEGRAM LIMITS**\n\nSo Pasted It On [SPACEBIN]({data['link']})" + ) + if rem: + os.remove(b) diff --git a/plugins/core.py b/plugins/core.py new file mode 100644 index 0000000000000000000000000000000000000000..d90c22199410c3923660ad80a943ba7d35a52a6b --- /dev/null +++ b/plugins/core.py @@ -0,0 +1,124 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from . import get_help + +__doc__ = get_help("help_core") + + +import os + +from pyUltroid.startup.loader import load_addons + +from . import LOGS, async_searcher, eod, get_string, safeinstall, ultroid_cmd, un_plug + + +@ultroid_cmd(pattern="install", fullsudo=True) +async def install(event): + await safeinstall(event) + + +@ultroid_cmd( + pattern=r"unload( (.*)|$)", +) +async def unload(event): + shortname = event.pattern_match.group(1).strip() + if not shortname: + await event.eor(get_string("core_9")) + return + lsd = os.listdir("addons") + zym = f"{shortname}.py" + if zym in lsd: + try: + un_plug(shortname) + await event.eor(f"**Uɴʟᴏᴀᴅᴇᴅ** `{shortname}` **Sᴜᴄᴄᴇssғᴜʟʟʏ.**", time=3) + except Exception as ex: + LOGS.exception(ex) + return await event.eor(str(ex)) + elif zym in os.listdir("plugins"): + return await event.eor(get_string("core_11"), time=3) + else: + await event.eor(f"**No Plugin Named** `{shortname}`", time=3) + + +@ultroid_cmd( + pattern=r"uninstall( (.*)|$)", +) +async def uninstall(event): + shortname = event.pattern_match.group(1).strip() + if not shortname: + await event.eor(get_string("core_13")) + return + lsd = os.listdir("addons") + zym = f"{shortname}.py" + if zym in lsd: + try: + un_plug(shortname) + await event.eor(f"**Uninstalled** `{shortname}` **successfully.**", time=3) + os.remove(f"addons/{shortname}.py") + except Exception as ex: + return await event.eor(str(ex)) + elif zym in os.listdir("plugins"): + return await event.eor(get_string("core_15"), time=3) + else: + return await event.eor(f"**No Plugin Named** `{shortname}`", time=3) + + +@ultroid_cmd( + pattern=r"load( (.*)|$)", + fullsudo=True, +) +async def load(event): + shortname = event.pattern_match.group(1).strip() + if not shortname: + await event.eor(get_string("core_16")) + return + try: + try: + un_plug(shortname) + except BaseException: + pass + load_addons(f"addons/{shortname}.py") + await event.eor(get_string("core_17").format(shortname), time=3) + except Exception as e: + LOGS.exception(e) + await eod( + event, + get_string("core_18").format(shortname, e), + time=3, + ) + + +@ultroid_cmd(pattern="getaddons( (.*)|$)", fullsudo=True) +async def get_the_addons_lol(event): + thelink = event.pattern_match.group(1).strip() + xx = await event.eor(get_string("com_1")) + fool = get_string("gas_1") + if thelink is None: + return await xx.eor(fool, time=10) + split_thelink = thelink.split("/") + if not ("raw" in thelink and thelink.endswith(".py")): + return await xx.eor(fool, time=10) + name_of_it = split_thelink[-1] + plug = await async_searcher(thelink) + fil = f"addons/{name_of_it}" + await xx.edit("Packing the codes...") + with open(fil, "w", encoding="utf-8") as uult: + uult.write(plug) + await xx.edit("Packed. Now loading the plugin..") + shortname = name_of_it.split(".")[0] + try: + load_addons(fil) + await xx.eor(get_string("core_17").format(shortname), time=15) + except Exception as e: + LOGS.exception(e) + await eod( + xx, + get_string("core_18").format(shortname, e), + time=3, + ) diff --git a/plugins/database.py b/plugins/database.py new file mode 100644 index 0000000000000000000000000000000000000000..83dd26b9b7bb2c6436533e4e2bbea1a475117406 --- /dev/null +++ b/plugins/database.py @@ -0,0 +1,77 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from . import get_help + +__doc__ = get_help("help_database") + + +import re + +from . import Redis, eor, get_string, udB, ultroid_cmd + + +@ultroid_cmd(pattern="setdb( (.*)|$)", fullsudo=True) +async def _(ult): + match = ult.pattern_match.group(1).strip() + if not match: + return await ult.eor("Provide key and value to set!") + try: + delim = " " if re.search("[|]", match) is None else " | " + data = match.split(delim, maxsplit=1) + if data[0] in ["--extend", "-e"]: + data = data[1].split(maxsplit=1) + data[1] = f"{str(udB.get_key(data[0]))} {data[1]}" + udB.set_key(data[0], data[1]) + await ult.eor( + f"**DB Key Value Pair Updated\nKey :** `{data[0]}`\n**Value :** `{data[1]}`" + ) + + except BaseException: + await ult.eor(get_string("com_7")) + + +@ultroid_cmd(pattern="deldb( (.*)|$)", fullsudo=True) +async def _(ult): + key = ult.pattern_match.group(1).strip() + if not key: + return await ult.eor("Give me a key name to delete!", time=5) + _ = key.split(maxsplit=1) + try: + if _[0] == "-m": + for key in _[1].split(): + k = udB.del_key(key) + key = _[1] + else: + k = udB.del_key(key) + if k == 0: + return await ult.eor("`No Such Key.`") + await ult.eor(f"`Successfully deleted key {key}`") + except BaseException: + await ult.eor(get_string("com_7")) + + +@ultroid_cmd(pattern="rendb( (.*)|$)", fullsudo=True) +async def _(ult): + match = ult.pattern_match.group(1).strip() + if not match: + return await ult.eor("`Provide Keys name to rename..`") + delim = " " if re.search("[|]", match) is None else " | " + data = match.split(delim) + if Redis(data[0]): + try: + udB.rename(data[0], data[1]) + await eor( + ult, + f"**DB Key Rename Successful\nOld Key :** `{data[0]}`\n**New Key :** `{data[1]}`", + ) + + except BaseException: + await ult.eor(get_string("com_7")) + else: + await ult.eor("Key not found") diff --git a/plugins/delayspam.py b/plugins/delayspam.py new file mode 100644 index 0000000000000000000000000000000000000000..937a916696ea0cd16dcfcd4be174873cc66e2ed8 --- /dev/null +++ b/plugins/delayspam.py @@ -0,0 +1,95 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}delayspam ` + Spam a message `count` times with `delay` seconds between each. + Reply to a message to spam that message instead. + +• `{i}stopspam` + Stop an ongoing delayspam. + +Examples: + `{i}delayspam 5 2 Hello!` — sends "Hello!" 5 times with 2s delay + `{i}delayspam 10 1` (reply to a message) — forwards replied msg 10 times with 1s delay +""" + +import asyncio + +from . import HNDLR, eod, get_string, udB, ultroid_bot, ultroid_cmd + +_spam_tasks = {} + + +@ultroid_cmd(pattern=r"delayspam( (.*)|$)") +async def delayspam_cmd(ult): + args = ult.pattern_match.group(1).strip().split(None, 2) + reply = await ult.get_reply_message() + + if len(args) < 2: + return await ult.eor( + f"`Usage: {HNDLR}delayspam [message]\n" + f"Or reply to a message: {HNDLR}delayspam `", + time=10, + ) + + try: + count = int(args[0]) + delay = float(args[1]) + except ValueError: + return await ult.eor("`count and delay must be numbers.`", time=5) + + if count > 200: + return await ult.eor("`Max 200 repetitions allowed.`", time=5) + if delay < 0.5: + return await ult.eor("`Minimum delay is 0.5 seconds.`", time=5) + + text = args[2] if len(args) > 2 else None + chat_id = ult.chat_id + + if not text and not reply: + return await ult.eor( + f"`Provide a message or reply to one.\nUsage: {HNDLR}delayspam `", + time=8, + ) + + await ult.delete() + + task_key = f"{ult.sender_id}_{chat_id}" + if task_key in _spam_tasks: + _spam_tasks[task_key].cancel() + + async def _do_spam(): + for i in range(count): + if task_key not in _spam_tasks: + break + try: + if reply: + await ultroid_bot.send_message( + chat_id, reply.text or "", file=reply.media + ) + else: + await ultroid_bot.send_message(chat_id, text) + except Exception: + break + await asyncio.sleep(delay) + _spam_tasks.pop(task_key, None) + + task = asyncio.get_event_loop().create_task(_do_spam()) + _spam_tasks[task_key] = task + + +@ultroid_cmd(pattern="stopspam$") +async def stopspam_cmd(ult): + task_key = f"{ult.sender_id}_{ult.chat_id}" + if task_key in _spam_tasks: + _spam_tasks.pop(task_key).cancel() + await ult.eor("`Spam stopped.`", time=3) + else: + await ult.eor("`No active spam in this chat.`", time=3) diff --git a/plugins/devtools.py b/plugins/devtools.py new file mode 100644 index 0000000000000000000000000000000000000000..f7082d8171dafa3222af364387a86652a07efbd4 --- /dev/null +++ b/plugins/devtools.py @@ -0,0 +1,400 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_devtools") + +import inspect +import sys +import traceback +from io import BytesIO, StringIO +from os import remove +from pprint import pprint + +from telethon.utils import get_display_name + +from pyUltroid import _ignore_eval + +from . import * + +# Used for Formatting Eval Code, if installed +try: + import black +except ImportError: + black = None +from random import choice + +try: + from yaml import safe_load +except ImportError: + from pyUltroid.fns.tools import safe_load + +from . import upload_file as uf +from telethon.tl import functions + +fn = functions + + +@ultroid_cmd( + pattern="sysinfo$", +) +async def _(e): + xx = await e.eor(get_string("com_1")) + x, y = await bash("neofetch|sed 's/\x1B\\[[0-9;\\?]*[a-zA-Z]//g' >> neo.txt") + if y and y.endswith("NOT_FOUND"): + return await xx.edit(f"Error: `{y}`") + with open("neo.txt", "r", encoding="utf-8") as neo: + p = (neo.read()).replace("\n\n", "") + haa = await Carbon(code=p, file_name="neofetch", backgroundColor=choice(ATRA_COL)) + if isinstance(haa, dict): + await xx.edit(f"`{haa}`") + else: + await e.reply(file=haa) + await xx.delete() + remove("neo.txt") + + +@ultroid_cmd(pattern="bash", fullsudo=True, only_devs=True) +async def _(event): + carb, rayso, yamlf = None, None, False + try: + cmd = event.text.split(" ", maxsplit=1)[1] + if cmd.split()[0] in ["-c", "--carbon"]: + cmd = cmd.split(maxsplit=1)[1] + carb = True + if cmd.split()[0] in ["-r", "--rayso"]: + cmd = cmd.split(maxsplit=1)[1] + rayso = True + except IndexError: + return await event.eor(get_string("devs_1"), time=10) + xx = await event.eor(get_string("com_1")) + reply_to_id = event.reply_to_msg_id or event.id + stdout, stderr = await bash(cmd, run_code=1) + OUT = f"**☞ BASH\n\n• COMMAND:**\n`{cmd}` \n\n" + err, out = "", "" + if stderr: + err = f"**• ERROR:** \n`{stderr}`\n\n" + if stdout: + if (carb or udB.get_key("CARBON_ON_BASH")) and ( + event.is_private + or event.chat.admin_rights + or event.chat.creator + or event.chat.default_banned_rights.embed_links + ): + li = await Carbon( + code=stdout, + file_name="bash", + download=True, + backgroundColor=choice(ATRA_COL), + ) + if isinstance(li, dict): + await xx.edit( + f"Unknown Response from Carbon: `{li}`\n\nstdout`:{stdout}`\nstderr: `{stderr}`" + ) + return + url = uf(li) + OUT = f"[\xad]({url}){OUT}" + out = "**• OUTPUT:**" + remove(li) + elif (rayso or udB.get_key("RAYSO_ON_BASH")) and ( + event.is_private + or event.chat.admin_rights + or event.chat.creator + or event.chat.default_banned_rights.embed_links + ): + li = await Carbon( + code=stdout, + file_name="bash", + download=True, + backgroundColor=choice(ATRA_COL), + rayso=True, + ) + if isinstance(li, dict): + await xx.edit( + f"Unknown Response from Carbon: `{li}`\n\nstdout`:{stdout}`\nstderr: `{stderr}`" + ) + return + url = uf(li) + OUT = f"[\xad]({url}){OUT}" + out = "**• OUTPUT:**" + remove(li) + else: + if "pip" in cmd and all(":" in line for line in stdout.split("\n")): + try: + load = safe_load(stdout) + stdout = "" + for data in list(load.keys()): + res = load[data] or "" + if res and "http" not in str(res): + res = f"`{res}`" + stdout += f"**{data}** : {res}\n" + yamlf = True + except Exception as er: + stdout = f"`{stdout}`" + LOGS.exception(er) + else: + stdout = f"`{stdout}`" + out = f"**• OUTPUT:**\n{stdout}" + if not stderr and not stdout: + out = "**• OUTPUT:**\n`Success`" + OUT += err + out + if len(OUT) > 4096: + ultd = err + out + with BytesIO(str.encode(ultd)) as out_file: + out_file.name = "bash.txt" + await event.client.send_file( + event.chat_id, + out_file, + force_document=True, + thumb=ULTConfig.thumb, + allow_cache=False, + caption=f"`{cmd}`" if len(cmd) < 998 else None, + reply_to=reply_to_id, + ) + + await xx.delete() + else: + await xx.edit(OUT, link_preview=not yamlf) + + +pp = pprint # ignore: pylint +bot = ultroid = ultroid_bot + + +class u: + _ = "" + + +def _parse_eval(value=None): + if not value: + return value + if hasattr(value, "stringify"): + try: + return value.stringify() + except TypeError: + pass + elif isinstance(value, dict): + try: + return json_parser(value, indent=1) + except BaseException: + pass + elif isinstance(value, list): + newlist = "[" + for index, child in enumerate(value): + newlist += "\n " + str(_parse_eval(child)) + if index < len(value) - 1: + newlist += "," + newlist += "\n]" + return newlist + return str(value) + + +@ultroid_cmd(pattern="eval", fullsudo=True, only_devs=True) +async def _(event): + try: + cmd = event.text.split(maxsplit=1)[1] + except IndexError: + return await event.eor(get_string("devs_2"), time=5) + xx = None + mode = "" + spli = cmd.split() + + async def get_(): + try: + cm = cmd.split(maxsplit=1)[1] + except IndexError: + await event.eor("->> Wrong Format <<-") + cm = None + return cm + + if spli[0] in ["-s", "--silent"]: + await event.delete() + mode = "silent" + elif spli[0] in ["-n", "-noedit"]: + mode = "no-edit" + xx = await event.reply(get_string("com_1")) + elif spli[0] in ["-gs", "--source"]: + mode = "gsource" + elif spli[0] in ["-ga", "--args"]: + mode = "g-args" + if mode: + cmd = await get_() + if not cmd: + return + if not mode == "silent" and not xx: + xx = await event.eor(get_string("com_1")) + if black: + try: + cmd = black.format_str(cmd, mode=black.Mode()) + except BaseException: + # Consider it as Code Error, and move on to be shown ahead. + pass + reply_to_id = event.reply_to_msg_id or event + if any(item in cmd for item in KEEP_SAFE().All) and ( + not (event.out or event.sender_id == ultroid_bot.uid) + ): + warning = await event.forward_to(udB.get_key("LOG_CHANNEL")) + await warning.reply( + f"Malicious Activities suspected by {inline_mention(await event.get_sender())}" + ) + _ignore_eval.append(event.sender_id) + return await xx.edit( + "`Malicious Activities suspected⚠️!\nReported to owner. Aborted this request!`" + ) + old_stderr = sys.stderr + old_stdout = sys.stdout + redirected_output = sys.stdout = StringIO() + redirected_error = sys.stderr = StringIO() + stdout, stderr, exc, timeg = None, None, None, None + tima = time.time() + try: + value = await aexec(cmd, event) + except Exception: + value = None + exc = traceback.format_exc() + tima = time.time() - tima + stdout = redirected_output.getvalue() + stderr = redirected_error.getvalue() + sys.stdout = old_stdout + sys.stderr = old_stderr + if value: + try: + if mode == "gsource": + exc = inspect.getsource(value) + elif mode == "g-args": + args = inspect.signature(value).parameters.values() + name = "" + if hasattr(value, "__name__"): + name = value.__name__ + exc = f"**{name}**\n\n" + "\n ".join([str(arg) for arg in args]) + except Exception: + exc = traceback.format_exc() + evaluation = exc or stderr or stdout or _parse_eval(value) or get_string("instu_4") + if mode == "silent": + if exc: + msg = f"• EVAL ERROR\n\n• CHAT: {get_display_name(event.chat)} [{event.chat_id}]" + msg += f"\n\n∆ CODE:\n{cmd}\n\n∆ ERROR:\n{exc}" + log_chat = udB.get_key("LOG_CHANNEL") + if len(msg) > 4000: + with BytesIO(msg.encode()) as out_file: + out_file.name = "Eval-Error.txt" + return await event.client.send_message( + log_chat, f"`{cmd}`", file=out_file + ) + await event.client.send_message(log_chat, msg, parse_mode="html") + return + tmt = tima * 1000 + timef = time_formatter(tmt) + timeform = timef if not timef == "0s" else f"{tmt:.3f}ms" + final_output = "__►__ **EVAL** (__in {}__)\n```{}``` \n\n __►__ **OUTPUT**: \n```{}``` \n".format( + timeform, + cmd, + evaluation, + ) + if len(final_output) > 4096: + final_output = evaluation + with BytesIO(str.encode(final_output)) as out_file: + out_file.name = "eval.txt" + await event.client.send_file( + event.chat_id, + out_file, + force_document=True, + thumb=ULTConfig.thumb, + allow_cache=False, + caption=f"```{cmd}```" if len(cmd) < 998 else None, + reply_to=reply_to_id, + ) + return await xx.delete() + await xx.edit(final_output) + + +def _stringify(text=None, *args, **kwargs): + if text: + u._ = text + text = _parse_eval(text) + return print(text, *args, **kwargs) + + +async def aexec(code, event): + # Create a dedicated namespace for execution + exec_globals = { + 'print': _stringify, + 'p': _stringify, + 'message': event, + 'event': event, + 'client': event.client, + 'reply': await event.get_reply_message(), + 'chat': event.chat_id, + 'u': u, + '__builtins__': __builtins__, + '__name__': __name__ + } + + # Format the async function definition + wrapped_code = ( + 'async def __aexec(e, client):\n' + + '\n'.join(f' {line}' for line in code.split('\n')) + ) + + try: + # Execute the wrapped code in our custom namespace + exec(wrapped_code, exec_globals) + # Get the defined async function + func = exec_globals['__aexec'] + # Execute it with proper parameters + return await func(event, event.client) + except Exception as e: + raise Exception(f"Failed to execute code: {str(e)}") + + +DUMMY_CPP = """#include +using namespace std; + +int main(){ +!code +} +""" + + +@ultroid_cmd(pattern="cpp", only_devs=True) +async def doie(e): + match = e.text.split(" ", maxsplit=1) + try: + match = match[1] + except IndexError: + return await e.eor(get_string("devs_3")) + msg = await e.eor(get_string("com_1")) + if "main(" not in match: + new_m = "".join(" " * 4 + i + "\n" for i in match.split("\n")) + match = DUMMY_CPP.replace("!code", new_m) + open("cpp-ultroid.cpp", "w").write(match) + m = await bash("g++ -o CppUltroid cpp-ultroid.cpp") + o_cpp = f"• **Eval-Cpp**\n`{match}`" + if m[1]: + o_cpp += f"\n\n**• Error :**\n`{m[1]}`" + if len(o_cpp) > 3000: + os.remove("cpp-ultroid.cpp") + if os.path.exists("CppUltroid"): + os.remove("CppUltroid") + with BytesIO(str.encode(o_cpp)) as out_file: + out_file.name = "error.txt" + return await msg.reply(f"`{match}`", file=out_file) + return await eor(msg, o_cpp) + m = await bash("./CppUltroid") + if m[0] != "": + o_cpp += f"\n\n**• Output :**\n`{m[0]}`" + if m[1]: + o_cpp += f"\n\n**• Error :**\n`{m[1]}`" + if len(o_cpp) > 3000: + with BytesIO(str.encode(o_cpp)) as out_file: + out_file.name = "eval.txt" + await msg.reply(f"`{match}`", file=out_file) + else: + await eor(msg, o_cpp) + os.remove("CppUltroid") + os.remove("cpp-ultroid.cpp") diff --git a/plugins/downloadupload.py b/plugins/downloadupload.py new file mode 100644 index 0000000000000000000000000000000000000000..c27afcad80988b83189e698ed850d67d71e5eaf7 --- /dev/null +++ b/plugins/downloadupload.py @@ -0,0 +1,222 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_downloadupload") + +import asyncio +import glob +import os +import time +from datetime import datetime as dt + +from aiohttp.client_exceptions import InvalidURL +from telethon.errors.rpcerrorlist import MessageNotModifiedError + +from pyUltroid.fns.helper import time_formatter +from pyUltroid.fns.tools import get_chat_and_msgid, set_attributes + +from . import ( + LOGS, + ULTConfig, + downloader, + eor, + fast_download, + get_all_files, + get_string, + progress, + time_formatter, + ultroid_cmd, +) + + +@ultroid_cmd( + pattern="download( (.*)|$)", +) +async def down(event): + matched = event.pattern_match.group(1).strip() + msg = await event.eor(get_string("udl_4")) + if not matched: + return await eor(msg, get_string("udl_5"), time=5) + try: + splited = matched.split(" | ") + link = splited[0] + filename = splited[1] + except IndexError: + filename = None + s_time = time.time() + try: + filename, d = await fast_download( + link, + filename, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + msg, + s_time, + f"Downloading from {link}", + ) + ), + ) + except InvalidURL: + return await msg.eor("`Invalid URL provided :(`", time=5) + await msg.eor(f"`{filename}` `downloaded in {time_formatter(d*1000)}.`") + + +@ultroid_cmd( + pattern="dl( (.*)|$)", +) +async def download(event): + match = event.pattern_match.group(1).strip() + if match and "t.me/" in match: + chat, msg = get_chat_and_msgid(match) + if not (chat and msg): + return await event.eor(get_string("gms_1")) + match = "" + ok = await event.client.get_messages(chat, ids=msg) + elif event.reply_to_msg_id: + ok = await event.get_reply_message() + else: + return await event.eor(get_string("cvt_3"), time=8) + xx = await event.eor(get_string("com_1")) + if not (ok and ok.media): + return await xx.eor(get_string("udl_1"), time=5) + s = dt.now() + k = time.time() + if hasattr(ok.media, "document"): + file = ok.media.document + mime_type = file.mime_type + filename = match or ok.file.name + if not filename: + if "audio" in mime_type: + filename = "audio_" + dt.now().isoformat("_", "seconds") + ".ogg" + elif "video" in mime_type: + filename = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" + try: + result = await downloader( + f"resources/downloads/{filename}", + file, + xx, + k, + f"Downloading {filename}...", + ) + + except MessageNotModifiedError as err: + return await xx.edit(str(err)) + file_name = result.name + else: + d = "resources/downloads/" + file_name = await event.client.download_media( + ok, + d, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + xx, + k, + get_string("com_5"), + ), + ), + ) + e = dt.now() + t = time_formatter(((e - s).seconds) * 1000) + await xx.eor(get_string("udl_2").format(file_name, t)) + + +@ultroid_cmd( + pattern="ul( (.*)|$)", +) +async def _(event): + msg = await event.eor(get_string("com_1")) + match = event.pattern_match.group(1) + if match: + match = match.strip() + if not event.out and match == ".env": + return await event.reply("`You can't do this...`") + stream, force_doc, delete, thumb = ( + False, + True, + False, + ULTConfig.thumb, + ) + if "--stream" in match: + stream = True + force_doc = False + if "--delete" in match: + delete = True + if "--no-thumb" in match: + thumb = None + arguments = ["--stream", "--delete", "--no-thumb"] + if any(item in match for item in arguments): + match = ( + match.replace("--stream", "") + .replace("--delete", "") + .replace("--no-thumb", "") + .strip() + ) + if match.endswith("/"): + match += "*" + results = glob.glob(match) + if not results and os.path.exists(match): + results = [match] + if not results: + try: + await event.reply(file=match) + return await event.try_delete() + except Exception as er: + LOGS.exception(er) + return await msg.eor(get_string("ls1")) + for result in results: + if os.path.isdir(result): + c, s = 0, 0 + for files in get_all_files(result): + attributes = None + if stream: + try: + attributes = await set_attributes(files) + except KeyError as er: + LOGS.exception(er) + try: + file, _ = await event.client.fast_uploader( + files, show_progress=True, event=msg, to_delete=delete + ) + await event.client.send_file( + event.chat_id, + file, + supports_streaming=stream, + force_document=force_doc, + thumb=thumb, + attributes=attributes, + caption=f"`Uploaded` `{files}` `in {time_formatter(_*1000)}`", + reply_to=event.reply_to_msg_id or event, + ) + s += 1 + except (ValueError, IsADirectoryError): + c += 1 + break + attributes = None + if stream: + try: + attributes = await set_attributes(result) + except KeyError as er: + LOGS.exception(er) + file, _ = await event.client.fast_uploader( + result, show_progress=True, event=msg, to_delete=delete + ) + await event.client.send_file( + event.chat_id, + file, + supports_streaming=stream, + force_document=force_doc, + thumb=thumb, + attributes=attributes, + caption=f"`Uploaded` `{result}` `in {time_formatter(_*1000)}`", + ) + await msg.try_delete() diff --git a/plugins/echo.py b/plugins/echo.py new file mode 100644 index 0000000000000000000000000000000000000000..b7ffc26ad4c486a2974eb66b0ba2e8a51b4a0c00 --- /dev/null +++ b/plugins/echo.py @@ -0,0 +1,76 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_echo") + + +from telethon.utils import get_display_name + +from pyUltroid.dB.echo_db import add_echo, check_echo, list_echo, rem_echo + +from . import inline_mention, ultroid_cmd + + +@ultroid_cmd(pattern="addecho( (.*)|$)") +async def echo(e): + r = await e.get_reply_message() + if r: + user = r.sender_id + else: + try: + user = e.text.split()[1] + if user.startswith("@"): + ok = await e.client.get_entity(user) + user = ok.id + else: + user = int(user) + except BaseException: + return await e.eor("Reply To A user.", time=5) + if check_echo(e.chat_id, user): + return await e.eor("Echo already activated for this user.", time=5) + add_echo(e.chat_id, user) + ok = await e.client.get_entity(user) + user = inline_mention(ok) + await e.eor(f"Activated Echo For {user}.") + + +@ultroid_cmd(pattern="remecho( (.*)|$)") +async def rm(e): + r = await e.get_reply_message() + if r: + user = r.sender_id + else: + try: + user = e.text.split()[1] + if user.startswith("@"): + ok = await e.client.get_entity(user) + user = ok.id + else: + user = int(user) + except BaseException: + return await e.eor("Reply To A User.", time=5) + if check_echo(e.chat_id, user): + rem_echo(e.chat_id, user) + ok = await e.client.get_entity(user) + user = f"[{get_display_name(ok)}](tg://user?id={ok.id})" + return await e.eor(f"Deactivated Echo For {user}.") + await e.eor("Echo not activated for this user") + + +@ultroid_cmd(pattern="listecho$") +async def lstecho(e): + if k := list_echo(e.chat_id): + user = "**Activated Echo For Users:**\n\n" + for x in k: + ok = await e.client.get_entity(int(x)) + kk = f"[{get_display_name(ok)}](tg://user?id={ok.id})" + user += f"•{kk}" + "\n" + await e.eor(user) + else: + await e.eor("`List is Empty, For echo`", time=5) diff --git a/plugins/extra.py b/plugins/extra.py new file mode 100644 index 0000000000000000000000000000000000000000..ffe8555373b9288d5e3d22899eb132f72e09b6f4 --- /dev/null +++ b/plugins/extra.py @@ -0,0 +1,85 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("extra") + +import asyncio + +from . import get_string, ultroid_cmd + + +@ultroid_cmd( + pattern="del$", + manager=True, +) +async def delete_it(delme): + msg_src = await delme.get_reply_message() + if not msg_src: + return + await msg_src.try_delete() + await delme.try_delete() + + +@ultroid_cmd( + pattern="copy$", +) +async def copy(e): + reply = await e.get_reply_message() + if reply: + await reply.reply(reply) + return await e.try_delete() + await e.eor(get_string("ex_1"), time=5) + + +@ultroid_cmd( + pattern="edit", +) +async def editer(edit): + message = edit.text + chat = await edit.get_input_chat() + string = str(message[6:]) + reply = await edit.get_reply_message() + if reply and reply.text: + try: + await reply.edit(string) + await edit.delete() + except BaseException: + pass + else: + i = 1 + async for message in edit.client.iter_messages(chat, from_user="me", limit=2): + if i == 2: + await message.edit(string) + await edit.delete() + break + i += 1 + + +@ultroid_cmd( + pattern="reply$", +) +async def _(e): + if e.reply_to_msg_id: + chat = e.chat_id + try: + msg = (await e.client.get_messages(e.chat_id, limit=1, max_id=e.id))[0] + except IndexError: + return await e.eor( + "`You have previously sent no message to reply again...`", time=5 + ) + except BaseException as er: + return await e.eor(f"**ERROR:** `{er}`") + await asyncio.wait( + [ + e.client.delete_messages(chat, [e.id, msg.id]), + e.client.send_message(chat, msg, reply_to=e.reply_to_msg_id), + ] + ) + else: + await e.try_delete() diff --git a/plugins/fakeaction.py b/plugins/fakeaction.py new file mode 100644 index 0000000000000000000000000000000000000000..63544644520068c0ac304b1e5ac28e09b9baca9a --- /dev/null +++ b/plugins/fakeaction.py @@ -0,0 +1,36 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_fakeaction") + +import math +import time + +from pyUltroid.fns.admins import ban_time + +from . import asyncio, get_string, ultroid_cmd + + +@ultroid_cmd( + pattern="f(typing|audio|contact|document|game|location|sticker|photo|round|video)( (.*)|$)" +) +async def _(e): + act = e.pattern_match.group(1).strip() + t = e.pattern_match.group(2) + if act in ["audio", "round", "video"]: + act = f"record-{act}" + if t.isdigit(): + t = int(t) + elif t.endswith(("s", "h", "d", "m")): + t = math.ceil((ban_time(t)) - time.time()) + else: + t = 60 + await e.eor(get_string("fka_1").format(str(t)), time=5) + async with e.client.action(e.chat_id, act): + await asyncio.sleep(t) diff --git a/plugins/fileshare.py b/plugins/fileshare.py new file mode 100644 index 0000000000000000000000000000000000000000..a8b5940a959751c2e96444db6281a416a650146f --- /dev/null +++ b/plugins/fileshare.py @@ -0,0 +1,95 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_fileshare") + +import os + +from pyUltroid.dB.filestore_db import del_stored, get_stored_msg, list_all_stored_msgs +from pyUltroid.fns.tools import get_file_link + +from . import HNDLR, asst, get_string, in_pattern, udB, ultroid_bot, ultroid_cmd + + +@ultroid_cmd(pattern="store$") +async def filestoreplg(event): + msg = await event.get_reply_message() + if not msg: + return await event.eor(get_string("fsh_3"), time=10) + # allow storing both messages and media. + filehash = await get_file_link(msg) + link_to_file = f"https://t.me/{asst.me.username}?start={filehash}" + await event.eor( + get_string("fsh_2").format(link_to_file), + link_preview=False, + ) + + +@ultroid_cmd("delstored ?(.*)") +async def _(event): + match = event.pattern_match.group(1) + if not match: + return await event.eor("`Give stored film's link to delete.`", time=5) + match = match.split("?start=") + botusername = match[0].split("/")[-1] + if botusername != asst.me.username: + return await event.eor( + "`Message/Media of provided link was not stored by this bot.`", time=5 + ) + msg_id = get_stored_msg(match[1]) + if not msg_id: + return await event.eor( + "`Message/Media of provided link was already deleted.`", time=5 + ) + del_stored(match[1]) + await ultroid_bot.delete_messages(udB.get_key("LOG_CHANNEL"), int(msg_id)) + await event.eor("__Deleted__") + + +@ultroid_cmd("liststored$") +async def liststored(event): + files = list_all_stored_msgs() + if not files: + return await event.eor(get_string("fsh_4"), time=5) + msg = "**Stored files:**\n" + for c, i in enumerate(files, start=1): + msg += f"`{c}`. https://t.me/{asst.me.username}?start={i}\n" + if len(msg) > 4095: + with open("liststored.txt", "w") as f: + f.write(msg.replace("**", "").replace("`", "")) + await event.reply(get_string("fsh_1"), file="liststored.txt") + os.remove("liststored.txt") + return + await event.eor(msg, link_preview=False) + + +@in_pattern("filestore", owner=True) +async def file_short(event): + all_ = list_all_stored_msgs() + res = [] + if all_: + LOG_CHA = udB.get_key("LOG_CHANNEL") + for msg in all_[:50]: + m_id = get_stored_msg(msg) + if not m_id: + continue + message = await asst.get_messages(LOG_CHA, ids=m_id) + if not message: + continue + if message.media: + res.append(await event.builder.document(title=msg, file=message.media)) + elif message.text: + res.append( + await event.builder.article(title=message.text, text=message.text) + ) + if not res: + title = "You have no stored file :(" + text = f"{title}\n\nRead `{HNDLR}help fileshare` to know how to store." + return await event.answer([await event.builder.article(title=title, text=text)]) + await event.answer(res, switch_pm="• File Store •", switch_pm_param="start") diff --git a/plugins/filter.py b/plugins/filter.py new file mode 100644 index 0000000000000000000000000000000000000000..b5aab958b54856edc6cf1f8e914994c905d6880d --- /dev/null +++ b/plugins/filter.py @@ -0,0 +1,100 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_filter") + +import os +import re + +from telethon.tl.types import User +from telethon.utils import pack_bot_file_id + +from pyUltroid.dB.filter_db import add_filter, get_filter, list_filter, rem_filter +from pyUltroid.fns.tools import create_tl_btn, format_btn, get_msg_button + +from . import events, get_string, mediainfo, udB, ultroid_bot, ultroid_cmd, upload_file +from ._inline import something + + +@ultroid_cmd(pattern="addfilter( (.*)|$)") +async def af(e): + wrd = (e.pattern_match.group(1).strip()).lower() + wt = await e.get_reply_message() + chat = e.chat_id + if not (wt and wrd): + return await e.eor(get_string("flr_1")) + btn = format_btn(wt.buttons) if wt.buttons else None + if wt and wt.media: + wut = mediainfo(wt.media) + if wut.startswith(("pic", "gif")): + dl = await wt.download_media() + m = upload_file(dl) + os.remove(dl) + elif wut == "video": + if wt.media.document.size > 8 * 1000 * 1000: + return await e.eor(get_string("com_4"), time=5) + dl = await wt.download_media() + m = upload_file(dl) + os.remove(dl) + else: + m = pack_bot_file_id(wt.media) + if wt.text: + txt = wt.text + if not btn: + txt, btn = get_msg_button(wt.text) + add_filter(chat, wrd, txt, m, btn) + else: + add_filter(chat, wrd, None, m, btn) + else: + txt = wt.text + if not btn: + txt, btn = get_msg_button(wt.text) + add_filter(chat, wrd, txt, None, btn) + await e.eor(get_string("flr_4").format(wrd)) + ultroid_bot.add_handler(filter_func, events.NewMessage()) + + +@ultroid_cmd(pattern="remfilter( (.*)|$)") +async def rf(e): + wrd = (e.pattern_match.group(1).strip()).lower() + chat = e.chat_id + if not wrd: + return await e.eor(get_string("flr_3")) + rem_filter(int(chat), wrd) + await e.eor(get_string("flr_5").format(wrd)) + + +@ultroid_cmd(pattern="listfilter$") +async def lsnote(e): + if x := list_filter(e.chat_id): + sd = "Filters Found In This Chats Are\n\n" + return await e.eor(sd + x) + await e.eor(get_string("flr_6")) + + +async def filter_func(e): + if isinstance(e.sender, User) and e.sender.bot: + return + xx = (e.text).lower() + chat = e.chat_id + if x := get_filter(chat): + for c in x: + pat = r"( |^|[^\w])" + re.escape(c) + r"( |$|[^\w])" + if re.search(pat, xx): + if k := x.get(c): + msg = k["msg"] + media = k["media"] + if k.get("button"): + btn = create_tl_btn(k["button"]) + return await something(e, msg, media, btn) + await e.reply(msg, file=media) + + +if udB.get_key("FILTERS"): + ultroid_bot.add_handler(filter_func, events.NewMessage()) diff --git a/plugins/fontgen.py b/plugins/fontgen.py new file mode 100644 index 0000000000000000000000000000000000000000..adb946c1e1e43cde8041b8eab9df22a4a85ea9a7 --- /dev/null +++ b/plugins/fontgen.py @@ -0,0 +1,60 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import get_help + +__doc__ = get_help("help_fontgen") + +import string + +from . import eod, ultroid_cmd + +_default = string.ascii_letters +Fonts = { + "small caps": "ᴀʙᴄᴅᴇғɢʜɪᴊᴋʟᴍɴᴏᴘϙʀsᴛᴜᴠᴡxʏᴢABCDEFGHIJKLMNOPQRSTUVWXYZ", + "monospace": "𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉", + "double stroke": "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ", + "script royal": "𝒶𝒷𝒸𝒹𝑒𝒻𝑔𝒽𝒾𝒿𝓀𝓁𝓂𝓃𝑜𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵", +} + + +@ultroid_cmd( + pattern="font( (.*)|$)", +) +async def _(e): + input = e.pattern_match.group(1).strip() + reply = await e.get_reply_message() + if not input: + m = "**Available Fonts**\n\n" + for x in Fonts.keys(): + m += f"• `{x}`\n" + return await e.eor(m, time=5) + if not reply: + try: + _ = input.split(":", maxsplit=1) + font = _[0][:-1] + text = _[1] + except IndexError: + return await eod(e, help) + elif not input: + return await eod(e, "`Give font dude :/`") + else: + font = input + text = reply.message + if font not in Fonts.keys(): + return await e.eor(f"`{font} not in font list`.", time=5) + msg = gen_font(text, Fonts[font]) + await e.eor(msg) + + +def gen_font(text, new_font): + new_font = " ".join(new_font).split() + for q in text: + if q in _default: + new = new_font[_default.index(q)] + text = text.replace(q, new) + return text diff --git a/plugins/forcesubscribe.py b/plugins/forcesubscribe.py new file mode 100644 index 0000000000000000000000000000000000000000..8a37c90598936df553a09c0dea7d2053032c0add --- /dev/null +++ b/plugins/forcesubscribe.py @@ -0,0 +1,179 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}fsub ` + Enable ForceSub in Used Chat ! + +• `{i}checkfsub` + Check/Get Active ForceSub Setting of Used Chat. + +• `{i}remfsub` + Remove ForceSub from Used Chat ! + + Note - You Need to be Admin in Both Channel/Chats + in order to Use ForceSubscribe. +""" + +import re + +from telethon.errors.rpcerrorlist import ChatAdminRequiredError, UserNotParticipantError +from telethon.tl.custom import Button +from telethon.tl.functions.channels import GetParticipantRequest +from telethon.tl.functions.messages import ExportChatInviteRequest +from telethon.tl.types import ( + Channel, + ChannelParticipantBanned, + ChannelParticipantLeft, + User, +) + +from pyUltroid.dB.forcesub_db import add_forcesub, get_forcesetting, rem_forcesub + +from . import ( + LOGS, + asst, + callback, + events, + get_string, + in_pattern, + inline_mention, + udB, + ultroid_bot, + ultroid_cmd, +) + +CACHE = {} + + +@ultroid_cmd(pattern="fsub( (.*)|$)", admins_only=True, groups_only=True) +async def addfor(e): + match = e.pattern_match.group(1).strip() + if not match: + return await e.eor(get_string("fsub_1"), time=5) + try: + match = await e.client.parse_id(match) + except BaseException: + return await e.eor(get_string("fsub_2"), time=5) + add_forcesub(e.chat_id, match) + await e.eor("Added ForceSub in This Chat !") + ultroid_bot.add_handler(force_sub, events.NewMessage(incoming=True)) + + +@ultroid_cmd(pattern="remfsub$") +async def remor(e): + res = rem_forcesub(e.chat_id) + if not res: + return await e.eor(get_string("fsub_3"), time=5) + await e.eor("Removed ForceSub...") + + +@ultroid_cmd(pattern="checkfsub$") +async def getfsr(e): + res = get_forcesetting(e.chat_id) + if not res: + return await e.eor("ForceSub is Not Active In This Chat !", time=5) + cha = await e.client.get_entity(int(res)) + await e.eor(f"**ForceSub Status** : `Active`\n- **{cha.title}** `({res})`") + + +@in_pattern("fsub( (.*)|$)", owner=True) +async def fcall(e): + match = e.pattern_match.group(1).strip() + spli = match.split("_") + user = await ultroid_bot.get_entity(int(spli[0])) + cl = await ultroid_bot.get_entity(int(spli[1])) + text = f"Hi {inline_mention(user)}, You Need to Join" + text += f" {cl.title} in order to Chat in this Group." + el = ( + f"https://t.me/{cl.username}" + if cl.username + else (await ultroid_bot(ExportChatInviteRequest(cl))).link + ) + + res = [ + await e.builder.article( + title="forcesub", + text=text, + buttons=[ + [Button.url(text=get_string("fsub_4"), url=el)], + [Button.inline(get_string("fsub_5"), data=f"unm_{match}")], + ], + ) + ] + await e.answer(res) + + +@callback(re.compile("unm_(.*)")) +async def diesoon(e): + match = (e.data_match.group(1)).decode("UTF-8") + spli = match.split("_") + if e.sender_id != int(spli[0]): + return await e.answer(get_string("fsub_7"), alert=True) + try: + values = await ultroid_bot(GetParticipantRequest(int(spli[1]), int(spli[0]))) + if isinstance(values.participant, ChannelParticipantLeft) or ( + isinstance(values.participant, ChannelParticipantBanned) and values.left + ): + raise UserNotParticipantError("") + except UserNotParticipantError: + return await e.answer( + "Please Join That Channel !\nThen Click This Button !", alert=True + ) + await ultroid_bot.edit_permissions( + e.chat_id, int(spli[0]), send_messages=True, until_date=None + ) + await e.edit(get_string("fsub_8")) + + +async def force_sub(ult): + if not udB.get_key("FORCESUB"): + return + user = await ult.get_sender() + joinchat = get_forcesetting(ult.chat_id) + if (not joinchat) or (isinstance(user, User) and user.bot): + return + if CACHE.get(ult.chat_id): + if CACHE[ult.chat_id].get(user.id): + CACHE[ult.chat_id].update({user.id: CACHE[ult.chat_id][user.id] + 1}) + else: + CACHE[ult.chat_id].update({user.id: 1}) + else: + CACHE.update({ult.chat_id: {user.id: 1}}) + count = CACHE[ult.chat_id][user.id] + if count == 11: + CACHE[ult.chat_id][user.id] = 1 + return + if count in range(2, 11): + return + try: + await ultroid_bot.get_permissions(int(joinchat), user.id) + return + except UserNotParticipantError: + pass + if isinstance(user, Channel): + try: + await ultroid_bot.edit_permissions( + ult.chat_id, user.id, view_messages=False + ) + return + except BaseException as er: + LOGS.exception(er) + try: + await ultroid_bot.edit_permissions(ult.chat_id, user.id, send_messages=False) + except ChatAdminRequiredError: + return + except Exception as e: + await ult.delete() + LOGS.info(e) + res = await ultroid_bot.inline_query(asst.me.username, f"fsub {user.id}_{joinchat}") + await res[0].click(ult.chat_id, reply_to=ult.id) + + +if udB.get_key("FORCESUB"): + ultroid_bot.add_handler(force_sub, events.NewMessage(incoming=True)) diff --git a/plugins/gdrive.py b/plugins/gdrive.py new file mode 100644 index 0000000000000000000000000000000000000000..a9ff5123d8cbb1cc3f73cfa4fac5f9a92fbe4c67 --- /dev/null +++ b/plugins/gdrive.py @@ -0,0 +1,232 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available + +• `{i}gdul ` + Reply to file to upload on Google Drive. + Add file name to upload on Google Drive. + +• `{i}gdown | ` + Download from Gdrive link or file id. + +• `{i}gdsearch ` + Search file name on Google Drive and get link. + +• `{i}gdlist` + List all GDrive files. + +• `{i}gdfolder` + Link to your Google Drive Folder. + If added then all files will be uploaded in this folder. +""" + +import os +import time + +from telethon.tl.types import Message + +from pyUltroid.fns.gDrive import GDriveManager +from pyUltroid.fns.helper import time_formatter + +from . import ULTConfig, asst, eod, eor, get_string, ultroid_cmd + + +@ultroid_cmd( + pattern="gdown( (.*)|$)", + fullsudo=True, +) +async def gdown(event): + GDrive = GDriveManager() + match = event.pattern_match.group(1).strip() + if not match: + return await eod(event, "`Give file id or Gdrive link to download from!`") + filename = match.split(" | ")[1].strip() if " | " in match else None + eve = await event.eor(get_string("com_1")) + _start = time.time() + status, response = await GDrive._download_file(eve, match, filename) + if not status: + return await eve.edit(response) + await eve.edit( + f"`Downloaded ``{response}`` in {time_formatter((time.time() - _start)*1000)}`" + ) + + +@ultroid_cmd( + pattern="gdlist$", + fullsudo=True, +) +async def files(event): + GDrive = GDriveManager() + if not os.path.exists(GDrive.token_file): + return await event.eor(get_string("gdrive_6").format(asst.me.username)) + eve = await event.eor(get_string("com_1")) + msg = "" + if files := GDrive._list_files: + msg += f"{len(files.keys())} files found in gdrive.\n\n" + for _ in files: + msg += f"> [{files[_]}]({_})\n" + else: + msg += "Nothing in Gdrive" + if len(msg) < 4096: + await eve.edit(msg, link_preview=False) + else: + with open("drive-files.txt", "w") as f: + f.write( + msg.replace("[", "File Name: ") + .replace("](", "\n» Link: ") + .replace(")\n", "\n\n") + ) + try: + await eve.delete() + except BaseException: + pass + await event.client.send_file( + event.chat_id, + "drive-files.txt", + thumb=ULTConfig.thumb, + reply_to=event, + ) + os.remove("drive-files.txt") + + +@ultroid_cmd( + pattern="gdul( (.*)|$)", + fullsudo=True, +) +async def _(event): + GDrive = GDriveManager() + if not os.path.exists(GDrive.token_file): + return await eod(event, get_string("gdrive_6").format(asst.me.username)) + input_file = event.pattern_match.group(1).strip() or await event.get_reply_message() + if not input_file: + return await eod(event, "`Reply to file or give its location.`") + mone = await event.eor(get_string("com_1")) + if isinstance(input_file, Message): + location = "resources/downloads" + if input_file.photo: + filename = await input_file.download_media(location) + else: + filename = input_file.file.name + if not filename: + filename = str(round(time.time())) + filename = f"{location}/{filename}" + try: + filename, downloaded_in = await event.client.fast_downloader( + file=input_file.media.document, + filename=filename, + show_progress=True, + event=mone, + message=get_string("com_5"), + ) + filename = filename.name + except Exception as e: + return await eor(mone, str(e), time=10) + await mone.edit( + f"`Downloaded to ``{filename}`.`", + ) + else: + filename = input_file.strip() + if not os.path.exists(filename): + return await eod( + mone, + "File Not found in local server. Give me a file path :((", + time=5, + ) + folder_id = None + if os.path.isdir(filename): + files = os.listdir(filename) + if not files: + return await eod( + mone, "`Requested directory is empty. Can't create empty directory.`" + ) + folder_id = GDrive.create_directory(filename) + c = 0 + for files in sorted(files): + file = f"{filename}/{files}" + if not os.path.isdir(file): + try: + await GDrive._upload_file(mone, path=file, folder_id=folder_id) + c += 1 + except Exception as e: + return await mone.edit( + f"Exception occurred while uploading to gDrive {e}" + ) + return await mone.edit( + f"`Uploaded `[{filename}](https://drive.google.com/folderview?id={folder_id})` with {c} files.`" + ) + try: + g_drive_link = await GDrive._upload_file( + mone, + filename, + ) + await mone.edit( + get_string("gdrive_7").format(filename.split("/")[-1], g_drive_link) + ) + except Exception as e: + await mone.edit(f"Exception occurred while uploading to gDrive {e}") + + +@ultroid_cmd( + pattern="gdsearch( (.*)|$)", + fullsudo=True, +) +async def _(event): + GDrive = GDriveManager() + if not os.path.exists(GDrive.token_file): + return await event.eor(get_string("gdrive_6").format(asst.me.username)) + input_str = event.pattern_match.group(1).strip() + if not input_str: + return await event.eor("`Give filename to search on GDrive...`") + eve = await event.eor(f"`Searching for {input_str} in G-Drive...`") + files = GDrive.search(input_str) + msg = "" + if files: + msg += ( + f"{len(files.keys())} files with {input_str} in title found in GDrive.\n\n" + ) + for _ in files: + msg += f"> [{files[_]}]({_})\n" + else: + msg += f"`No files with title {input_str}`" + if len(msg) < 4096: + await eve.eor(msg, link_preview=False) + else: + with open("drive-files.txt", "w") as f: + f.write( + msg.replace("[", "File Name: ") + .replace("](", "\n» Link: ") + .replace(")\n", "\n\n") + ) + try: + await eve.delete() + except BaseException: + pass + await event.client.send_file( + event.chat_id, + f"{input_str}.txt", + thumb=ULTConfig.thumb, + reply_to=event, + ) + os.remove(f"{input_str}.txt") + + +@ultroid_cmd( + pattern="gdfolder$", + fullsudo=True, +) +async def _(event): + GDrive = GDriveManager() + if not os.path.exists(GDrive.token_file): + return await event.eor(get_string("gdrive_6").format(asst.me.username)) + if GDrive.folder_id: + await event.eor( + "`Your G-Drive Folder link : `\n" + + GDrive._create_folder_link(GDrive.folder_id) + ) + else: + await eod(event, "Set FOLDERID from your Assistant bot's Settings ") diff --git a/plugins/giftools.py b/plugins/giftools.py new file mode 100644 index 0000000000000000000000000000000000000000..ba10f60a53c97a00ca64790edd6b46c350a2292e --- /dev/null +++ b/plugins/giftools.py @@ -0,0 +1,128 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available + +•`{i}invertgif` + Make Gif Inverted(negative). + +•`{i}bwgif` + Make Gif black and white + +•`{i}rvgif` + Reverse a gif + +•`{i}vtog` + Reply To Video , It will Create Gif + Video to Gif + +•`{i}gif ` + Send video regarding to query. +""" +import os +import random +import time +from datetime import datetime as dt + +from . import HNDLR, LOGS, bash, downloader, get_string, mediainfo, ultroid_cmd + + +@ultroid_cmd(pattern="(bw|invert)gif$") +async def igif(e): + match = e.pattern_match.group(1).strip() + a = await e.get_reply_message() + if not (a and a.media): + return await e.eor("`Reply To gif only`", time=5) + wut = mediainfo(a.media) + if "gif" not in wut: + return await e.eor("`Reply To Gif Only`", time=5) + xx = await e.eor(get_string("com_1")) + z = await a.download_media() + if match == "bw": + cmd = f'ffmpeg -i "{z}" -vf format=gray ult.gif -y' + else: + cmd = f'ffmpeg -i "{z}" -vf lutyuv="y=negval:u=negval:v=negval" ult.gif -y' + try: + await bash(cmd) + await e.client.send_file(e.chat_id, "ult.gif", supports_streaming=True) + os.remove(z) + os.remove("ult.gif") + await xx.delete() + except Exception as er: + LOGS.info(er) + + +@ultroid_cmd(pattern="rvgif$") +async def reverse_gif(event): + a = await event.get_reply_message() + if not (a and a.media) and "video" not in mediainfo(a.media): + return await event.eor("`Reply To Video only`", time=5) + msg = await event.eor(get_string("com_1")) + file = await a.download_media() + await bash(f'ffmpeg -i "{file}" -vf reverse -af areverse reversed.mp4 -y') + await event.respond("- **Reversed Video/GIF**", file="reversed.mp4") + await msg.delete() + os.remove(file) + os.remove("reversed.mp4") + + +@ultroid_cmd(pattern="gif( (.*)|$)") +async def gifs(ult): + get = ult.pattern_match.group(1).strip() + xx = random.randint(0, 5) + n = 0 + if ";" in get: + try: + n = int(get.split(";")[-1]) + except IndexError: + pass + if not get: + return await ult.eor(f"`{HNDLR}gif `") + m = await ult.eor(get_string("com_2")) + gifs = await ult.client.inline_query("gif", get) + if not n: + await gifs[xx].click( + ult.chat_id, reply_to=ult.reply_to_msg_id, silent=True, hide_via=True + ) + else: + for x in range(n): + await gifs[x].click( + ult.chat_id, reply_to=ult.reply_to_msg_id, silent=True, hide_via=True + ) + await m.delete() + + +@ultroid_cmd(pattern="vtog$") +async def vtogif(e): + a = await e.get_reply_message() + if not (a and a.media): + return await e.eor("`Reply To video only`", time=5) + wut = mediainfo(a.media) + if "video" not in wut: + return await e.eor("`Reply To Video Only`", time=5) + xx = await e.eor(get_string("com_1")) + dur = a.media.document.attributes[0].duration + tt = time.time() + if int(dur) < 120: + z = await a.download_media() + await bash( + f'ffmpeg -i {z} -vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 ult.gif -y' + ) + else: + filename = a.file.name + if not filename: + filename = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" + vid = await downloader(filename, a.media.document, xx, tt, get_string("com_5")) + z = vid.name + await bash( + f'ffmpeg -ss 3 -t 100 -i {z} -vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 ult.gif' + ) + + await e.client.send_file(e.chat_id, "ult.gif", support_stream=True) + os.remove(z) + os.remove("ult.gif") + await xx.delete() diff --git a/plugins/glitch.py b/plugins/glitch.py new file mode 100644 index 0000000000000000000000000000000000000000..cd64f0d5e3c80d746a873a1b9a9d7b532d929713 --- /dev/null +++ b/plugins/glitch.py @@ -0,0 +1,42 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +•`{i}glitch ` + gives a glitchy gif. +""" +import os + +from . import bash, get_string, mediainfo, ultroid_cmd + + +@ultroid_cmd(pattern="glitch$") +async def _(e): + try: + import glitch_me # ignore :pylint + except ModuleNotFoundError: + await bash( + "pip install -e git+https://github.com/1Danish-00/glitch_me.git#egg=glitch_me" + ) + reply = await e.get_reply_message() + if not reply or not reply.media: + return await e.eor(get_string("cvt_3")) + xx = await e.eor(get_string("glitch_1")) + wut = mediainfo(reply.media) + if wut.startswith(("pic", "sticker")): + ok = await reply.download_media() + elif reply.document and reply.document.thumbs: + ok = await reply.download_media(thumb=-1) + else: + return await xx.eor(get_string("com_4")) + cmd = f"glitch_me gif --line_count 200 -f 10 -d 50 '{ok}' ult.gif" + await bash(cmd) + await e.reply(file="ult.gif", force_document=False) + await xx.delete() + os.remove(ok) + os.remove("ult.gif") diff --git a/plugins/globaltools.py b/plugins/globaltools.py new file mode 100644 index 0000000000000000000000000000000000000000..ebce824638aec9c999937ef4dab355e09fe977c8 --- /dev/null +++ b/plugins/globaltools.py @@ -0,0 +1,753 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}gban ` +• `{i}ungban` + Ban/Unban Globally. + +• `{i}gstat ` + Check if user is GBanned. + +• `{i}listgban` : List all GBanned users. + +• `{i}gmute` | `{i}ungmute` + Mute/UnMute Globally. + +• `{i}gkick ` `Globally Kick User` +• `{i}gcast ` `Globally Send msg in all grps` + +• `{i}gadmincast ` `Globally broadcast in your admin chats` +• `{i}gucast ` `Globally send msg in all pm users` + +• `{i}gblacklist ` + globally promote user where you are admin + - Set whether To promote only in groups/channels/all. + Eg- `gpromote group boss` ~ promotes user in all grps. + `gpromote @username all sar` ~ promote the user in all group & channel +• `{i}gdemote` - `demote user globally` +""" +import asyncio +import os + +from telethon.errors.rpcerrorlist import ChatAdminRequiredError, FloodWaitError +from telethon.tl.functions.channels import EditAdminRequest +from telethon.tl.functions.contacts import BlockRequest, UnblockRequest +from telethon.tl.types import ChatAdminRights, User + +from pyUltroid.dB import DEVLIST +from pyUltroid.dB.base import KeyManager +from pyUltroid.dB.gban_mute_db import ( + gban, + gmute, + is_gbanned, + is_gmuted, + list_gbanned, + ungban, + ungmute, +) +from pyUltroid.fns.tools import create_tl_btn, format_btn, get_msg_button + +from . import ( + HNDLR, + LOGS, + NOSPAM_CHAT, + OWNER_NAME, + eod, + eor, + get_string, + inline_mention, + ultroid_bot, + ultroid_cmd, +) +from ._inline import something + +_gpromote_rights = ChatAdminRights( + add_admins=False, + invite_users=True, + change_info=False, + ban_users=True, + delete_messages=True, + pin_messages=True, +) + +_gdemote_rights = ChatAdminRights( + add_admins=False, + invite_users=False, + change_info=False, + ban_users=False, + delete_messages=False, + pin_messages=False, +) + +keym = KeyManager("GBLACKLISTS", cast=list) + + +@ultroid_cmd(pattern="gpromote( (.*)|$)", fullsudo=True) +async def _(e): + x = e.pattern_match.group(1).strip() + ultroid_bot = e.client + if not x: + return await e.eor(get_string("schdl_2"), time=5) + user = await e.get_reply_message() + if user: + ev = await e.eor("`Promoting Replied User Globally`") + ok = e.text.split() + key = "all" + if len(ok) > 1 and (("group" in ok[1]) or ("channel" in ok[1])): + key = ok[1] + rank = ok[2] if len(ok) > 2 else "AdMin" + c = 0 + user.id = user.peer_id.user_id if e.is_private else user.from_id.user_id + async for x in e.client.iter_dialogs(): + if ( + "group" in key.lower() + and x.is_group + or "group" not in key.lower() + and "channel" in key.lower() + and x.is_channel + ): + try: + await e.client( + EditAdminRequest( + x.id, + user.id, + _gpromote_rights, + rank, + ), + ) + c += 1 + except BaseException: + pass + elif ( + ("group" not in key.lower() or x.is_group) + and ( + "group" in key.lower() + or "channel" not in key.lower() + or x.is_channel + ) + and ( + "group" in key.lower() + or "channel" in key.lower() + or x.is_group + or x.is_channel + ) + ): + try: + await e.client( + EditAdminRequest( + x.id, + user.id, + _gpromote_rights, + rank, + ), + ) + c += 1 + except Exception as er: + LOGS.info(er) + await eor(ev, f"Promoted The Replied Users in Total : {c} {key} chats") + else: + k = e.text.split() + if not k[1]: + return await eor( + e, "`Give someone's username/id or replied to user.", time=5 + ) + user = k[1] + if user.isdigit(): + user = int(user) + try: + name = await e.client.get_entity(user) + except BaseException: + return await e.eor(f"`No User Found Regarding {user}`", time=5) + ev = await e.eor(f"`Promoting {name.first_name} globally.`") + key = "all" + if len(k) > 2 and (("group" in k[2]) or ("channel" in k[2])): + key = k[2] + rank = k[3] if len(k) > 3 else "AdMin" + c = 0 + async for x in e.client.iter_dialogs(): + if ( + "group" in key.lower() + and x.is_group + or "group" not in key.lower() + and "channel" in key.lower() + and x.is_channel + or "group" not in key.lower() + and "channel" not in key.lower() + and (x.is_group or x.is_channel) + ): + try: + await ultroid_bot( + EditAdminRequest( + x.id, + user, + _gpromote_rights, + rank, + ), + ) + c += 1 + except BaseException: + pass + await eor(ev, f"Promoted {name.first_name} in Total : {c} {key} chats.") + + +@ultroid_cmd(pattern="gdemote( (.*)|$)", fullsudo=True) +async def _(e): + x = e.pattern_match.group(1).strip() + ultroid_bot = e.client + if not x: + return await e.eor(get_string("schdl_2"), time=5) + user = await e.get_reply_message() + if user: + user.id = user.peer_id.user_id if e.is_private else user.from_id.user_id + ev = await e.eor("`Demoting Replied User Globally`") + ok = e.text.split() + key = "all" + if len(ok) > 1 and (("group" in ok[1]) or ("channel" in ok[1])): + key = ok[1] + rank = "Not AdMin" + c = 0 + async for x in e.client.iter_dialogs(): + if ( + "group" in key.lower() + and x.is_group + or "group" not in key.lower() + and "channel" in key.lower() + and x.is_channel + or "group" not in key.lower() + and "channel" not in key.lower() + and (x.is_group or x.is_channel) + ): + try: + await ultroid_bot( + EditAdminRequest( + x.id, + user.id, + _gdemote_rights, + rank, + ), + ) + c += 1 + except BaseException: + pass + await eor(ev, f"Demoted The Replied Users in Total : {c} {key} chats") + else: + k = e.text.split() + if not k[1]: + return await eor( + e, "`Give someone's username/id or replied to user.", time=5 + ) + user = k[1] + if user.isdigit(): + user = int(user) + try: + name = await ultroid_bot.get_entity(user) + except BaseException: + return await e.eor(f"`No User Found Regarding {user}`", time=5) + ev = await e.eor(f"`Demoting {name.first_name} globally.`") + key = "all" + if len(k) > 2 and (("group" in k[2]) or ("channel" in k[2])): + key = k[2] + rank = "Not AdMin" + c = 0 + async for x in ultroid_bot.iter_dialogs(): + if ( + "group" in key.lower() + and x.is_group + or "group" not in key.lower() + and "channel" in key.lower() + and x.is_channel + or "group" not in key.lower() + and "channel" not in key.lower() + and (x.is_group or x.is_channel) + ): + try: + await ultroid_bot( + EditAdminRequest( + x.id, + user, + _gdemote_rights, + rank, + ), + ) + c += 1 + except BaseException: + pass + await eor(ev, f"Demoted {name.first_name} in Total : {c} {key} chats.") + + +@ultroid_cmd(pattern="ungban( (.*)|$)", fullsudo=True) +async def _(e): + xx = await e.eor("`UnGbanning...`") + match = e.pattern_match.group(1).strip() + peer = None + if e.reply_to_msg_id: + userid = (await e.get_reply_message()).sender_id + elif match: + try: + userid = int(match) + except ValueError: + userid = match + try: + userid = (await e.client.get_entity(userid)).id + except Exception as er: + return await xx.edit(f"Failed to get User...\nError: {er}") + elif e.is_private: + userid = e.chat_id + else: + return await xx.eor("`Reply to some msg or add their id.`", time=5) + if not is_gbanned(userid): + return await xx.edit("`User/Channel is not Gbanned...`") + try: + if not peer: + peer = await e.client.get_entity(userid) + name = inline_mention(peer) + except BaseException: + userid = int(userid) + name = str(userid) + chats = 0 + if e.client._dialogs: + dialog = e.client._dialogs + else: + dialog = await e.client.get_dialogs() + e.client._dialogs.extend(dialog) + for ggban in dialog: + if ggban.is_group or ggban.is_channel: + try: + await e.client.edit_permissions(ggban.id, userid, view_messages=True) + chats += 1 + except FloodWaitError as fw: + LOGS.info( + f"[FLOOD_WAIT_ERROR] : on Ungban\nSleeping for {fw.seconds+10}" + ) + await asyncio.sleep(fw.seconds + 10) + try: + await e.client.edit_permissions( + ggban.id, userid, view_messages=True + ) + chats += 1 + except BaseException as er: + LOGS.exception(er) + except (ChatAdminRequiredError, ValueError): + pass + except BaseException as er: + LOGS.exception(er) + ungban(userid) + if isinstance(peer, User): + await e.client(UnblockRequest(userid)) + await xx.edit( + f"`Ungbaned` {name} in {chats} `chats.\nRemoved from gbanwatch.`", + ) + + +@ultroid_cmd(pattern="gban( (.*)|$)", fullsudo=True) +async def _(e): + xx = await e.eor("`Gbanning...`") + reason = "" + if e.reply_to_msg_id: + userid = (await e.get_reply_message()).sender_id + try: + reason = e.text.split(" ", maxsplit=1)[1] + except IndexError: + pass + elif e.pattern_match.group(1).strip(): + usr = e.text.split(maxsplit=2)[1] + try: + userid = await e.client.parse_id(usr) + except ValueError: + userid = usr + try: + reason = e.text.split(maxsplit=2)[2] + except IndexError: + pass + elif e.is_private: + userid = e.chat_id + try: + reason = e.text.split(" ", maxsplit=1)[1] + except IndexError: + pass + else: + return await xx.eor("`Reply to some msg or add their id.`", time=5) + user = None + try: + user = await e.client.get_entity(userid) + name = inline_mention(user) + except BaseException: + userid = int(userid) + name = str(userid) + chats = 0 + if userid == ultroid_bot.uid: + return await xx.eor("`I can't gban myself.`", time=3) + elif userid in DEVLIST: + return await xx.eor("`I can't gban my Developers.`", time=3) + elif is_gbanned(userid): + return await eod( + xx, + "`User is already gbanned and added to gbanwatch.`", + time=4, + ) + if e.client._dialogs: + dialog = e.client._dialogs + else: + dialog = await e.client.get_dialogs() + e.client._dialogs.extend(dialog) + for ggban in dialog: + if ggban.is_group or ggban.is_channel: + try: + await e.client.edit_permissions(ggban.id, userid, view_messages=False) + chats += 1 + except FloodWaitError as fw: + LOGS.info( + f"[FLOOD_WAIT_ERROR] : on GBAN Command\nSleeping for {fw.seconds+10}" + ) + await asyncio.sleep(fw.seconds + 10) + try: + await e.client.edit_permissions( + ggban.id, userid, view_messages=False + ) + chats += 1 + except BaseException as er: + LOGS.exception(er) + except (ChatAdminRequiredError, ValueError): + pass + except BaseException as er: + LOGS.exception(er) + gban(userid, reason) + if isinstance(user, User): + await e.client(BlockRequest(userid)) + gb_msg = f"**#Gbanned** {name} `in {chats} chats and added to gbanwatch!`" + if reason: + gb_msg += f"\n**Reason** : {reason}" + await xx.edit(gb_msg) + + +@ultroid_cmd(pattern="g(admin|)cast( (.*)|$)", fullsudo=True) +async def gcast(event): + text, btn, reply = "", None, None + if xx := event.pattern_match.group(2): + msg, btn = get_msg_button(event.text.split(maxsplit=1)[1]) + elif event.is_reply: + reply = await event.get_reply_message() + msg = reply.text + if reply.buttons: + btn = format_btn(reply.buttons) + else: + msg, btn = get_msg_button(msg) + else: + return await eor( + event, "`Give some text to Globally Broadcast or reply a message..`" + ) + + kk = await event.eor("`Globally Broadcasting Msg...`") + er = 0 + done = 0 + err = "" + if event.client._dialogs: + dialog = event.client._dialogs + else: + dialog = await event.client.get_dialogs() + event.client._dialogs.extend(dialog) + for x in dialog: + if x.is_group: + chat = x.entity.id + if ( + not keym.contains(chat) + and int(f"-100{str(chat)}") not in NOSPAM_CHAT + and ( + ( + event.text[2:7] != "admin" + or (x.entity.admin_rights or x.entity.creator) + ) + ) + ): + try: + if btn: + bt = create_tl_btn(btn) + await something( + event, + msg, + reply.media if reply else None, + bt, + chat=chat, + reply=False, + ) + else: + await event.client.send_message( + chat, msg, file=reply.media if reply else None + ) + done += 1 + except FloodWaitError as fw: + await asyncio.sleep(fw.seconds + 10) + try: + if btn: + bt = create_tl_btn(btn) + await something( + event, + msg, + reply.media if reply else None, + bt, + chat=chat, + reply=False, + ) + else: + await event.client.send_message( + chat, msg, file=reply.media if reply else None + ) + done += 1 + except Exception as rr: + err += f"• {rr}\n" + er += 1 + except BaseException as h: + err += f"• {str(h)}" + "\n" + er += 1 + text += f"Done in {done} chats, error in {er} chat(s)" + if err != "": + open("gcast-error.log", "w+").write(err) + text += f"\nYou can do `{HNDLR}ul gcast-error.log` to know error report." + await kk.edit(text) + + +@ultroid_cmd(pattern="gucast( (.*)|$)", fullsudo=True) +async def gucast(event): + msg, btn, reply = "", None, None + if xx := event.pattern_match.group(1).strip(): + msg, btn = get_msg_button(event.text.split(maxsplit=1)[1]) + elif event.is_reply: + reply = await event.get_reply_message() + msg = reply.text + if reply.buttons: + btn = format_btn(reply.buttons) + else: + msg, btn = get_msg_button(msg) + else: + return await eor( + event, "`Give some text to Globally Broadcast or reply a message..`" + ) + kk = await event.eor("`Globally Broadcasting Msg...`") + er = 0 + done = 0 + if event.client._dialogs: + dialog = event.client._dialogs + else: + dialog = await event.client.get_dialogs() + event.client._dialogs.extend(dialog) + for x in dialog: + if x.is_user and not x.entity.bot: + chat = x.id + if not keym.contains(chat): + try: + if btn: + bt = create_tl_btn(btn) + await something( + event, + msg, + reply.media if reply else None, + bt, + chat=chat, + reply=False, + ) + else: + await event.client.send_message( + chat, msg, file=reply.media if reply else None + ) + done += 1 + except BaseException: + er += 1 + await kk.edit(f"Done in {done} chats, error in {er} chat(s)") + + +@ultroid_cmd(pattern="gkick( (.*)|$)", fullsudo=True) +async def gkick(e): + xx = await e.eor("`Gkicking...`") + if e.reply_to_msg_id: + userid = (await e.get_reply_message()).sender_id + elif e.pattern_match.group(1).strip(): + userid = await e.client.parse_id(e.pattern_match.group(1).strip()) + elif e.is_private: + userid = e.chat_id + else: + return await xx.edit("`Reply to some msg or add their id.`", time=5) + name = (await e.client.get_entity(userid)).first_name + chats = 0 + if userid == ultroid_bot.uid: + return await xx.eor("`I can't gkick myself.`", time=3) + if userid in DEVLIST: + return await xx.eor("`I can't gkick my Developers.`", time=3) + if e.client._dialogs: + dialog = e.client._dialogs + else: + dialog = await e.client.get_dialogs() + e.client._dialogs.extend(dialog) + for gkick in dialog: + if gkick.is_group or gkick.is_channel: + try: + await e.client.kick_participant(gkick.id, userid) + chats += 1 + except BaseException: + pass + await xx.edit(f"`Gkicked` [{name}](tg://user?id={userid}) `in {chats} chats.`") + + +@ultroid_cmd(pattern="gmute( (.*)|$)", fullsudo=True) +async def _(e): + xx = await e.eor("`Gmuting...`") + if e.reply_to_msg_id: + userid = (await e.get_reply_message()).sender_id + elif e.pattern_match.group(1).strip(): + userid = await e.client.parse_id(e.pattern_match.group(1).strip()) + elif e.is_private: + userid = e.chat_id + else: + return await xx.eor("`Reply to some msg or add their id.`", tome=5, time=5) + name = await e.client.get_entity(userid) + chats = 0 + if userid == ultroid_bot.uid: + return await xx.eor("`I can't gmute myself.`", time=3) + if userid in DEVLIST: + return await xx.eor("`I can't gmute my Developers.`", time=3) + if is_gmuted(userid): + return await xx.eor("`User is already gmuted.`", time=4) + if e.client._dialogs: + dialog = e.client._dialogs + else: + dialog = await e.client.get_dialogs() + e.client._dialogs.extend(dialog) + for onmute in dialog: + if onmute.is_group: + try: + await e.client.edit_permissions(onmute.id, userid, send_messages=False) + chats += 1 + except BaseException: + pass + gmute(userid) + await xx.edit(f"`Gmuted` {inline_mention(name)} `in {chats} chats.`") + + +@ultroid_cmd(pattern="ungmute( (.*)|$)", fullsudo=True) +async def _(e): + xx = await e.eor("`UnGmuting...`") + if e.reply_to_msg_id: + userid = (await e.get_reply_message()).sender_id + elif e.pattern_match.group(1).strip(): + userid = await e.client.parse_id(e.pattern_match.group(1).strip()) + elif e.is_private: + userid = e.chat_id + else: + return await xx.eor("`Reply to some msg or add their id.`", time=5) + name = (await e.client.get_entity(userid)).first_name + chats = 0 + if not is_gmuted(userid): + return await xx.eor("`User is not gmuted.`", time=3) + if e.client._dialogs: + dialog = e.client._dialogs + else: + dialog = await e.client.get_dialogs() + e.client._dialogs.extend(dialog) + for hurr in dialog: + if hurr.is_group: + try: + await e.client.edit_permissions(hurr.id, userid, send_messages=True) + chats += 1 + except BaseException: + pass + ungmute(userid) + await xx.edit(f"`Ungmuted` {inline_mention(name)} `in {chats} chats.`") + + +@ultroid_cmd( + pattern="listgban$", +) +async def list_gengbanned(event): + users = list_gbanned() + x = await event.eor(get_string("com_1")) + msg = "" + if not users: + return await x.edit("`You haven't GBanned anyone!`") + for i in users: + try: + name = await event.client.get_entity(int(i)) + except BaseException: + name = i + msg += f"User: {inline_mention(name, html=True)}\n" + reason = users[i] + msg += f"Reason: {reason}\n\n" if reason is not None else "\n" + gbanned_users = f"List of users GBanned by {OWNER_NAME}:\n\n{msg}" + if len(gbanned_users) > 4096: + with open("gbanned.txt", "w") as f: + f.write( + gbanned_users.replace("", "") + .replace("", "") + .replace("", "") + ) + await x.reply( + file="gbanned.txt", + message=f"List of users GBanned by {inline_mention(ultroid_bot.me)}", + ) + os.remove("gbanned.txt") + await x.delete() + else: + await x.edit(gbanned_users, parse_mode="html") + + +@ultroid_cmd( + pattern="gstat( (.*)|$)", +) +async def gstat_(e): + xx = await e.eor(get_string("com_1")) + if e.is_private: + userid = (await e.get_chat()).id + elif e.reply_to_msg_id: + userid = (await e.get_reply_message()).sender_id + elif e.pattern_match.group(1).strip(): + try: + userid = await e.client.parse_id(e.pattern_match.group(1).strip()) + except Exception as err: + return await xx.eor(f"{err}", time=10) + else: + return await xx.eor("`Reply to some msg or add their id.`", time=5) + name = (await e.client.get_entity(userid)).first_name + msg = f"**{name} is " + is_banned = is_gbanned(userid) + reason = list_gbanned().get(userid) + if is_banned: + msg += "Globally Banned" + msg += f" with reason** `{reason}`" if reason else ".**" + else: + msg += "not Globally Banned.**" + await xx.edit(msg) + + +@ultroid_cmd(pattern="gblacklist$") +async def blacklist_(event): + await gblacker(event, "add") + + +@ultroid_cmd(pattern="ungblacklist$") +async def ungblacker(event): + await gblacker(event, "remove") + + +async def gblacker(event, type_): + try: + chat_id = int(event.text.split(maxsplit=1)[1]) + try: + chat_id = (await event.client.get_entity(chat_id)).id + except Exception as e: + return await event.eor(f"**ERROR**\n`{str(e)}`") + except IndexError: + chat_id = event.chat_id + if type_ == "add": + keym.add(chat_id) + elif type_ == "remove": + keym.remove(chat_id) + await event.eor(f"Global Broadcasts: \n{type_}ed {chat_id}") diff --git a/plugins/greetings.py b/plugins/greetings.py new file mode 100644 index 0000000000000000000000000000000000000000..ee2e62b1afb4e939af4b44161e6eb9ab02a80661 --- /dev/null +++ b/plugins/greetings.py @@ -0,0 +1,201 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +---- Welcomes ---- +• `{i}setwelcome ` + Set welcome message in the current chat. + +• `{i}clearwelcome` + Delete the welcome in the current chat. + +• `{i}getwelcome` + Get the welcome message in the current chat. + +---- GoodByes ---- +• `{i}setgoodbye ` + Set goodbye message in the current chat. + +• `{i}cleargoodbye` + Delete the goodbye in the current chat. + +• `{i}getgoodbye` + Get the goodbye message in the current chat. + +• `{i}thankmembers on/off` + Send a thank you sticker on hitting a members count of 100*x in your groups. +""" +import os + +from . import upload_file as uf +from telethon.utils import pack_bot_file_id + +from pyUltroid.dB.greetings_db import ( + add_goodbye, + add_thanks, + add_welcome, + delete_goodbye, + delete_welcome, + get_goodbye, + get_welcome, + must_thank, + remove_thanks, +) +from pyUltroid.fns.tools import create_tl_btn, format_btn, get_msg_button + +from . import HNDLR, eor, get_string, mediainfo, ultroid_cmd +from ._inline import something + +Note = "\n\nNote: `{mention}`, `{group}`, `{count}`, `{name}`, `{fullname}`, `{username}`, `{userid}` can be used as formatting parameters.\n\n" + + +@ultroid_cmd(pattern="setwelcome", groups_only=True) +async def setwel(event): + x = await event.eor(get_string("com_1")) + r = await event.get_reply_message() + btn = format_btn(r.buttons) if (r and r.buttons) else None + try: + text = event.text.split(maxsplit=1)[1] + except IndexError: + text = r.text if r else None + if r and r.media: + wut = mediainfo(r.media) + if wut.startswith(("pic", "gif")): + dl = await r.download_media() + m = uf(dl) + os.remove(dl) + elif wut == "video": + if r.media.document.size > 8 * 1000 * 1000: + return await eor(x, get_string("com_4"), time=5) + dl = await r.download_media() + m = uf(dl) + os.remove(dl) + elif wut == "web": + m = None + else: + m = pack_bot_file_id(r.media) + if r.text: + txt = r.text + if not btn: + txt, btn = get_msg_button(r.text) + add_welcome(event.chat_id, txt, m, btn) + else: + add_welcome(event.chat_id, None, m, btn) + await eor(x, get_string("grt_1")) + elif text: + if not btn: + txt, btn = get_msg_button(text) + add_welcome(event.chat_id, txt, None, btn) + await eor(x, get_string("grt_1")) + else: + await eor(x, get_string("grt_3"), time=5) + + +@ultroid_cmd(pattern="clearwelcome$", groups_only=True) +async def clearwel(event): + if not get_welcome(event.chat_id): + return await event.eor(get_string("grt_4"), time=5) + delete_welcome(event.chat_id) + await event.eor(get_string("grt_5"), time=5) + + +@ultroid_cmd(pattern="getwelcome$", groups_only=True) +async def listwel(event): + wel = get_welcome(event.chat_id) + if not wel: + return await event.eor(get_string("grt_4"), time=5) + msgg, med = wel["welcome"], wel["media"] + if wel.get("button"): + btn = create_tl_btn(wel["button"]) + return await something(event, msgg, med, btn) + await event.reply(f"**Welcome Note in this chat**\n\n`{msgg}`", file=med) + await event.delete() + + +@ultroid_cmd(pattern="setgoodbye", groups_only=True) +async def setgb(event): + x = await event.eor(get_string("com_1")) + r = await event.get_reply_message() + btn = format_btn(r.buttons) if (r and r.buttons) else None + try: + text = event.text.split(maxsplit=1)[1] + except IndexError: + text = r.text if r else None + if r and r.media: + wut = mediainfo(r.media) + if wut.startswith(("pic", "gif")): + dl = await r.download_media() + m = uf(dl) + os.remove(dl) + elif wut == "video": + if r.media.document.size > 8 * 1000 * 1000: + return await eor(x, get_string("com_4"), time=5) + dl = await r.download_media() + m = uf(dl) + os.remove(dl) + elif wut == "web": + m = None + else: + m = pack_bot_file_id(r.media) + if r.text: + txt = r.text + if not btn: + txt, btn = get_msg_button(r.text) + add_goodbye(event.chat_id, txt, m, btn) + else: + add_goodbye(event.chat_id, None, m, btn) + await eor(x, "`Goodbye note saved`") + elif text: + if not btn: + txt, btn = get_msg_button(text) + add_goodbye(event.chat_id, txt, None, btn) + await eor(x, "`Goodbye note saved`") + else: + await eor(x, get_string("grt_7"), time=5) + + +@ultroid_cmd(pattern="cleargoodbye$", groups_only=True) +async def clearwgb(event): + if not get_goodbye(event.chat_id): + return await event.eor(get_string("grt_6"), time=5) + delete_goodbye(event.chat_id) + await event.eor("`Goodbye Note Deleted`", time=5) + + +@ultroid_cmd(pattern="getgoodbye$", groups_only=True) +async def listgd(event): + wel = get_goodbye(event.chat_id) + if not wel: + return await event.eor(get_string("grt_6"), time=5) + msgg = wel["goodbye"] + med = wel["media"] + if wel.get("button"): + btn = create_tl_btn(wel["button"]) + return await something(event, msgg, med, btn) + await event.reply(f"**Goodbye Note in this chat**\n\n`{msgg}`", file=med) + await event.delete() + + +@ultroid_cmd(pattern="thankmembers (on|off)", groups_only=True) +async def thank_set(event): + type_ = event.pattern_match.group(1).strip() + if not type_ or type_ == "": + await eor( + event, + f"**Current Chat Settings:**\n**Thanking Members:** `{must_thank(event.chat_id)}`\n\nUse `{HNDLR}thankmembers on` or `{HNDLR}thankmembers off` to toggle current settings!", + ) + return + chat = event.chat_id + if type_.lower() == "on": + add_thanks(chat) + elif type_.lower() == "off": + remove_thanks(chat) + await eor( + event, + f"**Done! Thank you members has been turned** `{type_.lower()}` **for this chat**!", + ) diff --git a/plugins/imagetools.py b/plugins/imagetools.py new file mode 100644 index 0000000000000000000000000000000000000000..c4dd93d60d9b0f8090644a519aa72a772993ec77 --- /dev/null +++ b/plugins/imagetools.py @@ -0,0 +1,292 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}border ` + To create border around that media.. + Ex - `{i}border 12,22,23` + - `{i}border 12,22,23 ; width (in number)` + +• `{i}grey ` + To make it black nd white. + +• `{i}color ` + To make it Colorfull. + +• `{i}toon ` + To make it toon. + +• `{i}danger ` + To make it look Danger. + +• `{i}negative ` + To make negative image. + +• `{i}blur ` + To make it blurry. + +• `{i}quad ` + create a Vortex. + +• `{i}mirror ` + To create mirror pic. + +• `{i}flip ` + To make it flip. + +• `{i}sketch ` + To draw its sketch. + +• `{i}blue ` + just cool. + +• `{i}csample ` + example : `{i}csample red` + `{i}csample #ffffff` + +• `{i}pixelator ` + Create a Pixelated Image.. +""" +import os + +from . import LOGS, con + +try: + import cv2 +except ImportError: + LOGS.error(f"{__file__}: OpenCv not Installed.") + +import numpy as np + +try: + from PIL import Image +except ImportError: + Image = None + LOGS.info(f"{__file__}: PIL not Installed.") + +from . import upload_file as upf +from telethon.errors.rpcerrorlist import ( + ChatSendMediaForbiddenError, + MessageDeleteForbiddenError, +) + +from . import ( + Redis, + async_searcher, + download_file, + get_string, + requests, + udB, + ultroid_cmd, +) + + +@ultroid_cmd(pattern="color$") +async def _(event): + reply = await event.get_reply_message() + if not (reply and reply.media): + return await event.eor("`Reply To a Black and White Image`") + xx = await event.eor("`Coloring image 🎨🖌️...`") + image = await reply.download_media() + img = cv2.VideoCapture(image) + ret, frame = img.read() + cv2.imwrite("ult.jpg", frame) + if udB.get_key("DEEP_API"): + key = Redis("DEEP_API") + else: + key = "quickstart-QUdJIGlzIGNvbWluZy4uLi4K" + r = requests.post( + "https://api.deepai.org/api/colorizer", + files={"image": open("ult.jpg", "rb")}, + headers={"api-key": key}, + ) + os.remove("ult.jpg") + os.remove(image) + if "status" in r.json(): + return await event.edit( + r.json()["status"] + "\nGet api nd set `{i}setdb DEEP_API key`" + ) + r_json = r.json()["output_url"] + await event.client.send_file(event.chat_id, r_json, reply_to=reply) + await xx.delete() + + +@ultroid_cmd(pattern="(grey|blur|negative|danger|mirror|quad|sketch|flip|toon)$") +async def ult_tools(event): + match = event.pattern_match.group(1) + ureply = await event.get_reply_message() + if not (ureply and (ureply.media)): + await event.eor(get_string("cvt_3")) + return + ultt = await ureply.download_media() + xx = await event.eor(get_string("com_1")) + if ultt.endswith(".tgs"): + xx = await xx.edit(get_string("sts_9")) + file = await con.convert(ultt, convert_to="png", outname="ult") + ult = cv2.imread(file) + if match == "grey": + ultroid = cv2.cvtColor(ult, cv2.COLOR_BGR2GRAY) + elif match == "blur": + ultroid = cv2.GaussianBlur(ult, (35, 35), 0) + elif match == "negative": + ultroid = cv2.bitwise_not(ult) + elif match == "danger": + dan = cv2.cvtColor(ult, cv2.COLOR_BGR2RGB) + ultroid = cv2.cvtColor(dan, cv2.COLOR_HSV2BGR) + elif match == "mirror": + ish = cv2.flip(ult, 1) + ultroid = cv2.hconcat([ult, ish]) + elif match == "flip": + trn = cv2.flip(ult, 1) + ish = cv2.rotate(trn, cv2.ROTATE_180) + ultroid = cv2.vconcat([ult, ish]) + elif match == "quad": + ult = cv2.imread(file) + roid = cv2.flip(ult, 1) + mici = cv2.hconcat([ult, roid]) + fr = cv2.flip(mici, 1) + trn = cv2.rotate(fr, cv2.ROTATE_180) + ultroid = cv2.vconcat([mici, trn]) + elif match == "sketch": + gray_image = cv2.cvtColor(ult, cv2.COLOR_BGR2GRAY) + inverted_gray_image = 255 - gray_image + blurred_img = cv2.GaussianBlur(inverted_gray_image, (21, 21), 0) + inverted_blurred_img = 255 - blurred_img + ultroid = cv2.divide(gray_image, inverted_blurred_img, scale=256.0) + elif match == "toon": + height, width, _ = ult.shape + samples = np.zeros([height * width, 3], dtype=np.float32) + count = 0 + for x in range(height): + for y in range(width): + samples[count] = ult[x][y] + count += 1 + _, labels, centers = cv2.kmeans( + samples, + 12, + None, + (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001), + 5, + cv2.KMEANS_PP_CENTERS, + ) + centers = np.uint8(centers) + ish = centers[labels.flatten()] + ultroid = ish.reshape(ult.shape) + cv2.imwrite("ult.jpg", ultroid) + await ureply.reply( + file="ult.jpg", + force_document=False, + ) + await xx.delete() + os.remove("ult.jpg") + os.remove(file) + + +@ultroid_cmd(pattern="csample (.*)") +async def sampl(ult): + if color := ult.pattern_match.group(1).strip(): + img = Image.new("RGB", (200, 100), f"{color}") + img.save("csample.png") + try: + try: + await ult.delete() + await ult.respond(f"Colour Sample for `{color}` !", file="csample.png") + except MessageDeleteForbiddenError: + await ult.reply(f"Colour Sample for `{color}` !", file="csample.png") + except ChatSendMediaForbiddenError: + await ult.eor("Umm! Sending Media is disabled here!") + + else: + await ult.eor("Wrong Color Name/Hex Code specified!") + + +@ultroid_cmd( + pattern="blue$", +) +async def ultd(event): + ureply = await event.get_reply_message() + xx = await event.eor("`...`") + if not (ureply and (ureply.media)): + await xx.edit(get_string("cvt_3")) + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit(get_string("sts_9")) + file = await con.convert(ultt, convert_to="png", outname="ult") + lnk = upf(file) + r = await async_searcher( + f"https://nekobot.xyz/api/imagegen?type=blurpify&image={lnk}", re_json=True + ) + ms = r.get("message") + if not r["success"]: + return await xx.edit(ms) + await download_file(ms, "ult.png") + img = Image.open("ult.png").convert("RGB") + img.save("ult.webp", "webp") + await event.client.send_file( + event.chat_id, + "ult.webp", + force_document=False, + reply_to=event.reply_to_msg_id, + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.webp") + os.remove(ultt) + + +@ultroid_cmd(pattern="border( (.*)|$)") +async def ok(event): + hm = await event.get_reply_message() + if not (hm and (hm.photo or hm.sticker)): + return await event.eor("`Reply to Sticker or Photo..`") + col = event.pattern_match.group(1).strip() + wh = 20 + if not col: + col = [255, 255, 255] + else: + try: + if ";" in col: + col_ = col.split(";", maxsplit=1) + wh = int(col_[1]) + col = col_[0] + col = [int(col) for col in col.split(",")[:2]] + except ValueError: + return await event.eor("`Not a Valid Input...`") + okla = await hm.download_media() + img1 = cv2.imread(okla) + constant = cv2.copyMakeBorder(img1, wh, wh, wh, wh, cv2.BORDER_CONSTANT, value=col) + cv2.imwrite("output.png", constant) + await event.client.send_file(event.chat.id, "output.png") + os.remove("output.png") + os.remove(okla) + await event.delete() + + +@ultroid_cmd(pattern="pixelator( (.*)|$)") +async def pixelator(event): + reply_message = await event.get_reply_message() + if not (reply_message and (reply_message.photo or reply_message.sticker)): + return await event.eor("`Reply to a photo`") + hw = 50 + try: + hw = int(event.pattern_match.group(1).strip()) + except (ValueError, TypeError): + pass + msg = await event.eor(get_string("com_1")) + image = await reply_message.download_media() + input_ = cv2.imread(image) + height, width = input_.shape[:2] + w, h = (hw, hw) + temp = cv2.resize(input_, (w, h), interpolation=cv2.INTER_LINEAR) + output = cv2.resize(temp, (width, height), interpolation=cv2.INTER_NEAREST) + cv2.imwrite("output.jpg", output) + await msg.respond("• Pixelated by Ultroid", file="output.jpg") + await msg.delete() + os.remove("output.jpg") + os.remove(image) diff --git a/plugins/locks.py b/plugins/locks.py new file mode 100644 index 0000000000000000000000000000000000000000..7f9938cb7ecbb9e7f0c4977633ee816a3e0fe87d --- /dev/null +++ b/plugins/locks.py @@ -0,0 +1,39 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}lock ` + Lock the Used Setting in Used Group. + +• `{i}unlock ` + UNLOCK the Used Setting in Used Group. +""" +from telethon.tl.functions.messages import EditChatDefaultBannedRightsRequest + +from pyUltroid.fns.admins import lock_unlock + +from . import ultroid_cmd + + +@ultroid_cmd( + pattern="(un|)lock( (.*)|$)", admins_only=True, manager=True, require="change_info" +) +async def un_lock(e): + mat = e.pattern_match.group(2).strip() + if not mat: + return await e.eor("`Give some Proper Input..`", time=5) + lock = e.pattern_match.group(1) == "" + ml = lock_unlock(mat, lock) + if not ml: + return await e.eor("`Incorrect Input`", time=5) + msg = "Locked" if lock else "Unlocked" + try: + await e.client(EditChatDefaultBannedRightsRequest(e.chat_id, ml)) + except Exception as er: + return await e.eor(str(er)) + await e.eor(f"**{msg}** - `{mat}` ! ") diff --git a/plugins/logo.py b/plugins/logo.py new file mode 100644 index 0000000000000000000000000000000000000000..9635ef96b5852bd421cbc7d5bdbdb4ee49093a52 --- /dev/null +++ b/plugins/logo.py @@ -0,0 +1,101 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}logo ` + Generate a logo of the given Text + Or Reply To image , to write ur text on it. + Or Reply To Font File, To write with that font. + +""" +import glob +import os +import random + +from telethon.tl.types import InputMessagesFilterPhotos + +try: + from PIL import Image +except ImportError: + Image = None +from pyUltroid.fns.misc import unsplashsearch +from pyUltroid.fns.tools import LogoHelper + +from . import OWNER_ID, OWNER_NAME, download_file, get_string, mediainfo, ultroid_cmd + + +@ultroid_cmd(pattern="logo( (.*)|$)") +async def logo_gen(event): + xx = await event.eor(get_string("com_1")) + name = event.pattern_match.group(1).strip() + if not name: + return await xx.eor("`Give a name too!`", time=5) + bg_, font_ = None, None + if event.reply_to_msg_id: + temp = await event.get_reply_message() + if temp.media: + if hasattr(temp.media, "document") and ( + ("font" in temp.file.mime_type) + or (".ttf" in temp.file.name) + or (".otf" in temp.file.name) + ): + font_ = await temp.download_media("resources/fonts/") + elif "pic" in mediainfo(temp.media): + bg_ = await temp.download_media() + if not bg_: + SRCH = [ + "background", + "neon", + "anime", + "art", + "bridges", + "streets", + "computer", + "cyberpunk", + "nature", + "abstract", + "exoplanet", + "magic", + "3d render", + ] + res = await unsplashsearch(random.choice(SRCH), limit=1) + bg_, _ = await download_file(res[0], "resources/downloads/logo.png") + newimg = "resources/downloads/unsplash-temp.jpg" + img_ = Image.open(bg_) + img_.save(newimg) + os.remove(bg_) + bg_ = newimg + + if not font_: + fpath_ = glob.glob("resources/fonts/*") + font_ = random.choice(fpath_) + if len(name) <= 8: + strke = 10 + elif len(name) >= 9: + strke = 5 + else: + strke = 20 + name = LogoHelper.make_logo( + bg_, + name, + font_, + fill="white", + stroke_width=strke, + stroke_fill="black", + ) + await xx.edit("`Done!`") + await event.client.send_file( + event.chat_id, + file=name, + caption=f"Logo by [{OWNER_NAME}](tg://user?id={OWNER_ID})", + force_document=True, + ) + os.remove(name) + await xx.delete() + if os.path.exists(bg_): + os.remove(bg_) diff --git a/plugins/mediatools.py b/plugins/mediatools.py new file mode 100644 index 0000000000000000000000000000000000000000..faccbb088d09beca8f4ebf45d2cc593ab3a434df --- /dev/null +++ b/plugins/mediatools.py @@ -0,0 +1,147 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}mediainfo //` + To get info about it. + +• `{i}rotate ` + Rotate any video/photo/media.. + Note : for video it should be angle of 90's +""" +import os +import time +from datetime import datetime as dt + +from pyUltroid.fns.misc import rotate_image +from pyUltroid.fns.tools import make_html_telegraph + +from . import ( + LOGS, + Telegraph, + bash, + downloader, + get_string, + upload_file, + is_url_ok, + mediainfo, + ultroid_cmd, +) + +try: + import cv2 +except ImportError: + LOGS.info("WARNING: 'cv2' not found!") + cv2 = None + + +@ultroid_cmd(pattern="mediainfo( (.*)|$)") +async def mi(e): + r = await e.get_reply_message() + match = e.pattern_match.group(1).strip() + taime = time.time() + extra = "" + if r and r.media: + xx = mediainfo(r.media) + murl = r.media.stringify() + url = await make_html_telegraph("Mediainfo", f"
{murl}
") + extra = f"**[{xx}]({url})**\n\n" + e = await e.eor(f"{extra}`Loading More...`", link_preview=False) + + if hasattr(r.media, "document"): + file = r.media.document + mime_type = file.mime_type + filename = r.file.name + if not filename: + if "audio" in mime_type: + filename = "audio_" + dt.now().isoformat("_", "seconds") + ".ogg" + elif "video" in mime_type: + filename = "video_" + dt.now().isoformat("_", "seconds") + ".mp4" + dl = await downloader( + f"resources/downloads/{filename}", + file, + e, + taime, + f"{extra}`Loading More...`", + ) + + naam = dl.name + else: + naam = await r.download_media() + elif match and ( + os.path.isfile(match) + or (match.startswith("https://") and (await is_url_ok(match))) + ): + naam, xx = match, "file" + else: + return await e.eor(get_string("cvt_3"), time=5) + out, er = await bash(f"mediainfo '{naam}'") + if er: + LOGS.info(er) + out = extra or str(er) + return await e.edit(out, link_preview=False) + makehtml = "" + if naam.endswith((".jpg", ".png")): + if os.path.exists(naam): + med = upload_file(naam) + else: + med = match + makehtml += f"
" + for line in out.split("\n"): + line = line.strip() + if not line: + makehtml += "
" + elif ":" not in line: + makehtml += f"

{line}

" + else: + makehtml += f"

{line}

" + try: + urll = await make_html_telegraph("Mediainfo", makehtml) + except Exception as er: + LOGS.exception(er) + return + await e.eor(f"{extra}[{get_string('mdi_1')}]({urll})", link_preview=False) + if not match: + os.remove(naam) + + +@ultroid_cmd(pattern="rotate( (.*)|$)") +async def rotate_(ult): + match = ult.pattern_match.group(1).strip() + if not ult.is_reply: + return await ult.eor("`Reply to a media...`") + if match: + try: + match = int(match) + except ValueError: + match = None + if not match: + return await ult.eor("`Please provide a valid angle to rotate media..`") + reply = await ult.get_reply_message() + msg = await ult.eor(get_string("com_1")) + photo = reply.game.photo if reply.game else None + if reply.video: + media = await reply.download_media() + file = f"{media}.mp4" + await bash( + f'ffmpeg -i "{media}" -c copy -metadata:s:v:0 rotate={match} "{file}" -y' + ) + elif photo or reply.photo or reply.sticker: + media = await ult.client.download_media(photo or reply) + img = cv2.imread(media) + new_ = rotate_image(img, match) + file = "ult.png" + cv2.imwrite(file, new_) + else: + return await msg.edit("`Unsupported Media..\nReply to Photo/Video`") + if os.path.exists(file): + await ult.client.send_file( + ult.chat_id, file=file, video_note=bool(reply.video_note), reply_to=reply.id + ) + os.remove(media) + await msg.try_delete() diff --git a/plugins/misc.py b/plugins/misc.py new file mode 100644 index 0000000000000000000000000000000000000000..e82b6702e97262c19c7e95bfc2055c0dcc1e5d19 --- /dev/null +++ b/plugins/misc.py @@ -0,0 +1,140 @@ +# Ultroid - UserBot +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}eod` + `Get Event of the Today` + +• `{i}pntrst ` + Download and send pinterest pins + +• `{i}gadget ` + Gadget Search from Telegram. + +• `{i}randomuser` + Generate details about a random user. + +• `{i}ascii ` + Convert replied image into html. +""" + +import os +from datetime import datetime as dt + +from bs4 import BeautifulSoup as bs + +try: + from htmlwebshot import WebShot +except ImportError: + WebShot = None +try: + from img2html.converter import Img2HTMLConverter +except ImportError: + Img2HTMLConverter = None + +from . import async_searcher, get_random_user_data, get_string, re, ultroid_cmd + + +@ultroid_cmd(pattern="eod$") +async def diela(e): + m = await e.eor(get_string("com_1")) + li = "https://daysoftheyear.com" + te = "🎊 **Events of the Day**\n\n" + da = dt.now() + month = da.strftime("%b") + li += f"/days/{month}/" + da.strftime("%F").split("-")[2] + ct = await async_searcher(li, re_content=True) + bt = bs(ct, "html.parser", from_encoding="utf-8") + ml = bt.find_all("a", "js-link-target", href=re.compile("daysoftheyear.com/days")) + for eve in ml[:5]: + te += f'• [{eve.text}]({eve["href"]})\n' + await m.edit(te, link_preview=False) + + +@ultroid_cmd( + pattern="pntrst( (.*)|$)", +) +async def pinterest(e): + m = e.pattern_match.group(1).strip() + if not m: + return await e.eor("`Give pinterest link.`", time=3) + soup = await async_searcher( + "https://www.expertstool.com/download-pinterest-video/", + data={"url": m}, + post=True, + ) + try: + _soup = bs(soup, "html.parser").find("table").tbody.find_all("tr") + except BaseException: + return await e.eor("`Wrong link or private pin.`", time=5) + file = _soup[1] if len(_soup) > 1 else _soup[0] + file = file.td.a["href"] + await e.client.send_file(e.chat_id, file, caption=f"Pin:- {m}") + + +@ultroid_cmd(pattern="gadget( (.*)|$)") +async def mobs(e): + mat = e.pattern_match.group(1).strip() + if not mat: + await e.eor("Please Give a Mobile Name to look for.") + query = mat.replace(" ", "%20") + jwala = f"https://gadgets.ndtv.com/search?searchtext={query}" + c = await async_searcher(jwala) + b = bs(c, "html.parser", from_encoding="utf-8") + co = b.find_all("div", "rvw-imgbox") + if not co: + return await e.eor("No Results Found!") + bt = await e.eor(get_string("com_1")) + out = "**📱 Mobile / Gadgets Search**\n\n" + li = co[0].find("a") + imu, title = None, li.find("img")["title"] + cont = await async_searcher(li["href"]) + nu = bs(cont, "html.parser", from_encoding="utf-8") + req = nu.find_all("div", "_pdsd") + imu = nu.find_all( + "img", src=re.compile("https://i.gadgets360cdn.com/products/large/") + ) + if imu: + imu = imu[0]["src"].split("?")[0] + "?downsize=*:420&output-quality=80" + out += f"☑️ **[{title}]({li['href']})**\n\n" + for fp in req: + ty = fp.findNext() + out += f"- **{ty.text}** - `{ty.findNext().text}`\n" + out += "_" + if imu == []: + imu = None + await e.reply(out, file=imu, link_preview=False) + await bt.delete() + + +@ultroid_cmd(pattern="randomuser") +async def _gen_data(event): + x = await event.eor(get_string("com_1")) + msg, pic = await get_random_user_data() + await event.reply(file=pic, message=msg) + await x.delete() + + +@ultroid_cmd( + pattern="ascii( (.*)|$)", +) +async def _(e): + if not Img2HTMLConverter: + return await e.eor("'img2html-converter' not installed!") + if not e.reply_to_msg_id: + return await e.eor(get_string("ascii_1")) + m = await e.eor(get_string("ascii_2")) + img = await (await e.get_reply_message()).download_media() + char = e.pattern_match.group(1).strip() or "■" + converter = Img2HTMLConverter(char=char) + html = converter.convert(img) + shot = WebShot(quality=85) + pic = await shot.create_pic_async(html=html) + await m.delete() + await e.reply(file=pic) + os.remove(pic) + os.remove(img) diff --git a/plugins/mute.py b/plugins/mute.py new file mode 100644 index 0000000000000000000000000000000000000000..84b11f3afd6694bbda02f1208490a9767a437074 --- /dev/null +++ b/plugins/mute.py @@ -0,0 +1,210 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}mute ` + Mute user in current chat. + +• `{i}unmute ` + Unmute user in current chat. + +• `{i}dmute ` + Mute user in current chat by deleting msgs. + +• `{i}undmute ` + Unmute dmuted user in current chat. + +• `{i}tmute
{title}\n" + await uu.edit(a, parse_mode="html") + + except Exception as e: + await uu.edit(f"`Error: {str(e)}`\nTry again later.") + +@ultroid_cmd(pattern="wall( (.*)|$)") +async def wall(event): + inp = event.pattern_match.group(1).strip() + if not inp: + return await event.eor("`Give me something to search..`") + nn = await event.eor(get_string("com_1")) + query = f"hd {inp}" + images = await get_google_images(query) + for z in range(5): + await event.client.send_file(event.chat_id, file=images[z]["original"]) + await nn.delete() + + +@ultroid_cmd(pattern="q( (.*)|$)", manager=True, allow_pm=True) +async def quott_(event): + match = event.pattern_match.group(1).strip() + if not event.is_reply: + return await event.eor("`Reply to Message..`") + msg = await event.eor(get_string("com_1")) + reply = await event.get_reply_message() + replied_to, reply_ = None, None + if match: + spli_ = match.split(maxsplit=1) + if (spli_[0] in ["r", "reply"]) or ( + spli_[0].isdigit() and int(spli_[0]) in range(1, 21) + ): + if spli_[0].isdigit(): + if not event.client._bot: + reply_ = await event.client.get_messages( + event.chat_id, + min_id=event.reply_to_msg_id - 1, + reverse=True, + limit=int(spli_[0]), + ) + else: + id_ = reply.id + reply_ = [] + for msg_ in range(id_, id_ + int(spli_[0])): + msh = await event.client.get_messages(event.chat_id, ids=msg_) + if msh: + reply_.append(msh) + else: + replied_to = await reply.get_reply_message() + try: + match = spli_[1] + except IndexError: + match = None + user = None + if not reply_: + reply_ = reply + if match: + match = match.split(maxsplit=1) + if match: + if match[0].startswith("@") or match[0].isdigit(): + try: + match_ = await event.client.parse_id(match[0]) + user = await event.client.get_entity(match_) + except ValueError: + pass + match = match[1] if len(match) == 2 else None + else: + match = match[0] + if match == "random": + match = choice(all_col) + try: + file = await quotly.create_quotly( + reply_, bg=match, reply=replied_to, sender=user + ) + except Exception as er: + return await msg.edit(str(er)) + message = await reply.reply("Quotly by Ultroid", file=file) + os.remove(file) + await msg.delete() + return message diff --git a/plugins/stickertools.py b/plugins/stickertools.py new file mode 100644 index 0000000000000000000000000000000000000000..196a32b62343d5bd652eac180d8305fee5e759c2 --- /dev/null +++ b/plugins/stickertools.py @@ -0,0 +1,531 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +❍ Commands Available - + +• `{i}destroy ` + To destroy the sticker. + +• `{i}tiny ` + To create Tiny stickers. + +• `{i}kang ` + Kang the sticker (add to your pack). + +• `{i}packkang ` + Kang the Complete sticker set (with custom name). + +• `{i}round ` + To extract round sticker. +""" +import glob +import io +import os +import random +from os import remove + +try: + import cv2 +except ImportError: + cv2 = None +try: + import numpy as np +except ImportError: + np = None +try: + from PIL import Image, ImageDraw +except ImportError: + pass + +from telethon.errors import PeerIdInvalidError, YouBlockedUserError +from telethon.tl.functions.messages import UploadMediaRequest +from telethon.tl.types import ( + DocumentAttributeFilename, + DocumentAttributeSticker, + InputPeerSelf, +) +from telethon.utils import get_input_document + +from . import ( + KANGING_STR, + LOGS, + asst, + async_searcher, + bash, + con, + functions, + get_string, + inline_mention, + mediainfo, + ultroid_cmd, + quotly, + types, + udB, +) + + +@ultroid_cmd(pattern="packkang") +async def pack_kangish(_): + _e = await _.get_reply_message() + local = None + try: + cmdtext = _.text.split(maxsplit=1)[1] + except IndexError: + cmdtext = None + if cmdtext and os.path.isdir(cmdtext): + local = True + elif not (_e and _e.sticker and _e.file.mime_type == "image/webp"): + return await _.eor(get_string("sts_4")) + msg = await _.eor(get_string("com_1")) + _packname = cmdtext or f"Ultroid Kang Pack By {_.sender_id}" + typee = None + if not local: + _id = _e.media.document.attributes[1].stickerset.id + _hash = _e.media.document.attributes[1].stickerset.access_hash + _get_stiks = await _.client( + functions.messages.GetStickerSetRequest( + stickerset=types.InputStickerSetID(id=_id, access_hash=_hash), hash=0 + ) + ) + docs = _get_stiks.documents + else: + docs = [] + files = glob.glob(f"{cmdtext}/*") + exte = files[-1] + if exte.endswith(".tgs"): + typee = "anim" + elif exte.endswith(".webm"): + typee = "vid" + count = 0 + for file in files: + if file.endswith((".tgs", ".webm")): + count += 1 + upl = await asst.upload_file(file) + docs.append(await asst(UploadMediaRequest(InputPeerSelf(), upl))) + if count % 5 == 0: + await msg.edit(f"`Uploaded {count} files.`") + + stiks = [] + for i in docs: + x = get_input_document(i) + stiks.append( + types.InputStickerSetItem( + document=x, + emoji=( + random.choice(["😐", "👍", "😂"]) + if local + else (i.attributes[1]).alt + ), + ) + ) + try: + short_name = "ult_" + _packname.replace(" ", "_") + str(_.id) + _r_e_s = await asst( + functions.stickers.CreateStickerSetRequest( + user_id=_.sender_id, + title=_packname, + short_name=f"{short_name}_by_{asst.me.username}", + animated=typee == "anim", + videos=typee == "vid", + stickers=stiks, + ) + ) + except PeerIdInvalidError: + return await msg.eor( + f"Hey {inline_mention(_.sender)} send `/start` to @{asst.me.username} and later try this command again.." + ) + except BaseException as er: + LOGS.exception(er) + return await msg.eor(str(er)) + await msg.eor( + get_string("sts_5").format(f"https://t.me/addstickers/{_r_e_s.set.short_name}"), + ) + + +@ultroid_cmd( + pattern="kang", +) +async def hehe(args): + ultroid_bot = args.client + xx = await args.eor(get_string("com_1")) + user = ultroid_bot.me + username = user.username + username = f"@{username}" if username else user.first_name + message = await args.get_reply_message() + photo = None + is_anim, is_vid = False, False + emoji = None + if not message: + return await xx.eor(get_string("sts_6")) + if message.photo: + photo = io.BytesIO() + photo = await ultroid_bot.download_media(message.photo, photo) + elif message.file and "image" in message.file.mime_type.split("/"): + photo = io.BytesIO() + await ultroid_bot.download_file(message.media.document, photo) + if ( + DocumentAttributeFilename(file_name="sticker.webp") + in message.media.document.attributes + ): + emoji = message.media.document.attributes[1].alt + + elif message.file and "video" in message.file.mime_type.split("/"): + xy = await message.download_media() + if (message.file.duration or 0) <= 10: + is_vid = True + photo = await con.create_webm(xy) + else: + y = cv2.VideoCapture(xy) + heh, lol = y.read() + cv2.imwrite("ult.webp", lol) + photo = "ult.webp" + elif message.file and "tgsticker" in message.file.mime_type: + await ultroid_bot.download_file( + message.media.document, + "AnimatedSticker.tgs", + ) + attributes = message.media.document.attributes + for attribute in attributes: + if isinstance(attribute, DocumentAttributeSticker): + emoji = attribute.alt + is_anim = True + photo = 1 + elif message.message: + photo = await quotly.create_quotly(message) + else: + return await xx.edit(get_string("com_4")) + if not udB.get_key("language") or udB.get_key("language") == "en": + ra = random.choice(KANGING_STR) + else: + ra = get_string("sts_11") + await xx.edit(f"`{ra}`") + if photo: + splat = args.text.split() + pack = 1 + if not emoji: + emoji = "🏵" + if len(splat) == 3: + pack = splat[2] # User sent ultroid_both + emoji = splat[1] + elif len(splat) == 2: + if splat[1].isnumeric(): + pack = int(splat[1]) + else: + emoji = splat[1] + + packname = f"ult_{user.id}_{pack}" + packnick = f"{username}'s Pack {pack}" + cmd = "/newpack" + file = io.BytesIO() + + if is_vid: + packname += "_vid" + packnick += " (Video)" + cmd = "/newvideo" + elif is_anim: + packname += "_anim" + packnick += " (Animated)" + cmd = "/newanimated" + else: + image = con.resize_photo_sticker(photo) + file.name = "sticker.png" + image.save(file, "PNG") + + response = await async_searcher(f"http://t.me/addstickers/{packname}") + htmlstr = response.split("\n") + + if ( + " A Telegram user has created the Sticker Set." + not in htmlstr + ): + async with ultroid_bot.conversation("@Stickers") as conv: + try: + await conv.send_message("/addsticker") + except YouBlockedUserError: + LOGS.info("Unblocking @Stickers for kang...") + await ultroid_bot(functions.contacts.UnblockRequest("stickers")) + await conv.send_message("/addsticker") + await conv.get_response() + await conv.send_message(packname) + x = await conv.get_response() + if x.text.startswith("Alright! Now send me the video sticker."): + await conv.send_file(photo, force_document=True) + x = await conv.get_response() + t = "50" if (is_anim or is_vid) else "120" + while t in x.message: + pack += 1 + packname = f"ult_{user.id}_{pack}" + packnick = f"{username}'s Pack {pack}" + if is_anim: + packname += "_anim" + packnick += " (Animated)" + elif is_vid: + packnick += " (Video)" + packname += "_vid" + await xx.edit(get_string("sts_13").format(pack)) + await conv.send_message("/addsticker") + await conv.get_response() + await conv.send_message(packname) + x = await conv.get_response() + if x.text.startswith("Alright! Now send me the video sticker."): + await conv.send_file(photo, force_document=True) + x = await conv.get_response() + if x.text in ["Invalid pack selected.", "Invalid set selected."]: + await conv.send_message(cmd) + await conv.get_response() + await conv.send_message(packnick) + await conv.get_response() + if is_anim: + await conv.send_file("AnimatedSticker.tgs") + remove("AnimatedSticker.tgs") + else: + if is_vid: + file = photo + else: + file.seek(0) + await conv.send_file(file, force_document=True) + await conv.get_response() + await conv.send_message(emoji) + await conv.get_response() + await conv.send_message("/publish") + if is_anim: + await conv.get_response() + await conv.send_message(f"<{packnick}>") + await conv.get_response() + await conv.send_message("/skip") + await conv.get_response() + await conv.send_message(packname) + await conv.get_response() + await xx.edit( + get_string("sts_7").format(packname), + parse_mode="md", + ) + return + if is_anim: + await conv.send_file("AnimatedSticker.tgs") + remove("AnimatedSticker.tgs") + elif "send me an emoji" not in x.message: + if is_vid: + file = photo + else: + file.seek(0) + await conv.send_file(file, force_document=True) + rsp = await conv.get_response() + if "Sorry, the file type is invalid." in rsp.text: + await xx.edit( + get_string("sts_8"), + ) + return + await conv.send_message(emoji) + await conv.get_response() + await conv.send_message("/done") + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + else: + await xx.edit("`Brewing a new Pack...`") + async with ultroid_bot.conversation("Stickers") as conv: + await conv.send_message(cmd) + await conv.get_response() + await conv.send_message(packnick) + await conv.get_response() + if is_anim: + await conv.send_file("AnimatedSticker.tgs") + remove("AnimatedSticker.tgs") + else: + if is_vid: + file = photo + else: + file.seek(0) + await conv.send_file(file, force_document=True) + rsp = await conv.get_response() + if "Sorry, the file type is invalid." in rsp.text: + await xx.edit( + get_string("sts_8"), + ) + return + await conv.send_message(emoji) + await conv.get_response() + await conv.send_message("/publish") + if is_anim: + await conv.get_response() + await conv.send_message(f"<{packnick}>") + + await conv.get_response() + await conv.send_message("/skip") + await conv.get_response() + await conv.send_message(packname) + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await xx.edit( + get_string("sts_12").format(emoji, packname), + parse_mode="md", + ) + try: + os.remove(photo) + except BaseException: + pass + try: + os.remove("AnimatedSticker.tgs") + except BaseException: + pass + try: + os.remove("ult.webp") + except BaseException: + pass + + +@ultroid_cmd( + pattern="round$", +) +async def ultdround(event): + ureply = await event.get_reply_message() + xx = await event.eor(get_string("com_1")) + if not (ureply and (ureply.media)): + await xx.edit(get_string("sts_10")) + return + ultt = await ureply.download_media() + file = await con.convert( + ultt, + convert_to="png", + allowed_formats=["jpg", "jpeg", "png"], + outname="round", + remove_old=True, + ) + img = Image.open(file).convert("RGB") + npImage = np.array(img) + h, w = img.size + alpha = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(alpha) + draw.pieslice([0, 0, h, w], 0, 360, fill=255) + npAlpha = np.array(alpha) + npImage = np.dstack((npImage, npAlpha)) + Image.fromarray(npImage).save("ult.webp") + await event.client.send_file( + event.chat_id, + "ult.webp", + force_document=False, + reply_to=event.reply_to_msg_id, + ) + await xx.delete() + os.remove(file) + os.remove("ult.webp") + + +@ultroid_cmd( + pattern="destroy$", +) +async def ultdestroy(event): + ult = await event.get_reply_message() + if not (ult and ult.media and "animated" in mediainfo(ult.media)): + return await event.eor(get_string("sts_2")) + await event.client.download_media(ult, "ultroid.tgs") + xx = await event.eor(get_string("com_1")) + await bash("lottie_convert.py ultroid.tgs json.json") + with open("json.json") as json: + jsn = json.read() + jsn = ( + jsn.replace("[100]", "[200]") + .replace("[10]", "[40]") + .replace("[-1]", "[-10]") + .replace("[0]", "[15]") + .replace("[1]", "[20]") + .replace("[2]", "[17]") + .replace("[3]", "[40]") + .replace("[4]", "[37]") + .replace("[5]", "[60]") + .replace("[6]", "[70]") + .replace("[7]", "[40]") + .replace("[8]", "[37]") + .replace("[9]", "[110]") + ) + open("json.json", "w").write(jsn) + file = await con.animated_sticker("json.json", "ultroid.tgs") + if file: + await event.client.send_file( + event.chat_id, + file="ultroid.tgs", + force_document=False, + reply_to=event.reply_to_msg_id, + ) + await xx.delete() + os.remove("json.json") + + +@ultroid_cmd( + pattern="tiny$", +) +async def ultiny(event): + reply = await event.get_reply_message() + if not (reply and (reply.media)): + await event.eor(get_string("sts_10")) + return + xx = await event.eor(get_string("com_1")) + ik = await reply.download_media() + im1 = Image.open("resources/extras/ultroid_blank.png") + if ik.endswith(".tgs"): + await con.animated_sticker(ik, "json.json") + with open("json.json") as json: + jsn = json.read() + jsn = jsn.replace("512", "2000") + open("json.json", "w").write(jsn) + await con.animated_sticker("json.json", "ult.tgs") + file = "ult.tgs" + os.remove("json.json") + elif ik.endswith((".gif", ".webm", ".mp4")): + iik = cv2.VideoCapture(ik) + dani, busy = iik.read() + cv2.imwrite("i.png", busy) + fil = "i.png" + im = Image.open(fil) + z, d = im.size + if z == d: + xxx, yyy = 200, 200 + else: + t = z + d + a = z / t + b = d / t + aa = (a * 100) - 50 + bb = (b * 100) - 50 + xxx = 200 + 5 * aa + yyy = 200 + 5 * bb + k = im.resize((int(xxx), int(yyy))) + k.save("k.png", format="PNG", optimize=True) + im2 = Image.open("k.png") + back_im = im1.copy() + back_im.paste(im2, (150, 0)) + back_im.save("o.webp", "WEBP", quality=95) + file = "o.webp" + os.remove(fil) + os.remove("k.png") + else: + im = Image.open(ik) + z, d = im.size + if z == d: + xxx, yyy = 200, 200 + else: + t = z + d + a = z / t + b = d / t + aa = (a * 100) - 50 + bb = (b * 100) - 50 + xxx = 200 + 5 * aa + yyy = 200 + 5 * bb + k = im.resize((int(xxx), int(yyy))) + k.save("k.png", format="PNG", optimize=True) + im2 = Image.open("k.png") + back_im = im1.copy() + back_im.paste(im2, (150, 0)) + back_im.save("o.webp", "WEBP", quality=95) + file = "o.webp" + os.remove("k.png") + if os.path.exists(file): + await event.client.send_file( + event.chat_id, file, reply_to=event.reply_to_msg_id + ) + os.remove(file) + await xx.delete() + os.remove(ik) diff --git a/plugins/stories.py b/plugins/stories.py new file mode 100644 index 0000000000000000000000000000000000000000..3cecb19e1446c6a6fddb6a29a5143e0bc81a40f3 --- /dev/null +++ b/plugins/stories.py @@ -0,0 +1,150 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2023 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}setstory ` + Set replied media as your story. + +• `{i}storydl ` + Download and upload user stories or specific story from link! +""" + +import os +import re +from contextlib import suppress +from . import ultroid_cmd, get_string, LOGS + +from telethon import TelegramClient +from telethon.tl.functions.channels import GetFullChannelRequest +from telethon.tl.types import User, UserFull, InputPeerSelf, InputPrivacyValueAllowAll, Channel, InputUserSelf +from telethon.tl.functions.stories import SendStoryRequest, GetStoriesByIDRequest +from telethon.tl.functions.users import GetFullUserRequest +from telethon.events import NewMessage + + +@ultroid_cmd("setstory") +async def setStory(event: NewMessage.Event): + reply = await event.get_reply_message() + if not (reply and (reply.photo or reply.video)): + await event.eor("Please reply to a photo or video!", time=5) + return + msg = await event.eor(get_string("com_1")) + try: + await event.client( + SendStoryRequest( + InputPeerSelf(), + reply.media, + privacy_rules=[ + InputPrivacyValueAllowAll() + ] + ) + ) + await msg.eor("🔥 **Story is Live!**", time=5) + except Exception as er: + await msg.edit(f"__ERROR: {er}__") + LOGS.exception(er) + + +@ultroid_cmd("storydl") +async def downloadUserStories(event: NewMessage.Event): + replied = await event.get_reply_message() + message = await event.eor(get_string("com_1")) + + try: + text_input = event.text.split(maxsplit=1)[1] + # Check if input is a Telegram story link + story_link_pattern = r"https?://t\.me/([^/]+)/s/(\d+)" + match = re.match(story_link_pattern, text_input) + + if match: + # Extract username and story ID from link + username = match.group(1) + story_id = int(match.group(2)) + + try: + # Get the entity for the username + entity = await event.client.get_entity(username) + + # Using GetStoriesByIDRequest to fetch the specific story + stories_response = await event.client( + GetStoriesByIDRequest( + entity.id, + id=[story_id] + ) + ) + print(stories_response) + + if not stories_response.stories: + return await message.eor("ERROR: Story not found or expired!") + + # Download and upload the story + for story in stories_response.stories: + client: TelegramClient = event.client + file = await client.download_media(story.media) + caption = story.caption if hasattr(story, 'caption') else "" + await message.reply( + caption, + file=file + ) + os.remove(file) + + return await message.eor("**Uploaded Story!**", time=5) + + except Exception as er: + await message.eor(f"ERROR while fetching story: __{er}__") + LOGS.exception(er) + return + + # If not a story link, proceed with the original functionality + username = text_input + + except IndexError as er: + LOGS.exception + if replied and isinstance(replied.sender, User): + username = replied.sender_id + else: + return await message.eor( + "Please reply to a user, provide username or story link!" + ) + + with suppress(ValueError): + username = int(username) + + stories = None + + try: + entity = await event.client.get_entity(username) + if isinstance(entity, Channel): + full_user: UserFull = ( + await event.client(GetFullChannelRequest(entity.id)) + ).full_channel + stories = full_user.stories + else: + full_user: UserFull = ( + await event.client(GetFullUserRequest(id=username)) + ).full_user + stories = full_user.stories + except Exception as er: + await message.eor(f"ERROR: __{er}__") + return + + if not (stories and stories.stories): + await message.eor("ERROR: Stories not found!") + return + for story in stories.stories[:5]: + client: TelegramClient = event.client + file = await client.download_media(story.media) + caption = story.caption if hasattr(story, 'caption') else "" + await message.reply( + caption, + file=file + ) + os.remove(file) + + await message.eor("**Uploaded Stories!**", time=5) \ No newline at end of file diff --git a/plugins/sudo.py b/plugins/sudo.py new file mode 100644 index 0000000000000000000000000000000000000000..b8e706c43eb39946d29dd6e76da7586c871c9003 --- /dev/null +++ b/plugins/sudo.py @@ -0,0 +1,124 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}addsudo` + Add Sudo Users by replying to user or using separated userid(s) + +• `{i}delsudo` + Remove Sudo Users by replying to user or using separated userid(s) + +• `{i}listsudo` + List all sudo users. +""" + +from telethon.tl.types import User + +from pyUltroid._misc import sudoers + +from . import get_string, inline_mention, udB, ultroid_bot, ultroid_cmd + + +@ultroid_cmd(pattern="addsudo( (.*)|$)", fullsudo=True) +async def _(ult): + inputs = ult.pattern_match.group(1).strip() + if ult.reply_to_msg_id: + replied_to = await ult.get_reply_message() + id = replied_to.sender_id + name = await replied_to.get_sender() + elif inputs: + try: + id = await ult.client.parse_id(inputs) + except ValueError: + try: + id = int(inputs) + except ValueError: + id = inputs + try: + name = await ult.client.get_entity(int(id)) + except BaseException: + name = None + elif ult.is_private: + id = ult.chat_id + name = await ult.get_chat() + else: + return await ult.eor(get_string("sudo_1"), time=5) + if name and isinstance(name, User) and (name.bot or name.verified): + return await ult.eor(get_string("sudo_4")) + name = inline_mention(name) if name else f"`{id}`" + if id == ultroid_bot.uid: + mmm = get_string("sudo_2") + elif id in sudoers(): + mmm = f"{name} `is already a SUDO User ...`" + else: + udB.set_key("SUDO", "True") + key = sudoers() + key.append(id) + udB.set_key("SUDOS", key) + mmm = f"**Added** {name} **as SUDO User**" + await ult.eor(mmm, time=5) + + +@ultroid_cmd(pattern="delsudo( (.*)|$)", fullsudo=True) +async def _(ult): + inputs = ult.pattern_match.group(1).strip() + if ult.reply_to_msg_id: + replied_to = await ult.get_reply_message() + id = replied_to.sender_id + name = await replied_to.get_sender() + elif inputs: + try: + id = await ult.client.parse_id(inputs) + except ValueError: + try: + id = int(inputs) + except ValueError: + id = inputs + try: + name = await ult.client.get_entity(int(id)) + except BaseException: + name = None + elif ult.is_private: + id = ult.chat_id + name = await ult.get_chat() + else: + return await ult.eor(get_string("sudo_1"), time=5) + name = inline_mention(name) if name else f"`{id}`" + if id not in sudoers(): + mmm = f"{name} `wasn't a SUDO User ...`" + else: + key = sudoers() + key.remove(id) + udB.set_key("SUDOS", key) + mmm = f"**Removed** {name} **from SUDO User(s)**" + await ult.eor(mmm, time=5) + + +@ultroid_cmd( + pattern="listsudo$", +) +async def _(ult): + sudos = sudoers() + if not sudos: + return await ult.eor(get_string("sudo_3"), time=5) + msg = "" + for i in sudos: + try: + name = await ult.client.get_entity(int(i)) + except BaseException: + name = None + if name: + msg += f"• {inline_mention(name)} ( `{i}` )\n" + else: + msg += f"• `{i}` -> Invalid User\n" + m = udB.get_key("SUDO") or True + if not m: + m = "[False](https://graph.org/Ultroid-04-06)" + return await ult.eor( + f"**SUDO MODE : {m}\n\nList of SUDO Users :**\n{msg}", link_preview=False + ) diff --git a/plugins/tag.py b/plugins/tag.py new file mode 100644 index 0000000000000000000000000000000000000000..03dcba65b780894810809c983ba25522fd55dcf5 --- /dev/null +++ b/plugins/tag.py @@ -0,0 +1,76 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}tagall` + Tag Top 100 Members of chat. + +• `{i}tagadmins` + Tag Admins of that chat. + +• `{i}tagowner` + Tag Owner of that chat + +• `{i}tagbots` + Tag Bots of that chat. + +• `{i}tagrec` + Tag recently Active Members. + +• `{i}tagon` + Tag online Members(work only if privacy off). + +• `{i}tagoff` + Tag Offline Members(work only if privacy off). +""" + +from telethon.tl.types import ChannelParticipantAdmin as admin +from telethon.tl.types import ChannelParticipantCreator as owner +from telethon.tl.types import UserStatusOffline as off +from telethon.tl.types import UserStatusOnline as onn +from telethon.tl.types import UserStatusRecently as rec + +from . import inline_mention, ultroid_cmd + + +@ultroid_cmd( + pattern="tag(on|off|all|bots|rec|admins|owner)( (.*)|$)", + groups_only=True, +) +async def _(e): + okk = e.text + lll = e.pattern_match.group(2) + o = 0 + nn = 0 + rece = 0 + xx = f"{lll}" if lll else "" + lili = await e.client.get_participants(e.chat_id, limit=99) + for bb in lili: + x = bb.status + y = bb.participant + if isinstance(x, onn): + o += 1 + if "on" in okk: + xx += f"\n{inline_mention(bb)}" + elif isinstance(x, off): + nn += 1 + if "off" in okk and not bb.bot and not bb.deleted: + xx += f"\n{inline_mention(bb)}" + elif isinstance(x, rec): + rece += 1 + if "rec" in okk and not bb.bot and not bb.deleted: + xx += f"\n{inline_mention(bb)}" + if isinstance(y, owner): + xx += f"\n꧁{inline_mention(bb)}꧂" + if isinstance(y, admin) and "admin" in okk and not bb.deleted: + xx += f"\n{inline_mention(bb)}" + if "all" in okk and not bb.bot and not bb.deleted: + xx += f"\n{inline_mention(bb)}" + if "bot" in okk and bb.bot: + xx += f"\n{inline_mention(bb)}" + await e.eor(xx) diff --git a/plugins/tools.py b/plugins/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..47d9f03d3cdc9114319666797356d8504983e273 --- /dev/null +++ b/plugins/tools.py @@ -0,0 +1,471 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}circle` + Reply to a audio song or gif to get video note. + +• `{i}ls` + Get all the Files inside a Directory. + +• `{i}bots` + Shows the number of bots in the current chat with their perma-link. + +• `{i}hl ` + Embeds the link with a whitespace as message. + +• `{i}id` + Reply a Sticker to Get Its Id + Reply a User to Get His Id + Without Replying You Will Get the Chat's Id + +• `{i}sg ` + Get His Name History of the replied user. + +• `{i}tr <(reply to) a message>` + Get translated message. + +• `{i}webshot ` + Get a screenshot of the webpage. +""" +import asyncio +import glob +import io +import os +import re +import secrets +from asyncio.exceptions import TimeoutError as AsyncTimeout + +try: + import cv2 +except ImportError: + cv2 = None + +try: + from playwright.async_api import async_playwright +except ImportError: + async_playwright = None +try: + from htmlwebshot import WebShot +except ImportError: + WebShot = None + +from telethon import functions, types +from telethon.errors.rpcerrorlist import MessageTooLongError, YouBlockedUserError +from telethon.tl.types import ( + ChannelParticipantAdmin, + ChannelParticipantsBots, + DocumentAttributeVideo, +) + +from pyUltroid.fns.tools import metadata, translate + +from . import ( + HNDLR, + LOGS, + ULTConfig, + async_searcher, + bash, + check_filename, + con, + download_file, + eor, + get_string, + ultroid_bot, +) +from . import humanbytes as hb +from . import inline_mention, is_url_ok, json_parser, mediainfo, ultroid_cmd + + +@ultroid_cmd(pattern="tr( (.*)|$)", manager=True) +async def _(event): + input = event.pattern_match.group(1).strip().split(maxsplit=1) + txt = input[1] if len(input) > 1 else None + if input: + input = input[0] + if txt: + text = txt + elif event.is_reply: + previous_message = await event.get_reply_message() + text = previous_message.message + else: + return await eor( + event, f"`{HNDLR}tr LanguageCode` as reply to a message", time=5 + ) + lan = input or "en" + try: + tt = translate(text, lang_tgt=lan) + output_str = f"**TRANSLATED** to {lan}\n{tt}" + await event.eor(output_str) + except Exception as exc: + LOGS.exception(exc) + await event.eor(str(exc), time=5) + + +@ultroid_cmd( + pattern="id( (.*)|$)", + manager=True, +) +async def _(event): + ult = event + match = event.pattern_match.group(1).strip() + if match: + try: + ids = await event.client.parse_id(match) + except Exception as er: + return await event.eor(str(er)) + return await event.eor( + f"**Chat ID:** `{event.chat_id}`\n**User ID:** `{ids}`" + ) + data = f"**Current Chat ID:** `{event.chat_id}`" + if event.reply_to_msg_id: + event = await event.get_reply_message() + data += f"\n**From User ID:** `{event.sender_id}`" + if event.media: + bot_api_file_id = event.file.id + data += f"\n**Bot API File ID:** `{bot_api_file_id}`" + data += f"\n**Msg ID:** `{event.id}`" + await ult.eor(data) + + +@ultroid_cmd(pattern="bots( (.*)|$)", groups_only=True, manager=True) +async def _(ult): + mentions = "• **Bots in this Chat**: \n" + if input_str := ult.pattern_match.group(1).strip(): + mentions = f"• **Bots in **{input_str}: \n" + try: + chat = await ult.client.parse_id(input_str) + except Exception as e: + return await ult.eor(str(e)) + else: + chat = ult.chat_id + try: + async for x in ult.client.iter_participants( + chat, + filter=ChannelParticipantsBots, + ): + if isinstance(x.participant, ChannelParticipantAdmin): + mentions += f"\n⚜️ {inline_mention(x)} `{x.id}`" + else: + mentions += f"\n• {inline_mention(x)} `{x.id}`" + except Exception as e: + mentions += f" {str(e)}" + "\n" + await ult.eor(mentions) + + +@ultroid_cmd( + pattern="hl( (.*)|$)", +) +async def _(ult): + input_ = ult.pattern_match.group(1).strip() + if not input_: + return await ult.eor("`Input some link`", time=5) + text = None + if len(input_.split()) > 1: + spli_ = input_.split() + input_ = spli_[0] + text = spli_[1] + if not text: + text = "ㅤㅤㅤㅤㅤㅤㅤ" + await ult.eor(f"[{text}]({input_})", link_preview=False) + + +@ultroid_cmd( + pattern="circle$", +) +async def _(e): + reply = await e.get_reply_message() + if not (reply and reply.media): + return await e.eor("`Reply to a gif or audio file only.`") + if "audio" in mediainfo(reply.media): + msg = await e.eor("`Downloading...`") + try: + bbbb = await reply.download_media(thumb=-1) + except TypeError: + bbbb = ULTConfig.thumb + im = cv2.imread(bbbb) + dsize = (512, 512) + output = cv2.resize(im, dsize, interpolation=cv2.INTER_AREA) + cv2.imwrite("img.jpg", output) + thumb = "img.jpg" + audio, _ = await e.client.fast_downloader(reply.document) + await msg.edit("`Creating video note...`") + await bash( + f'ffmpeg -i "{thumb}" -i "{audio.name}" -preset ultrafast -c:a libmp3lame -ab 64 circle.mp4 -y' + ) + await msg.edit("`Uploading...`") + data = await metadata("circle.mp4") + file, _ = await e.client.fast_uploader("circle.mp4", to_delete=True) + await e.client.send_file( + e.chat_id, + file, + thumb=thumb, + reply_to=reply, + attributes=[ + DocumentAttributeVideo( + duration=min(data["duration"], 60), + w=512, + h=512, + round_message=True, + ) + ], + ) + + await msg.delete() + [os.remove(k) for k in [audio.name, thumb]] + elif mediainfo(reply.media) == "gif" or mediainfo(reply.media).startswith("video"): + msg = await e.eor("**Creating video note**") + file = await reply.download_media("resources/downloads/") + if file.endswith(".webm"): + nfile = await con.ffmpeg_convert(file, "file.mp4") + os.remove(file) + file = nfile + if file: + await e.client.send_file( + e.chat_id, + file, + video_note=True, + thumb=ULTConfig.thumb, + reply_to=reply, + ) + os.remove(file) + await msg.delete() + + else: + await e.eor("`Reply to a gif or audio file only.`") + + +FilesEMOJI = { + "py": "🐍", + "json": "🔮", + ("sh", "bat"): "⌨️", + (".mkv", ".mp4", ".avi", ".gif", "webm"): "🎥", + (".mp3", ".ogg", ".m4a", ".opus"): "🔊", + (".jpg", ".jpeg", ".png", ".webp", ".ico"): "🖼", + (".txt", ".text", ".log"): "📄", + (".apk", ".xapk"): "📲", + (".pdf", ".epub"): "📗", + (".zip", ".rar"): "🗜", + (".exe", ".iso"): "⚙", +} + + +@ultroid_cmd( + pattern="ls( (.*)|$)", +) +async def _(e): + files = e.pattern_match.group(1).strip() + if not files: + files = "*" + elif files.endswith("/"): + files += "*" + elif "*" not in files: + files += "/*" + files = glob.glob(files) + if not files: + return await e.eor("`Directory Empty or Incorrect.`", time=5) + folders = [] + allfiles = [] + for file in sorted(files): + if os.path.isdir(file): + folders.append(f"📂 {file}") + else: + for ext in FilesEMOJI.keys(): + if file.endswith(ext): + allfiles.append(f"{FilesEMOJI[ext]} {file}") + break + else: + if "." in str(file)[1:]: + allfiles.append(f"🏷 {file}") + else: + allfiles.append(f"📒 {file}") + omk = [*sorted(folders), *sorted(allfiles)] + text = "" + fls, fos = 0, 0 + flc, foc = 0, 0 + for i in omk: + try: + emoji = i.split()[0] + name = i.split(maxsplit=1)[1] + nam = name.split("/")[-1] + if os.path.isdir(name): + size = 0 + for path, dirs, files in os.walk(name): + for f in files: + fp = os.path.join(path, f) + size += os.path.getsize(fp) + if hb(size): + text += f"{emoji} `{nam}` `{hb(size)}" + "`\n" + fos += size + else: + text += f"{emoji} `{nam}`" + "\n" + foc += 1 + else: + if hb(int(os.path.getsize(name))): + text += ( + emoji + + f" `{nam}`" + + " `" + + hb(int(os.path.getsize(name))) + + "`\n" + ) + fls += int(os.path.getsize(name)) + else: + text += f"{emoji} `{nam}`" + "\n" + flc += 1 + except BaseException: + pass + tfos, tfls, ttol = hb(fos), hb(fls), hb(fos + fls) + if not hb(fos): + tfos = "0 B" + if not hb(fls): + tfls = "0 B" + if not hb(fos + fls): + ttol = "0 B" + text += f"\n\n`Folders` : `{foc}` : `{tfos}`\n`Files` : `{flc}` : `{tfls}`\n`Total` : `{flc+foc}` : `{ttol}`" + try: + if (flc + foc) > 100: + text = text.replace("`", "") + await e.eor(text) + except MessageTooLongError: + with io.BytesIO(str.encode(text)) as out_file: + out_file.name = "output.txt" + await e.reply(f"`{e.text}`", file=out_file, thumb=ULTConfig.thumb) + await e.delete() + + + +def sanga_seperator(sanga_list): + string = "".join(info[info.find("\n") + 1 :] for info in sanga_list) + string = re.sub(r"^$\n", "", string, flags=re.MULTILINE) + try: + name, username = string.split("Usernames**") + name = name.split("Names")[1] + except ValueError: + name = "No Names Found" + username = "No Usernames Found" + return name, username + + +def mentionuser(name, userid): + return f"[{name}](tg://user?id={userid})" + + +@ultroid_cmd( + pattern="sg(|u)(?:\\s|$)([\\s\\S]*)", + fullsudo=True, +) +async def sangmata(event): + "To get name/username history." + cmd = event.pattern_match.group(1) + user = event.pattern_match.group(2) + reply = await event.get_reply_message() + if not user and reply: + user = str(reply.sender_id) + if not user: + await event.edit( + "`Reply to user's text message to get name/username history or give userid/username`", + ) + await asyncio.sleep(10) + return await event.delete() + + try: + if user.isdigit(): + userinfo = await ultroid_bot.get_entity(int(user)) + else: + userinfo = await ultroid_bot.get_entity(user) + except ValueError: + userinfo = None + if not isinstance(userinfo, types.User): + await event.edit("`Can't fetch the user...`") + await asyncio.sleep(10) + return await event.delete() + + await event.edit("`Processing...`") + async with event.client.conversation("@SangMata_beta_bot") as conv: + try: + await conv.send_message(f"{userinfo.id}") + except YouBlockedUserError: + return await event.edit("`Please unblock @SangMata_beta_bot and try again`") + # await conv.send_message(f"{userinfo.id}") + responses = [] + while True: + try: + response = await conv.get_response(timeout=2) + except asyncio.TimeoutError: + break + responses.append(response.text) + await event.client.send_read_acknowledge(conv.chat_id) + + if not responses: + await event.edit("`Bot can't fetch results`") + await asyncio.sleep(10) + await event.delete() + if "No records found" in responses or "No data available" in responses: + await event.edit("`The user doesn't have any record`") + await asyncio.sleep(10) + await event.delete() + + names, usernames = sanga_seperator(responses) + check = (usernames, "Username") if cmd == "u" else (names, "Name") + user_name = ( + f"{userinfo.first_name} {userinfo.last_name}" + if userinfo.last_name + else userinfo.first_name + ) + output = f"**➜ User Info :** {mentionuser(user_name, userinfo.id)}\n**➜ {check[1]} History :**\n{check[0]}" + await event.edit(output) + + +@ultroid_cmd(pattern="webshot( (.*)|$)") +async def webss(event): + xx = await event.eor(get_string("com_1")) + xurl = event.pattern_match.group(1).strip() + if not xurl: + return await xx.eor(get_string("wbs_1"), time=5) + if not (await is_url_ok(xurl)): + return await xx.eor(get_string("wbs_2"), time=5) + path, pic = check_filename("shot.png"), None + if async_playwright: + try: + async with async_playwright() as playwright: + chrome = await playwright.chromium.launch() + page = await chrome.new_page() + await page.goto(xurl) + await page.screenshot(path=path, full_page=True) + pic = path + except Exception as er: + LOGS.exception(er) + await xx.respond(f"Error with playwright:\n`{er}`") + if WebShot and not pic: + try: + shot = WebShot( + quality=88, flags=["--enable-javascript", "--no-stop-slow-scripts"] + ) + pic = await shot.create_pic_async(url=xurl) + except Exception as er: + LOGS.exception(er) + if not pic: + pic, msg = await download_file( + f"https://shot.screenshotapi.net/screenshot?&url={xurl}&output=image&file_type=png&wait_for_event=load", + path, + validate=True, + ) + if msg: + await xx.edit(json_parser(msg, indent=1)) + return + if pic: + await xx.reply( + get_string("wbs_3").format(xurl), + file=pic, + link_preview=False, + force_document=True, + ) + os.remove(pic) + await xx.delete() + diff --git a/plugins/twitter.py b/plugins/twitter.py new file mode 100644 index 0000000000000000000000000000000000000000..c105c923e9035bf49cb0781055790ef9ae50cb03 --- /dev/null +++ b/plugins/twitter.py @@ -0,0 +1,220 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}tw ` + Tweet the text. + +• `{i}twr ` + Get tweet details with reply/quote/comment count. + +• `{i}twuser ` + Get user details of the Twitter account. + +• `{i}twl ` + Upload the tweet media to telegram. + +""" + +import asyncio +import os + +try: + _current_loop = asyncio.get_event_loop() +except RuntimeError: + _current_loop = None + +from twikit import Client + +if _current_loop is not None: + # On Windows, importing twikit replaces the global asyncio event loop + # policy (see twikit/__init__.py), which orphans the loop Ultroid's + # clients are already connected to. Re-register it so existing + # Telethon connections keep working. + asyncio.set_event_loop(_current_loop) + +from . import LOGS, eor, get_string, udB, ultroid_cmd + +# Store client globally +twitter_client = None + +# Get path to cookies file +COOKIES_FILE = "resources/auth/twitter_cookies.json" + +async def get_client(): + global twitter_client + if twitter_client: + return twitter_client + + if not all(udB.get_key(key) for key in ["TWITTER_USERNAME", "TWITTER_EMAIL", "TWITTER_PASSWORD"]): + raise Exception("Set TWITTER_USERNAME, TWITTER_EMAIL and TWITTER_PASSWORD in vars first!") + + # Create auth directory if it doesn't exist + os.makedirs(os.path.dirname(COOKIES_FILE), exist_ok=True) + + client = Client() + await client.login( + auth_info_1=udB.get_key("TWITTER_USERNAME"), + auth_info_2=udB.get_key("TWITTER_EMAIL"), + password=udB.get_key("TWITTER_PASSWORD"), + cookies_file=COOKIES_FILE + ) + twitter_client = client + return client + + + +@ultroid_cmd(pattern="tw( (.*)|$)") +async def tweet_cmd(event): + """Post a tweet""" + text = event.pattern_match.group(1).strip() + if not text: + return await event.eor("🚫 `Give some text to tweet!`") + + msg = await event.eor("🕊 `Tweeting...`") + try: + client = await get_client() + tweet = await client.create_tweet(text=text) + await msg.edit(f"✨ **Successfully Posted!**\n\n🔗 https://x.com/{tweet.user.screen_name}/status/{tweet.id}") + except Exception as e: + await msg.edit(f"❌ **Error:**\n`{str(e)}`") + + +@ultroid_cmd(pattern="twdetail( (.*)|$)") +async def twitter_details(event): + """Get tweet details""" + match = event.pattern_match.group(1).strip() + if not match: + return await event.eor("🚫 `Give tweet ID/link to get details!`") + + msg = await event.eor("🔍 `Getting tweet details...`") + try: + client = await get_client() + from urllib.parse import urlparse + parsed_url = urlparse(match) + if parsed_url.hostname in ["twitter.com", "x.com"]: + tweet_id = parsed_url.path.split("/")[-1].split("?")[0] + else: + tweet_id = match + + tweet = await client.get_tweet_by_id(tweet_id) + text = "🐦 **Tweet Details**\n\n" + text += f"📝 **Content:** `{tweet.text}`\n\n" + if hasattr(tweet, "metrics"): + text += f"❤️ **Likes:** `{tweet.metrics.likes}`\n" + text += f"🔄 **Retweets:** `{tweet.metrics.retweets}`\n" + text += f"💬 **Replies:** `{tweet.metrics.replies}`\n" + text += f"👁 **Views:** `{tweet.metrics.views}`\n" + + await msg.edit(text) + except Exception as e: + await msg.edit(f"❌ **Error:**\n`{str(e)}`") + + +@ultroid_cmd(pattern="twuser( (.*)|$)") +async def twitter_user(event): + """Get user details""" + match = event.pattern_match.group(1).strip() + if not match: + return await event.eor("🚫 `Give username to get details!`") + + msg = await event.eor("🔍 `Getting user details...`") + try: + client = await get_client() + user = await client.get_user_by_screen_name(match) + text = "👤 **Twitter User Details**\n\n" + text += f"📛 **Name:** `{user.name}`\n" + text += f"🔖 **Username:** `@{user.screen_name}`\n" + text += f"📝 **Bio:** `{user.description}`\n\n" + text += f"👥 **Followers:** `{user.followers_count}`\n" + text += f"👣 **Following:** `{user.following_count}`\n" + text += f"🐦 **Total Tweets:** `{user.statuses_count}`\n" + text += f"📍 **Location:** `{user.location or 'Not Set'}`\n" + text += f"✅ **Verified:** `{user.verified}`\n" + + if user.profile_image_url: + image_url = user.profile_image_url.replace("_normal.", ".") + await event.client.send_file( + event.chat_id, + file=image_url, + caption=text, + force_document=False + ) + await msg.delete() + else: + await msg.edit(text) + + except Exception as e: + await msg.edit(f"❌ **Error:**\n`{str(e)}`") + + +@ultroid_cmd(pattern="twl( (.*)|$)") +async def twitter_media(event): + """Download tweet media""" + match = event.pattern_match.group(1).strip() + if not match: + return await event.eor("🚫 `Give tweet link to download media!`") + + msg = await event.eor("📥 `Downloading media...`") + try: + client = await get_client() + if "twitter.com" in match or "x.com" in match: + tweet_id = match.split("/")[-1].split("?")[0] + else: + tweet_id = match + + tweet = await client.get_tweet_by_id(tweet_id) + + if not hasattr(tweet, "media"): + return await msg.edit("😕 `No media found in tweet!`") + + # Prepare caption with tweet text + caption = f"🐦 **Tweet by @{tweet.user.screen_name}**\n\n" + caption += f"{tweet.text}\n\n" + if hasattr(tweet, "metrics"): + caption += f"❤️ `{tweet.metrics.likes}` 🔄 `{tweet.metrics.retweets}` 💬 `{tweet.metrics.replies}`" + + media_count = 0 + for media in tweet.media: + if media.type == "photo": + await event.client.send_file( + event.chat_id, + media.url, + caption=caption if media_count == 0 else None # Only add caption to first media + ) + media_count += 1 + elif media.type == "video": + if hasattr(media, "video_info") and isinstance(media.video_info, dict): + variants = media.video_info.get("variants", []) + mp4_variants = [ + v for v in variants + if v.get("content_type") == "video/mp4" and "bitrate" in v + ] + if mp4_variants: + best_video = max(mp4_variants, key=lambda x: x["bitrate"]) + video_caption = caption if media_count == 0 else "" # Only add tweet text to first media + if video_caption: + video_caption += f"\n🎥 Video Quality: {best_video['bitrate']/1000:.0f}kbps" + else: + video_caption = f"🎥 Video Quality: {best_video['bitrate']/1000:.0f}kbps" + + await event.client.send_file( + event.chat_id, + best_video["url"], + caption=video_caption + ) + media_count += 1 + + if media_count > 0: + await msg.edit(f"✅ Successfully downloaded {media_count} media items!") + await msg.delete() + else: + await msg.edit("😕 `No media could be downloaded!`") + except Exception as e: + await msg.edit(f"❌ **Error:**\n`{str(e)}`") diff --git a/plugins/unsplash.py b/plugins/unsplash.py new file mode 100644 index 0000000000000000000000000000000000000000..6f715853950bfb80b2ac0138d6f3aca58945136b --- /dev/null +++ b/plugins/unsplash.py @@ -0,0 +1,36 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• {i}unsplash ; + Unsplash Image Search. +""" + +from pyUltroid.fns.misc import unsplashsearch + +from . import asyncio, download_file, get_string, os, ultroid_cmd + + +@ultroid_cmd(pattern="unsplash( (.*)|$)") +async def searchunsl(ult): + match = ult.pattern_match.group(1).strip() + if not match: + return await ult.eor("Give me Something to Search") + num = 5 + if ";" in match: + num = int(match.split(";")[1]) + match = match.split(";")[0] + tep = await ult.eor(get_string("com_1")) + res = await unsplashsearch(match, limit=num) + if not res: + return await ult.eor(get_string("unspl_1"), time=5) + CL = [download_file(rp, f"{match}-{e}.png") for e, rp in enumerate(res)] + imgs = [z[0] for z in (await asyncio.gather(*CL)) if z] + await ult.respond(f"Uploaded {len(imgs)} Images!", file=imgs) + await tep.delete() + [os.remove(img) for img in imgs] diff --git a/plugins/usage.py b/plugins/usage.py new file mode 100644 index 0000000000000000000000000000000000000000..996ae5171e6178e27b3fddd2b8a5199ea380ab52 --- /dev/null +++ b/plugins/usage.py @@ -0,0 +1,188 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available + +• `{i}usage` + Get overall usage. + +• `{i}usage heroku` + Get heroku stats. + +• `{i}usage db` + Get database storage usage. +""" + +import math +import shutil +from random import choice + +from pyUltroid.fns import some_random_headers + +from . import ( + HOSTED_ON, + LOGS, + Var, + async_searcher, + get_string, + humanbytes, + udB, + ultroid_cmd, +) + +HEROKU_API = None +HEROKU_APP_NAME = None + +if HOSTED_ON == "heroku": + heroku_api, app_name = Var.HEROKU_API, Var.HEROKU_APP_NAME + try: + if heroku_api and app_name: + import heroku3 + + Heroku = heroku3.from_key(heroku_api) + app = Heroku.app(app_name) + HEROKU_API = heroku_api + HEROKU_APP_NAME = app_name + except BaseException as er: + LOGS.exception(er) + + +@ultroid_cmd(pattern="usage") +async def usage_finder(event): + x = await event.eor(get_string("com_1")) + try: + opt = event.text.split(" ", maxsplit=1)[1] + except IndexError: + return await x.edit(simple_usage()) + + if opt == "db": + await x.edit(db_usage()) + elif opt == "heroku": + is_hk, hk = await heroku_usage() + await x.edit(hk) + else: + await x.edit(await get_full_usage()) + + +def simple_usage(): + try: + import psutil + except ImportError: + return "Install 'psutil' to use this..." + total, used, free = shutil.disk_usage(".") + cpuUsage = psutil.cpu_percent() + memory = psutil.virtual_memory().percent + disk = psutil.disk_usage("/").percent + upload = humanbytes(psutil.net_io_counters().bytes_sent) + down = humanbytes(psutil.net_io_counters().bytes_recv) + TOTAL = humanbytes(total) + USED = humanbytes(used) + FREE = humanbytes(free) + return get_string("usage_simple").format( + TOTAL, + USED, + FREE, + upload, + down, + cpuUsage, + memory, + disk, + ) + + +async def heroku_usage(): + try: + import psutil + except ImportError: + return ( + False, + "'psutil' not installed!\nPlease Install it to use this.\n`pip3 install psutil`", + ) + if not (HEROKU_API and HEROKU_APP_NAME): + if HOSTED_ON == "heroku": + return False, "Please fill `HEROKU_API` and `HEROKU_APP_NAME`" + return ( + False, + f"`This command is only for Heroku Users, You are using {HOSTED_ON}`", + ) + user_id = Heroku.account().id + headers = { + "User-Agent": choice(some_random_headers), + "Authorization": f"Bearer {heroku_api}", + "Accept": "application/vnd.heroku+json; version=3.account-quotas", + } + her_url = f"https://api.heroku.com/accounts/{user_id}/actions/get-quota" + try: + result = await async_searcher(her_url, headers=headers, re_json=True) + except Exception as er: + return False, str(er) + quota = result["account_quota"] + quota_used = result["quota_used"] + remaining_quota = quota - quota_used + percentage = math.floor(remaining_quota / quota * 100) + minutes_remaining = remaining_quota / 60 + hours = math.floor(minutes_remaining / 60) + minutes = math.floor(minutes_remaining % 60) + App = result["apps"] + try: + App[0]["quota_used"] + except IndexError: + AppQuotaUsed = 0 + AppPercentage = 0 + else: + AppQuotaUsed = App[0]["quota_used"] / 60 + AppPercentage = math.floor(App[0]["quota_used"] * 100 / quota) + AppHours = math.floor(AppQuotaUsed / 60) + AppMinutes = math.floor(AppQuotaUsed % 60) + total, used, free = shutil.disk_usage(".") + _ = shutil.disk_usage("/") + disk = _.used / _.total * 100 + cpuUsage = psutil.cpu_percent() + memory = psutil.virtual_memory().percent + upload = humanbytes(psutil.net_io_counters().bytes_sent) + down = humanbytes(psutil.net_io_counters().bytes_recv) + TOTAL = humanbytes(total) + USED = humanbytes(used) + FREE = humanbytes(free) + return True, get_string("usage").format( + Var.HEROKU_APP_NAME, + AppHours, + AppMinutes, + AppPercentage, + hours, + minutes, + percentage, + TOTAL, + USED, + FREE, + upload, + down, + cpuUsage, + memory, + disk, + ) + + +def db_usage(): + if udB.name == "Mongo": + total = 512 + elif udB.name == "Redis": + total = 30 + elif udB.name == "SQL": + total = 20 + total = total * (2**20) + used = udB.usage + a = f"{humanbytes(used)}/{humanbytes(total)}" + b = f"{str(round((used / total) * 100, 2))}%" + return f"**{udB.name}**\n\n**Storage Used**: `{a}`\n**Usage percentage**: **{b}**" + + +async def get_full_usage(): + is_hk, hk = await heroku_usage() + her = hk if is_hk else "" + rd = db_usage() + return her + "\n\n" + rd diff --git a/plugins/utilities.py b/plugins/utilities.py new file mode 100644 index 0000000000000000000000000000000000000000..42414aea9f163c61ca0fcbeba543431c94fada60 --- /dev/null +++ b/plugins/utilities.py @@ -0,0 +1,835 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}kickme` : Leaves the group. + +• `{i}date` : Show Calender. + +• `{i}listreserved` + List all usernames (channels/groups) you own. + +• `{i}stats` : See your profile stats. + +• `{i}paste` - `Include long text / Reply to text file.` + +• `{i}info ` + Reply to someone's msg. + +• `{i}invite ` + Add user to the chat. + +• `{i}rmbg ` + Remove background from that picture. + +• `{i}telegraph ` + Upload media/text to telegraph. + +• `{i}json ` + Get the json encoding of the message. + +• `{i}suggest or ` + Create a Yes/No poll for the replied suggestion. + +• `{i}ipinfo ` : Get info about that IP address. + +• `{i}cpy ` + Copy the replied message, with formatting. Expires in 24hrs. +• `{i}pst` + Paste the copied message, with formatting. + +• `{i}thumb ` : Download the thumbnail of the replied file. + +• `{i}getmsg ` + Get messages from chats with forward/copy restrictions. +""" + +import asyncio +import calendar +import html +import io +import os +import pathlib +import time +from datetime import datetime as dt + +try: + from PIL import Image +except ImportError: + Image = None + +from pyUltroid._misc._assistant import asst_cmd +from pyUltroid.dB.gban_mute_db import is_gbanned +from pyUltroid.fns.tools import get_chat_and_msgid + +from . import upload_file as uf + +from telethon.errors.rpcerrorlist import ChatForwardsRestrictedError, UserBotError +from telethon.errors import MessageTooLongError +from telethon.events import NewMessage +from telethon.tl.custom import Dialog +from telethon.tl.functions.channels import ( + GetAdminedPublicChannelsRequest, + InviteToChannelRequest, + LeaveChannelRequest, +) +from telethon.tl.functions.contacts import GetBlockedRequest +from telethon.tl.functions.messages import AddChatUserRequest, GetAllStickersRequest +from telethon.tl.functions.users import GetFullUserRequest +from telethon.tl.types import ( + Channel, + Chat, + InputMediaPoll, + Poll, + PollAnswer, + TLObject, + User, + UserStatusOffline, + UserStatusOnline, + MessageMediaPhoto, + MessageMediaDocument, + DocumentAttributeVideo, +) +from telethon.utils import get_peer_id + +from pyUltroid.fns.info import get_chat_info + +from . import ( + HNDLR, + LOGS, + Image, + ReTrieveFile, + Telegraph, + asst, + async_searcher, + bash, + check_filename, + eod, + eor, + get_paste, + get_string, + inline_mention, + json_parser, + mediainfo, + udB, + ultroid_cmd, +) + +# =================================================================# + +TMP_DOWNLOAD_DIRECTORY = "resources/downloads/" + +CAPTION_LIMIT = 1024 # Telegram's caption character limit for non-premium + +_copied_msg = {} + + +@ultroid_cmd(pattern="kickme$", fullsudo=True) +async def leave(ult): + await ult.eor(f"`{ult.client.me.first_name} has left this group, bye!!.`") + await ult.client(LeaveChannelRequest(ult.chat_id)) + + +@ultroid_cmd( + pattern="date$", +) +async def date(event): + m = dt.now().month + y = dt.now().year + d = dt.now().strftime("Date - %B %d, %Y\nTime- %H:%M:%S") + k = calendar.month(y, m) + await event.eor(f"`{k}\n\n{d}`") + + +@ultroid_cmd( + pattern="listreserved$", +) +async def _(event): + result = await event.client(GetAdminedPublicChannelsRequest()) + if not result.chats: + return await event.eor("`No username Reserved`") + output_str = "".join( + f"- {channel_obj.title} @{channel_obj.username} \n" + for channel_obj in result.chats + ) + await event.eor(output_str) + + +@ultroid_cmd( + pattern="stats$", +) +async def stats( + event: NewMessage.Event, +): + ok = await event.eor("`Collecting stats...`") + start_time = time.time() + private_chats = 0 + bots = 0 + groups = 0 + broadcast_channels = 0 + admin_in_groups = 0 + creator_in_groups = 0 + admin_in_broadcast_channels = 0 + creator_in_channels = 0 + unread_mentions = 0 + unread = 0 + dialog: Dialog + async for dialog in event.client.iter_dialogs(): + entity = dialog.entity + if isinstance(entity, Channel) and entity.broadcast: + broadcast_channels += 1 + if entity.creator or entity.admin_rights: + admin_in_broadcast_channels += 1 + if entity.creator: + creator_in_channels += 1 + + elif (isinstance(entity, Channel) and entity.megagroup) or isinstance( + entity, Chat + ): + groups += 1 + if entity.creator or entity.admin_rights: + admin_in_groups += 1 + if entity.creator: + creator_in_groups += 1 + + elif isinstance(entity, User): + private_chats += 1 + if entity.bot: + bots += 1 + + unread_mentions += dialog.unread_mentions_count + unread += dialog.unread_count + stop_time = time.time() - start_time + try: + ct = (await event.client(GetBlockedRequest(1, 0))).count + except AttributeError: + ct = 0 + try: + sp = await event.client(GetAllStickersRequest(0)) + sp_count = len(sp.sets) + except BaseException: + sp_count = 0 + me = event.client.me + full_name = inline_mention(me) + username_line = ( + f" (`@{me.username}` | [Profile](https://t.me/{me.username}))" + if me.username + else f" (`ID: {me.id}`)" + ) + response = f"🔸 **Stats for {full_name}**{username_line}\n\n" + response += f"**Private Chats:** {private_chats} \n" + response += f"** •• **`Users: {private_chats - bots}` \n" + response += f"** •• **`Bots: {bots}` \n" + response += f"**Groups:** {groups} \n" + response += f"**Channels:** {broadcast_channels} \n" + response += f"**Admin in Groups:** {admin_in_groups} \n" + response += f"** •• **`Creator: {creator_in_groups}` \n" + response += f"** •• **`Admin Rights: {admin_in_groups - creator_in_groups}` \n" + response += f"**Admin in Channels:** {admin_in_broadcast_channels} \n" + response += f"** •• **`Creator: {creator_in_channels}` \n" + response += f"** •• **`Admin Rights: {admin_in_broadcast_channels - creator_in_channels}` \n" + response += f"**Unread:** {unread} \n" + response += f"**Unread Mentions:** {unread_mentions} \n" + response += f"**Blocked Users:** {ct}\n" + response += f"**Total Stickers Pack Installed :** `{sp_count}`\n\n" + response += f"**__It Took:__** {stop_time:.02f}s \n" + await ok.edit(response) + + +@ultroid_cmd(pattern="paste( (.*)|$)", manager=True, allow_all=True) +async def _(event): + try: + input_str = event.text.split(maxsplit=1)[1] + except IndexError: + input_str = None + xx = await event.eor("` 《 Pasting... 》 `") + downloaded_file_name = None + if input_str: + message = input_str + elif event.reply_to_msg_id: + previous_message = await event.get_reply_message() + if previous_message.media: + downloaded_file_name = await event.client.download_media( + previous_message, + "./resources/downloads", + ) + with open(downloaded_file_name, "r") as fd: + message = fd.read() + os.remove(downloaded_file_name) + else: + message = previous_message.message + else: + message = None + if not message: + return await xx.eor( + "`Reply to a Message/Document or Give me Some Text !`", time=5 + ) + done, data = await get_paste(message) + if not done and data.get("error"): + return await xx.eor(data["error"]) + reply_text = ( + f"• **Pasted to SpaceBin :** [Space]({data['link']})\n• **Raw Url :** : [Raw]({data['raw']})" + ) + try: + if event.client._bot: + return await xx.eor(reply_text) + ok = await event.client.inline_query(asst.me.username, f"pasta-{data['link']}") + await ok[0].click(event.chat_id, reply_to=event.reply_to_msg_id, hide_via=True) + await xx.delete() + except BaseException as e: + LOGS.exception(e) + await xx.edit(reply_text) + + +@ultroid_cmd( + pattern="info( (.*)|$)", + manager=True, +) +async def _(event): + if match := event.pattern_match.group(1).strip(): + try: + user = await event.client.parse_id(match) + except Exception as er: + return await event.eor(str(er)) + elif event.is_reply: + rpl = await event.get_reply_message() + user = rpl.sender_id + else: + user = event.chat_id + xx = await event.eor(get_string("com_1")) + try: + _ = await event.client.get_entity(user) + except Exception as er: + return await xx.edit(f"**ERROR :** {er}") + if not isinstance(_, User): + try: + peer = get_peer_id(_) + photo, capt = await get_chat_info(_, event) + if is_gbanned(peer): + capt += "\n• Is Gbanned: True" + if not photo: + return await xx.eor(capt, parse_mode="html") + await event.client.send_message( + event.chat_id, capt[:1024], file=photo, parse_mode="html" + ) + await xx.delete() + except Exception as er: + await event.eor("**ERROR ON CHATINFO**\n" + str(er)) + return + try: + full_user = (await event.client(GetFullUserRequest(user))).full_user + except Exception as er: + return await xx.edit(f"ERROR : {er}") + user = _ + user_photos = ( + await event.client.get_profile_photos(user.id, limit=0) + ).total or "NaN" + user_id = user.id + first_name = html.escape(user.first_name) + if first_name is not None: + first_name = first_name.replace("\u2060", "") + last_name = user.last_name + last_name = ( + last_name.replace("\u2060", "") if last_name else ("Last Name not found") + ) + user_bio = full_user.about + if user_bio is not None: + user_bio = html.escape(full_user.about) + common_chats = full_user.common_chats_count + if user.photo: + dc_id = user.photo.dc_id + else: + dc_id = "Need a Profile Picture to check this" + caption = """Extracted Data From Telegram's Database +• Telegram ID: {} +• Permanent Link: Click Here +• First Name: {} +• Last Name: {} +• Bio: {} +• DC ID: {} +• Number of Profile Pictures: {} +• Is Restricted: {} +• Verified: {} +• Is Premium: {} +• Is A Bot: {} +• Groups In Common: {} +""".format( + user_id, + user_id, + first_name, + last_name, + user_bio, + dc_id, + user_photos, + user.restricted, + user.verified, + user.premium, + user.bot, + common_chats, + ) + if chk := is_gbanned(user_id): + caption += f"""••Gʟᴏʙᴀʟʟʏ Bᴀɴɴᴇᴅ: True +••Rᴇᴀsᴏɴ: {chk}""" + await event.client.send_message( + event.chat_id, + caption, + reply_to=event.reply_to_msg_id, + parse_mode="HTML", + file=full_user.profile_photo, + force_document=False, + silent=True, + ) + await xx.delete() + + +@ultroid_cmd( + pattern="invite( (.*)|$)", + groups_only=True, +) +async def _(ult): + xx = await ult.eor(get_string("com_1")) + to_add_users = ult.pattern_match.group(1).strip() + if not ult.is_channel and ult.is_group: + for user_id in to_add_users.split(" "): + try: + await ult.client( + AddChatUserRequest( + chat_id=ult.chat_id, + user_id=await ult.client.parse_id(user_id), + fwd_limit=1000000, + ), + ) + await xx.edit(f"Successfully invited `{user_id}` to `{ult.chat_id}`") + except Exception as e: + await xx.edit(str(e)) + else: + for user_id in to_add_users.split(" "): + try: + await ult.client( + InviteToChannelRequest( + channel=ult.chat_id, + users=[await ult.client.parse_id(user_id)], + ), + ) + await xx.edit(f"Successfully invited `{user_id}` to `{ult.chat_id}`") + except UserBotError: + await xx.edit( + f"Bots can only be added as Admins in Channel.\nBetter Use `{HNDLR}promote {user_id}`" + ) + except Exception as e: + await xx.edit(str(e)) + + +@ultroid_cmd( + pattern="rmbg($| (.*))", +) +async def abs_rmbg(event): + RMBG_API = udB.get_key("RMBG_API") + if not RMBG_API: + return await event.eor( + "Get your API key from [here](https://www.remove.bg/) for this plugin to work.", + ) + match = event.pattern_match.group(1).strip() + reply = await event.get_reply_message() + if match and os.path.exists(match): + dl = match + elif reply and reply.media: + if reply.document and reply.document.thumbs: + dl = await reply.download_media(thumb=-1) + else: + dl = await reply.download_media() + else: + return await eod( + event, f"Use `{HNDLR}rmbg` as reply to a pic to remove its background." + ) + if not (dl and dl.endswith(("webp", "jpg", "png", "jpeg"))): + os.remove(dl) + return await event.eor(get_string("com_4")) + if dl.endswith("webp"): + file = f"{dl}.png" + Image.open(dl).save(file) + os.remove(dl) + dl = file + xx = await event.eor("`Sending to remove.bg`") + dn, out = await ReTrieveFile(dl) + os.remove(dl) + if not dn: + dr = out["errors"][0] + de = dr.get("detail", "") + return await xx.edit( + f"**ERROR ~** `{dr['title']}`,\n`{de}`", + ) + zz = Image.open(out) + if zz.mode != "RGB": + zz.convert("RGB") + wbn = check_filename("ult-rmbg.webp") + zz.save(wbn, "webp") + await event.client.send_file( + event.chat_id, + out, + force_document=True, + reply_to=reply, + ) + await event.client.send_file(event.chat_id, wbn, reply_to=reply) + os.remove(out) + os.remove(wbn) + await xx.delete() + + +@ultroid_cmd( + pattern="telegraph( (.*)|$)", +) +async def telegraphcmd(event): + xx = await event.eor(get_string("com_1")) + match = event.pattern_match.group(1).strip() or "Ultroid" + reply = await event.get_reply_message() + if not reply: + return await xx.eor("`Reply to Message.`") + if not reply.media and reply.message: + content = reply.message + else: + getit = await reply.download_media() + dar = mediainfo(reply.media) + if dar == "sticker": + file = f"{getit}.png" + Image.open(getit).save(file) + os.remove(getit) + getit = file + elif dar.endswith("animated"): + file = f"{getit}.gif" + await bash(f"lottie_convert.py '{getit}' {file}") + os.remove(getit) + getit = file + if "document" not in dar: + try: + nn = uf(getit) + amsg = f"Uploaded to [Telegraph]({nn}) !" + except Exception as e: + amsg = f"Error : {e}" + os.remove(getit) + return await xx.eor(amsg) + content = pathlib.Path(getit).read_text() + os.remove(getit) + makeit = Telegraph.create_page(title=match, content=[content]) + await xx.eor( + f"Pasted to Telegraph : [Telegraph]({makeit['url']})", link_preview=False + ) + + +@ultroid_cmd(pattern="json( (.*)|$)") +async def _(event): + reply_to_id = None + match = event.pattern_match.group(1).strip() + if event.reply_to_msg_id: + msg = await event.get_reply_message() + reply_to_id = event.reply_to_msg_id + else: + msg = event + reply_to_id = event.message.id + if match and hasattr(msg, match.split()[0]): + msg = getattr(msg, match.split()[0]) + try: + if hasattr(msg, "to_json"): + msg = msg.to_json(ensure_ascii=False, indent=1) + elif hasattr(msg, "to_dict"): + msg = json_parser(msg.to_dict(), indent=1) + else: + msg = TLObject.stringify(msg) + except Exception: + pass + msg = str(msg) + else: + msg = json_parser(msg.to_json(), indent=1) + if "-t" in match: + try: + data = json_parser(msg) + msg = json_parser( + {key: data[key] for key in data.keys() if data[key]}, indent=1 + ) + except Exception: + pass + if len(msg) > 4096: + with io.BytesIO(str.encode(msg)) as out_file: + out_file.name = "json-ult.txt" + await event.client.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + reply_to=reply_to_id, + ) + await event.delete() + else: + await event.eor(f"```{msg or None}```") + + +@ultroid_cmd(pattern="suggest( (.*)|$)", manager=True) +async def sugg(event): + sll = event.text.split(maxsplit=1) + try: + text = sll[1] + except IndexError: + text = None + if not (event.is_reply or text): + return await eod( + event, + "`Please reply to a message to make a suggestion poll!`", + ) + if event.is_reply and not text: + reply = await event.get_reply_message() + if reply.text and len(reply.text) < 35: + text = reply.text + else: + text = "Do you Agree to Replied Suggestion ?" + reply_to = event.reply_to_msg_id if event.is_reply else event.id + try: + await event.client.send_file( + event.chat_id, + file=InputMediaPoll( + poll=Poll( + id=12345, + question=text, + answers=[PollAnswer("Yes", b"1"), PollAnswer("No", b"2")], + ), + ), + reply_to=reply_to, + ) + except Exception as e: + return await eod(event, f"`Oops, you can't send polls here!\n\n{e}`") + await event.delete() + + +@ultroid_cmd(pattern="ipinfo( (.*)|$)") +async def ipinfo(event): + ip = event.text.split() + ipaddr = "" + try: + ipaddr = f"/{ip[1]}" + except IndexError: + ipaddr = "" + det = await async_searcher(f"https://ipinfo.io{ipaddr}/geo", re_json=True) + try: + ip = det["ip"] + city = det["city"] + region = det["region"] + country = det["country"] + cord = det["loc"] + try: + zipc = det["postal"] + except KeyError: + zipc = "None" + tz = det["timezone"] + await eor( + event, + """ +**IP Details Fetched.** + +**IP:** `{}` +**City:** `{}` +**Region:** `{}` +**Country:** `{}` +**Co-ordinates:** `{}` +**Postal Code:** `{}` +**Time Zone:** `{}` +""".format( + ip, + city, + region, + country, + cord, + zipc, + tz, + ), + ) + except BaseException: + err = det["error"]["title"] + msg = det["error"]["message"] + await event.eor(f"ERROR:\n{err}\n{msg}", time=5) + + +@ultroid_cmd( + pattern="cpy$", +) +async def copp(event): + msg = await event.get_reply_message() + if not msg: + return await event.eor(f"Use `{HNDLR}cpy` as reply to a message!", time=5) + _copied_msg["CLIPBOARD"] = msg + await event.eor(f"Copied. Use `{HNDLR}pst` to paste!", time=10) + + +@asst_cmd(pattern="pst$") +async def pepsodent(event): + await toothpaste(event) + + +@ultroid_cmd( + pattern="pst$", +) +async def colgate(event): + await toothpaste(event) + + +async def toothpaste(event): + try: + await event.respond(_copied_msg["CLIPBOARD"]) + except KeyError: + return await eod( + event, + f"Nothing was copied! Use `{HNDLR}cpy` as reply to a message first!", + ) + except Exception as ex: + return await event.eor(str(ex), time=5) + await event.delete() + + +@ultroid_cmd(pattern="thumb$") +async def thumb_dl(event): + reply = await event.get_reply_message() + if not (reply and reply.file): + return await eod(event, get_string("th_1"), time=5) + if not reply.file.media.thumbs: + return await eod(event, get_string("th_2")) + await event.eor(get_string("com_1")) + x = await event.get_reply_message() + m = await x.download_media(thumb=-1) + await event.reply(file=m) + os.remove(m) + + +async def get_video_duration(file_path): + cmd = [ + "ffprobe", + "-v", + "error", + "-show_entries", + "format=duration", + "-of", + "default=noprint_wrappers=1:nokey=1", + file_path, + ] + try: + result = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await result.communicate() + duration = float(stdout.decode().strip()) + return duration + except Exception as e: + print("Error running ffprobe:", e) + return None + +async def get_thumbnail(file_path, thumbnail_path): + try: + await asyncio.create_subprocess_exec( + "ffmpeg", + "-i", file_path, + "-ss", "00:00:04", + "-vframes", "1", # Extract a single frame as the thumbnail + thumbnail_path, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + except Exception as e: + print(f"Error extracting thumbnail: {e}") + +@ultroid_cmd(pattern="getmsg( ?(.*)|$)") +async def get_restricted_msg(event): + match = event.pattern_match.group(1).strip() + if not match: + await event.eor("`Please provide a link!`", time=5) + return + + xx = await event.eor("`Loading...`") + chat, msg = get_chat_and_msgid(match) + if not (chat and msg): + return await event.eor( + "Invalid link!\nExamples:\n" + "`https://t.me/TeamUltroid/3`\n" + "`https://t.me/c/1313492028/3`\n" + "`tg://openmessage?user_id=1234567890&message_id=1`" + ) + + try: + input_entity = await event.client.get_input_entity(chat) + message = await event.client.get_messages(input_entity, ids=msg) + except BaseException as er: + return await event.eor(f"**ERROR**\n`{er}`") + + if not message: + return await event.eor("`Message not found or may not exist.`") + + try: + await event.client.send_message(event.chat_id, message) + await xx.try_delete() + return + except ChatForwardsRestrictedError: + pass + + if message.media: + if isinstance(message.media, (MessageMediaPhoto, MessageMediaDocument)): + media_path, _ = await event.client.fast_downloader(message.document, show_progress=True, event=xx, message=get_string("com_5")) + + caption = message.text or "" + + attributes = [] + if message.video: + duration = await get_video_duration(media_path.name) + + width, height = 0, 0 + for attribute in message.document.attributes: + if isinstance(attribute, DocumentAttributeVideo): + width = attribute.w + height = attribute.h + break + + thumb_path = media_path.name + "_thumb.jpg" + await get_thumbnail(media_path.name, thumb_path) + + attributes.append( + DocumentAttributeVideo( + duration=int(duration) if duration else 0, + w=width, + h=height, + supports_streaming=True, + ) + ) + await xx.edit(get_string("com_6")) + media_path, _ = await event.client.fast_uploader(media_path.name, event=xx, show_progress=True, to_delete=True) + + try: + await event.client.send_file( + event.chat_id, + media_path, + caption=caption, + force_document=False, + supports_streaming=True if message.video else False, + thumb=thumb_path if message.video else None, + attributes=attributes if message.video else None, + ) + except MessageTooLongError: + if len(caption) > CAPTION_LIMIT: + caption = caption[:CAPTION_LIMIT] + "..." + await event.client.send_file( + event.chat_id, + media_path, + caption=caption, + force_document=False, # Set to True if you want to send as a document + supports_streaming=True if message.video else False, + thumb=thumb_path if message.video else None, + attributes=attributes if message.video else None, + ) + + if message.video and os.path.exists(thumb_path): + os.remove(thumb_path) + await xx.try_delete() + else: + await event.eor("`Cannot process this type of media.`") + else: + await event.eor("`No media found in the message.`") + diff --git a/plugins/variables.py b/plugins/variables.py new file mode 100644 index 0000000000000000000000000000000000000000..2163c38d262e905551c239366c0c177243860723 --- /dev/null +++ b/plugins/variables.py @@ -0,0 +1,94 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}get var ` + Get value of the given variable name. + +• `{i}get type ` + Get variable type. + +• `{i}get db ` + Get db value of the given key. + +• `{i}get keys` + Get all redis keys. +""" + +import os + +from . import eor, get_string, udB, ultroid_cmd, HNDLR + + +@ultroid_cmd(pattern="get($| (.*))", fullsudo=True) +async def get_var(event): + try: + opt = event.text.split(maxsplit=2)[1] + except IndexError: + return await event.eor(f"what to get?\nRead `{HNDLR}help variables`") + x = await event.eor(get_string("com_1")) + if opt != "keys": + try: + varname = event.text.split(maxsplit=2)[2] + except IndexError: + return await eor(x, "Such a var doesn't exist!", time=5) + if opt == "var": + c = 0 + # try redis + val = udB.get_key(varname) + if val is not None: + c += 1 + await x.edit( + f"**Variable** - `{varname}`\n**Value**: `{val}`\n**Type**: Redis Key." + ) + # try env vars + val = os.getenv(varname) + if val is not None: + c += 1 + await x.edit( + f"**Variable** - `{varname}`\n**Value**: `{val}`\n**Type**: Env Var." + ) + + if c == 0: + await eor(x, "Such a var doesn't exist!", time=5) + + elif opt == "type": + c = 0 + # try redis + val = udB.get_key(varname) + if val is not None: + c += 1 + await x.edit(f"**Variable** - `{varname}`\n**Type**: Redis Key.") + # try env vars + val = os.getenv(varname) + if val is not None: + c += 1 + await x.edit(f"**Variable** - `{varname}`\n**Type**: Env Var.") + + if c == 0: + await eor(x, "Such a var doesn't exist!", time=5) + + elif opt == "db": + val = udB.get(varname) + if val is not None: + await x.edit(f"**Key** - `{varname}`\n**Value**: `{val}`") + else: + await eor(x, "No such key!", time=5) + + elif opt == "keys": + keys = sorted(udB.keys()) + msg = "".join( + f"• `{i}`" + "\n" + for i in keys + if not i.isdigit() + and not i.startswith("-") + and not i.startswith("_") + and not i.startswith("GBAN_REASON_") + ) + + await x.edit(f"**List of DB Keys :**\n{msg}") diff --git a/plugins/vctools.py b/plugins/vctools.py new file mode 100644 index 0000000000000000000000000000000000000000..376ae3db00a54b304f40f1e7373f931479b0fea5 --- /dev/null +++ b/plugins/vctools.py @@ -0,0 +1,105 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +""" +✘ Commands Available - + +• `{i}startvc` + Start Group Call in a group. + +• `{i}stopvc` + Stop Group Call in a group. + +• `{i}vctitle ` + Change the title Group call. + +• `{i}vcinvite` + Invite all members of group in Group Call. + (You must be joined) +""" + +from telethon.tl.functions.channels import GetFullChannelRequest as getchat +from telethon.tl.functions.phone import CreateGroupCallRequest as startvc +from telethon.tl.functions.phone import DiscardGroupCallRequest as stopvc +from telethon.tl.functions.phone import EditGroupCallTitleRequest as settitle +from telethon.tl.functions.phone import GetGroupCallRequest as getvc +from telethon.tl.functions.phone import InviteToGroupCallRequest as invitetovc + +from . import get_string, ultroid_cmd + + +async def get_call(event): + mm = await event.client(getchat(event.chat_id)) + xx = await event.client(getvc(mm.full_chat.call, limit=1)) + return xx.call + + +def user_list(l, n): + for i in range(0, len(l), n): + yield l[i : i + n] + + +@ultroid_cmd( + pattern="stopvc$", + admins_only=True, + groups_only=True, +) +async def _(e): + try: + await e.client(stopvc(await get_call(e))) + await e.eor(get_string("vct_4")) + except Exception as ex: + await e.eor(f"`{ex}`") + + +@ultroid_cmd( + pattern="vcinvite$", + groups_only=True, +) +async def _(e): + ok = await e.eor(get_string("vct_3")) + users = [] + z = 0 + async for x in e.client.iter_participants(e.chat_id): + if not x.bot: + users.append(x.id) + hmm = list(user_list(users, 6)) + for p in hmm: + try: + await e.client(invitetovc(call=await get_call(e), users=p)) + z += 6 + except BaseException: + pass + await ok.edit(get_string("vct_5").format(z)) + + +@ultroid_cmd( + pattern="startvc$", + admins_only=True, + groups_only=True, +) +async def _(e): + try: + await e.client(startvc(e.chat_id)) + await e.eor(get_string("vct_1")) + except Exception as ex: + await e.eor(f"`{ex}`") + + +@ultroid_cmd( + pattern="vctitle(?: |$)(.*)", + admins_only=True, + groups_only=True, +) +async def _(e): + title = e.pattern_match.group(1).strip() + if not title: + return await e.eor(get_string("vct_6"), time=5) + try: + await e.client(settitle(call=await get_call(e), title=title.strip())) + await e.eor(get_string("vct_2").format(title)) + except Exception as ex: + await e.eor(f"`{ex}`") diff --git a/plugins/videotools.py b/plugins/videotools.py new file mode 100644 index 0000000000000000000000000000000000000000..7df53ba44f61b4bd400223b42c6be9c1bd872e4e --- /dev/null +++ b/plugins/videotools.py @@ -0,0 +1,139 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>. +""" +✘ Commands Available - + +•`{i}sample <duration in seconds>` + Creates Short sample of video.. + +• `{i}vshots <number of shots>` + Creates screenshot of video.. + +• `{i}vtrim <start time> - <end time> in seconds` + Crop a Lengthy video.. +""" + +import glob +import os + +from pyUltroid.fns.tools import set_attributes + +from . import ( + ULTConfig, + bash, + duration_s, + eod, + genss, + get_string, + mediainfo, + stdr, + ultroid_cmd, +) + + +@ultroid_cmd(pattern="sample( (.*)|$)") +async def gen_sample(e): + sec = e.pattern_match.group(1).strip() + stime = int(sec) if sec and sec.isdigit() else 30 + vido = await e.get_reply_message() + if vido and vido.media and "video" in mediainfo(vido.media): + msg = await e.eor(get_string("com_1")) + file, _ = await e.client.fast_downloader( + vido.document, show_progress=True, event=msg + ) + file_name = (file.name).split("/")[-1] + out = file_name.replace(file_name.split(".")[-1], "_sample.mkv") + xxx = await msg.edit(f"Generating Sample of `{stime}` seconds...") + ss, dd = await duration_s(file.name, stime) + cmd = f'ffmpeg -i "{file.name}" -preset ultrafast -ss {ss} -to {dd} -codec copy -map 0 "{out}" -y' + await bash(cmd) + os.remove(file.name) + attributes = await set_attributes(out) + mmmm, _ = await e.client.fast_uploader( + out, show_progress=True, event=xxx, to_delete=True + ) + caption = f"A Sample Video Of `{stime}` seconds" + await e.client.send_file( + e.chat_id, + mmmm, + thumb=ULTConfig.thumb, + caption=caption, + attributes=attributes, + force_document=False, + reply_to=e.reply_to_msg_id, + ) + await xxx.delete() + else: + await e.eor(get_string("audiotools_8"), time=5) + + +@ultroid_cmd(pattern="vshots( (.*)|$)") +async def gen_shots(e): + ss = e.pattern_match.group(1).strip() + shot = int(ss) if ss and ss.isdigit() else 5 + vido = await e.get_reply_message() + if vido and vido.media and "video" in mediainfo(vido.media): + msg = await e.eor(get_string("com_1")) + file, _ = await e.client.fast_downloader( + vido.document, show_progress=True, event=msg + ) + xxx = await msg.edit(f"Generating `{shot}` screenshots...") + await bash("rm -rf ss && mkdir ss") + cmd = f'ffmpeg -i "{file.name}" -vf fps=0.009 -vframes {shot} "ss/pic%01d.png"' + await bash(cmd) + os.remove(file.name) + pic = glob.glob("ss/*") + text = f"Uploaded {len(pic)}/{shot} screenshots" + if not pic: + text = "`Failed to Take Screenshots..`" + pic = None + await e.respond(text, file=pic) + await bash("rm -rf ss") + await xxx.delete() + + +@ultroid_cmd(pattern="vtrim( (.*)|$)") +async def gen_sample(e): + sec = e.pattern_match.group(1).strip() + if not sec or "-" not in sec: + return await eod(e, get_string("audiotools_3")) + a, b = sec.split("-") + if int(a) >= int(b): + return await eod(e, get_string("audiotools_4")) + vido = await e.get_reply_message() + if vido and vido.media and "video" in mediainfo(vido.media): + msg = await e.eor(get_string("audiotools_5")) + file, _ = await e.client.fast_downloader( + vido.document, show_progress=True, event=msg + ) + file_name = (file.name).split("/")[-1] + out = file_name.replace(file_name.split(".")[-1], "_trimmed.mkv") + if int(b) > int(await genss(file.name)): + os.remove(file.name) + return await eod(msg, get_string("audiotools_6")) + ss, dd = stdr(int(a)), stdr(int(b)) + xxx = await msg.edit(f"Trimming Video from `{ss}` to `{dd}`...") + cmd = f'ffmpeg -i "{file.name}" -preset ultrafast -ss {ss} -to {dd} -codec copy -map 0 "{out}" -y' + await bash(cmd) + os.remove(file.name) + attributes = await set_attributes(out) + mmmm, _ = await e.client.fast_uploader( + out, show_progress=True, event=msg, to_delete=True + ) + caption = f"Trimmed Video From `{ss}` To `{dd}`" + await e.client.send_file( + e.chat_id, + mmmm, + thumb=ULTConfig.thumb, + caption=caption, + attributes=attributes, + force_document=False, + reply_to=e.reply_to_msg_id, + ) + await xxx.delete() + else: + await e.eor(get_string("audiotools_8"), time=5) diff --git a/plugins/warn.py b/plugins/warn.py new file mode 100644 index 0000000000000000000000000000000000000000..338d6c8a8c7b148fc9062b3be952d90c2a6922fd --- /dev/null +++ b/plugins/warn.py @@ -0,0 +1,186 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>. +""" +✘ Commands Available + +•`{i}warn <reply to user> <reason>` + Gives Warn. + +•`{i}resetwarn <reply to user>` + To reset All Warns. + +•`{i}warns <reply to user>` + To Get List of Warnings of a user. + +•`{i}setwarn <warn count> | <ban/mute/kick>` + Set Number in warn count for warnings + After putting " | " mark put action like ban/mute/kick + Its Default 3 kick + Example : `setwarn 5 | mute` + +""" + +from pyUltroid.dB.warn_db import add_warn, reset_warn, warns + +from . import eor, get_string, inline_mention, udB, ultroid_cmd + + +@ultroid_cmd( + pattern="warn( (.*)|$)", + manager=True, + groups_only=True, + admins_only=True, +) +async def warn(e): + ultroid_bot = e.client + reply = await e.get_reply_message() + if len(e.text) > 5 and " " not in e.text[5]: + return + if reply: + user = reply.sender_id + reason = e.text[5:] if e.pattern_match.group(1).strip() else "unknown" + else: + try: + user = e.text.split()[1] + if user.startswith("@"): + ok = await ultroid_bot.get_entity(user) + user = ok.id + else: + user = int(user) + except BaseException: + return await e.eor("Reply To A User", time=5) + try: + reason = e.text.split(maxsplit=2)[-1] + except BaseException: + reason = "unknown" + count, r = warns(e.chat_id, user) + r = f"{r}|$|{reason}" if r else reason + try: + x = udB.get_key("SETWARN") + number, action = int(x.split()[0]), x.split()[1] + except BaseException: + number, action = 3, "kick" + if ("ban" or "kick" or "mute") not in action: + action = "kick" + if count + 1 >= number: + if "ban" in action: + try: + await ultroid_bot.edit_permissions(e.chat_id, user, view_messages=False) + except BaseException: + return await e.eor("`Something Went Wrong.`", time=5) + elif "kick" in action: + try: + await ultroid_bot.kick_participant(e.chat_id, user) + except BaseException: + return await e.eor("`Something Went Wrong.`", time=5) + elif "mute" in action: + try: + await ultroid_bot.edit_permissions( + e.chat_id, user, until_date=None, send_messages=False + ) + except BaseException: + return await e.eor("`Something Went Wrong.`", time=5) + add_warn(e.chat_id, user, count + 1, r) + c, r = warns(e.chat_id, user) + ok = await ultroid_bot.get_entity(user) + user = inline_mention(ok) + r = r.split("|$|") + text = f"User {user} Got {action} Due to {count+1} Warns.\n\n" + for x in range(c): + text += f"•**{x+1}.** {r[x]}\n" + await e.eor(text) + return reset_warn(e.chat_id, ok.id) + add_warn(e.chat_id, user, count + 1, r) + ok = await ultroid_bot.get_entity(user) + user = inline_mention(ok) + await eor( + e, + f"**WARNING :** {count+1}/{number}\n**To :**{user}\n**Be Careful !!!**\n\n**Reason** : {reason}", + ) + + +@ultroid_cmd( + pattern="resetwarn( (.*)|$)", + manager=True, + groups_only=True, + admins_only=True, +) +async def rwarn(e): + reply = await e.get_reply_message() + if reply: + user = reply.sender_id + else: + try: + user = e.text.split()[1] + if user.startswith("@"): + ok = await e.client.get_entity(user) + user = ok.id + else: + user = int(user) + except BaseException: + return await e.eor("Reply To user") + reset_warn(e.chat_id, user) + ok = await e.client.get_entity(user) + user = inline_mention(ok) + await e.eor(f"Cleared All Warns of {user}.") + + +@ultroid_cmd( + pattern="warns( (.*)|$)", + manager=True, + groups_only=True, + admins_only=True, +) +async def twarns(e): + reply = await e.get_reply_message() + if reply: + user = reply.from_id.user_id + else: + try: + user = e.text.split()[1] + if user.startswith("@"): + ok = await e.client.get_entity(user) + user = ok.id + else: + user = int(user) + except BaseException: + return await e.eor("Reply To A User", time=5) + c, r = warns(e.chat_id, user) + if c and r: + ok = await e.client.get_entity(user) + user = inline_mention(ok) + r = r.split("|$|") + text = f"User {user} Got {c} Warns.\n\n" + for x in range(c): + text += f"•**{x+1}.** {r[x]}\n" + await e.eor(text) + else: + await e.eor("`No Warnings`") + + +@ultroid_cmd(pattern="setwarn( (.*)|$)", manager=True) +async def warnset(e): + ok = e.pattern_match.group(1).strip() + if not ok: + return await e.eor("Invalid format. Correct usage: .setwarns <number>|<action>") + if "|" in ok: + try: + number, action = ok.split("|") + number = int(number.strip()) + action = action.strip() + except ValueError: + return await e.eor( + "Invalid format. Correct usage: .setwarns <number>|<action>", time=5 + ) + if action not in ["ban", "mute", "kick"]: + return await e.eor("Only mute / ban / kick options are supported", time=5) + udB.set_key("SETWARN", f"{number} {action}") + await e.eor(f"Done. Your Warn Count is now {number} and Action is {action}") + else: + await e.eor( + "Invalid format. Correct usage: .setwarns <number>|<action>", time=5 + ) diff --git a/plugins/weather.py b/plugins/weather.py new file mode 100644 index 0000000000000000000000000000000000000000..26420330ec771d984c0d1f44ca897a5b8ca56a93 --- /dev/null +++ b/plugins/weather.py @@ -0,0 +1,176 @@ +# Ultroid ~ UserBot +# Copyright (C) 2023-2024 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>. + +""" +**Get Weather Data using OpenWeatherMap API** +❍ Commands Available - + +• `{i}weather` <city name> + Shows the Weather of Cities + +• `{i}air` <city name> + Shows the Air Condition of Cities +""" + +import datetime +import time +from datetime import timedelta + +import aiohttp +import pytz + +from . import async_searcher, get_string, udB, ultroid_cmd + + +async def get_timezone(offset_seconds, use_utc=False): + offset = timedelta(seconds=offset_seconds) + hours, remainder = divmod(offset.seconds, 3600) + sign = "+" if offset.total_seconds() >= 0 else "-" + timezone = "UTC" if use_utc else "GMT" + if use_utc: + for m in pytz.all_timezones: + tz = pytz.timezone(m) + now = datetime.datetime.now(tz) + if now.utcoffset() == offset: + return f"{m} ({timezone}{sign}{hours:02d})" + else: + for m in pytz.all_timezones: + tz = pytz.timezone(m) + if m.startswith("Australia/"): + now = datetime.datetime.now(tz) + if now.utcoffset() == offset: + return f"{m} ({timezone}{sign}{hours:02d})" + for m in pytz.all_timezones: + tz = pytz.timezone(m) + now = datetime.datetime.now(tz) + if now.utcoffset() == offset: + return f"{m} ({timezone}{sign}{hours:02d})" + return "Timezone not found" + +async def getWindinfo(speed: str, degree: str) -> str: + dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] + ix = round(degree / (360.00 / len(dirs))) + kmph = str(float(speed) * 3.6) + " km/h" + return f"[{dirs[ix % len(dirs)]}] {kmph}" + +async def get_air_pollution_data(latitude, longitude, api_key): + url = f"http://api.openweathermap.org/data/2.5/air_pollution?lat={latitude}&lon={longitude}&appid={api_key}" + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + data = await response.json() + if "list" in data: + air_pollution = data["list"][0] + return air_pollution + else: + return None + + +@ultroid_cmd(pattern="weather ?(.*)") +async def weather(event): + if event.fwd_from: + return + msg = await event.eor(get_string("com_1")) + x = udB.get_key("OPENWEATHER_API") + if x is None: + await event.eor( + "No API found. Get One from [Here](https://api.openweathermap.org)\nAnd Add it in OPENWEATHER_API Redis Key", + time=8, + ) + return + input_str = event.pattern_match.group(1) + if not input_str: + await event.eor("No Location was Given...", time=5) + return + elif input_str == "butler": + await event.eor("search butler,au for australila", time=5) + sample_url = f"https://api.openweathermap.org/data/2.5/weather?q={input_str}&APPID={x}&units=metric" + try: + response_api = await async_searcher(sample_url, re_json=True) + if response_api["cod"] == 200: + country_time_zone = int(response_api["timezone"]) + tz = f"{await get_timezone(country_time_zone)}" + sun_rise_time = int(response_api["sys"]["sunrise"]) + country_time_zone + sun_set_time = int(response_api["sys"]["sunset"]) + country_time_zone + await msg.edit( + f"{response_api['name']}, {response_api['sys']['country']}\n\n" + f"╭────────────────•\n" + f"╰➢ **𝖶𝖾𝖺𝗍𝗁𝖾𝗋:** {response_api['weather'][0]['description']}\n" + f"╰➢ **𝖳𝗂𝗆𝖾𝗓𝗈𝗇𝖾:** {tz}\n" + f"╰➢ **𝖲𝗎𝗇𝗋𝗂𝗌𝖾:** {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(sun_rise_time))}\n" + f"╰➢ **𝖲𝗎𝗇𝗌𝖾𝗍:** {time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(sun_set_time))}\n" + f"╰➢ **𝖶𝗂𝗇𝖽:** {await getWindinfo(response_api['wind']['speed'], response_api['wind']['deg'])}\n" + f"╰➢ **𝖳𝖾𝗆𝗉𝖾𝗋𝖺𝗍𝗎𝗋𝖾:** {response_api['main']['temp']}°C\n" + f"╰➢ **𝖥𝖾𝖾𝗅𝗌 𝗅𝗂𝗄𝖾:** {response_api['main']['feels_like']}°C\n" + f"╰➢ **𝖬𝗂𝗇𝗂𝗆𝗎𝗆:** {response_api['main']['temp_min']}°C\n" + f"╰➢ **𝖬𝖺𝗑𝗂𝗆𝗎𝗆:** {response_api['main']['temp_max']}°C\n" + f"╰➢ **𝖯𝗋𝖾𝗌𝗌𝗎𝗋𝖾:** {response_api['main']['pressure']} hPa\n" + f"╰➢ **𝖧𝗎𝗆𝗂𝖽𝗂𝗍𝗒:** {response_api['main']['humidity']}%\n" + f"╰➢ **𝖵𝗂𝗌𝗂𝖻𝗂𝗅𝗂𝗍𝗒:** {response_api['visibility']} m\n" + f"╰➢ **𝖢𝗅𝗈𝗎𝖽𝗌:** {response_api['clouds']['all']}%\n" + f"╰────────────────•\n\n" + ) + else: + await msg.edit(response_api["message"]) + except Exception as e: + await event.eor(f"An unexpected error occurred: {str(e)}", time=5) + + +@ultroid_cmd(pattern="air ?(.*)") +async def air_pollution(event): + if event.fwd_from: + return + msg = await event.eor(get_string("com_1")) + x = udB.get_key("OPENWEATHER_API") + if x is None: + await event.eor( + "No API found. Get One from [Here](https://api.openweathermap.org)\nAnd Add it in OPENWEATHER_API Redis Key", + time=8, + ) + return + input_str = event.pattern_match.group(1) + if not input_str: + await event.eor("`No Location was Given...`", time=5) + return + if input_str.lower() == "perth": + geo_url = f"https://geocode.xyz/perth%20au?json=1" + else: + geo_url = f"https://geocode.xyz/{input_str}?json=1" + geo_data = await async_searcher(geo_url, re_json=True) + try: + longitude = geo_data["longt"] + latitude = geo_data["latt"] + except KeyError as e: + LOGS.info(e) + await event.eor("`Unable to find coordinates for the given location.`", time=5) + return + try: + city = geo_data["standard"]["city"] + prov = geo_data["standard"]["prov"] + except KeyError as e: + LOGS.info(e) + await event.eor("`Unable to find city for the given coordinates.`", time=5) + return + air_pollution_data = await get_air_pollution_data(latitude, longitude, x) + if air_pollution_data is None: + await event.eor( + "`Unable to fetch air pollution data for the given location.`", time=5 + ) + return + await msg.edit( + f"{city}, {prov}\n\n" + f"╭────────────────•\n" + f"╰➢ **𝖠𝖰𝖨:** {air_pollution_data['main']['aqi']}\n" + f"╰➢ **𝖢𝖺𝗋𝖻𝗈𝗇 𝖬𝗈𝗇𝗈𝗑𝗂𝖽𝖾:** {air_pollution_data['components']['co']}µg/m³\n" + f"╰➢ **𝖭𝗈𝗂𝗍𝗋𝗈𝗀𝖾𝗇 𝖬𝗈𝗇𝗈𝗑𝗂𝖽𝖾:** {air_pollution_data['components']['no']}µg/m³\n" + f"╰➢ **𝖭𝗂𝗍𝗋𝗈𝗀𝖾𝗇 𝖣𝗂𝗈𝗑𝗂𝖽𝖾:** {air_pollution_data['components']['no2']}µg/m³\n" + f"╰➢ **𝖮𝗓𝗈𝗇𝖾:** {air_pollution_data['components']['o3']}µg/m³\n" + f"╰➢ **𝖲𝗎𝗅𝗉𝗁𝗎𝗋 𝖣𝗂𝗈𝗑𝗂𝖽𝖾:** {air_pollution_data['components']['so2']}µg/m³\n" + f"╰➢ **𝖠𝗆𝗆𝗈𝗇𝗂𝖺:** {air_pollution_data['components']['nh3']}µg/m³\n" + f"╰➢ **𝖥𝗂𝗇𝖾 𝖯𝖺𝗋𝗍𝗂𝖼𝗅𝖾𝗌 (PM₂.₅):** {air_pollution_data['components']['pm2_5']}\n" + f"╰➢ **𝖢𝗈𝖺𝗋𝗌𝖾 𝖯𝖺𝗋𝗍𝗂𝖼𝗅𝖾𝗌 (PM₁₀):** {air_pollution_data['components']['pm10']}\n" + f"╰────────────────•\n\n" + ) diff --git a/plugins/webupload.py b/plugins/webupload.py new file mode 100644 index 0000000000000000000000000000000000000000..1e662ca1f24d8303191ed435e66cee881498cb51 --- /dev/null +++ b/plugins/webupload.py @@ -0,0 +1,67 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>. + +""" +✘ Commands Available - + +• `{i}webupload` + Upload files on another server. +""" + +import os + +from pyUltroid.fns.tools import _webupload_cache + +from . import Button, asst, get_string, ultroid_cmd + + +@ultroid_cmd(pattern="webupload( (.*)|$)") +async def _(event): + xx = await event.eor(get_string("com_1")) + match = event.pattern_match.group(1).strip() + if event.chat_id not in _webupload_cache: + _webupload_cache.update({int(event.chat_id): {}}) + if match: + if not os.path.exists(match): + return await xx.eor("File doesn't exist.") + _webupload_cache[event.chat_id][event.id] = match + elif event.reply_to_msg_id: + reply = await event.get_reply_message() + if reply.photo: + file = await reply.download_media("resources/downloads/") + _webupload_cache[int(event.chat_id)][int(event.id)] = file + else: + file, _ = await event.client.fast_downloader( + reply.document, show_progress=True, event=xx + ) + _webupload_cache[int(event.chat_id)][int(event.id)] = file.name + else: + return await xx.eor("Reply to file or give file path...") + if not event.client._bot: + results = await event.client.inline_query( + asst.me.username, f"fl2lnk {event.chat_id}:{event.id}" + ) + await results[0].click(event.chat_id, reply_to=event.reply_to_msg_id) + await xx.delete() + + else: + __cache = f"{event.chat_id}:{event.id}" + buttons = [ + [ + Button.inline("catbox", data=f"flcatbox//{__cache}"), + Button.inline("transfer", data=f"fltransfer//{__cache}"), + ], + [ + Button.inline("filebin", data=f"flfilebin//{__cache}"), + Button.inline("0x0.st", data=f"fl0x0.st//{__cache}"), + ], + [ + Button.inline("file.io", data=f"flfile.io//{__cache}"), + Button.inline("siasky", data=f"flsiasky//{__cache}"), + ], + ] + await xx.edit("Choose Server to Upload File...", buttons=buttons) diff --git a/plugins/words.py b/plugins/words.py new file mode 100644 index 0000000000000000000000000000000000000000..16a54ab2a123c0b9f04a6c69e0da9db7ed7bc079 --- /dev/null +++ b/plugins/words.py @@ -0,0 +1,115 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>. +""" +✘ Commands Available - + +• `{i}meaning <word>` + Get the meaning of the word. + +• `{i}synonym <word>` + Get all synonyms. + +• `{i}antonym <word>` + Get all antonyms. + +• `{i}ud <word>` + Fetch word defenition from urbandictionary. +""" +import io + +from pyUltroid.fns.misc import get_synonyms_or_antonyms +from pyUltroid.fns.tools import async_searcher + +from . import get_string, ultroid_cmd + + +@ultroid_cmd(pattern="meaning( (.*)|$)", manager=True) +async def mean(event): + wrd = event.pattern_match.group(1).strip() + if not wrd: + return await event.eor(get_string("wrd_4")) + url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{wrd}" + out = await async_searcher(url, re_json=True) + try: + return await event.eor(f'**{out["title"]}**') + except (KeyError, TypeError): + pass + defi = out[0]["meanings"][0]["definitions"][0] + ex = defi["example"] if defi.get("example") else "None" + text = get_string("wrd_1").format(wrd, defi["definition"], ex) + if defi.get("synonyms"): + text += ( + f"\n\n• **{get_string('wrd_5')} :**" + + "".join(f" {a}," for a in defi["synonyms"])[:-1][:10] + ) + if defi.get("antonyms"): + text += ( + f"\n\n**{get_string('wrd_6')} :**" + + "".join(f" {a}," for a in defi["antonyms"])[:-1][:10] + ) + if len(text) > 4096: + with io.BytesIO(str.encode(text)) as fle: + fle.name = f"{wrd}-meanings.txt" + await event.reply( + file=fle, + force_document=True, + caption=f"Meanings of {wrd}", + ) + await event.delete() + else: + await event.eor(text) + + +@ultroid_cmd( + pattern="(syno|anto)nym", +) +async def mean(event): + task = event.pattern_match.group(1) + "nyms" + try: + wrd = event.text.split(maxsplit=1)[1] + except IndexError: + return await event.eor("Give Something to search..") + try: + ok = await get_synonyms_or_antonyms(wrd, task) + x = get_string("wrd_2" if task == "synonyms" else "wrd_3").format(wrd) + for c, i in enumerate(ok, start=1): + x += f"**{c}.** `{i}`\n" + if len(x) > 4096: + with io.BytesIO(str.encode(x)) as fle: + fle.name = f"{wrd}-{task}.txt" + await event.client.send_file( + event.chat_id, + fle, + force_document=True, + allow_cache=False, + caption=f"{task} of {wrd}", + reply_to=event.reply_to_msg_id, + ) + await event.delete() + else: + await event.eor(x) + except Exception as e: + await event.eor( + get_string("wrd_7" if task == "synonyms" else "wrd_8").format(e) + ) + + +@ultroid_cmd(pattern="ud (.*)") +async def _(event): + word = event.pattern_match.group(1).strip() + if not word: + return await event.eor(get_string("autopic_1")) + out = await async_searcher( + "http://api.urbandictionary.com/v0/define", params={"term": word}, re_json=True + ) + try: + out = out["list"][0] + except IndexError: + return await event.eor(get_string("autopic_2").format(word)) + await event.eor( + get_string("wrd_1").format(out["word"], out["definition"], out["example"]), + ) diff --git a/plugins/writer.py b/plugins/writer.py new file mode 100644 index 0000000000000000000000000000000000000000..6ec314d97dc8d74740897623576f88e36daa72d6 --- /dev/null +++ b/plugins/writer.py @@ -0,0 +1,86 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>. + +""" +✘ Commands Available - + +• `{i}write <text or reply to text>` + It will write on a paper. + +• `{i}image <text or reply to html or any doc file>` + Write a image from html or any text. +""" + +import os + +from htmlwebshot import WebShot +from PIL import Image, ImageDraw, ImageFont + +from . import async_searcher, eod, get_string, text_set, ultroid_cmd + + +@ultroid_cmd(pattern="gethtml( (.*)|$)") +async def ghtml(e): + if txt := e.pattern_match.group(1).strip(): + link = e.text.split(maxsplit=1)[1] + else: + return await eod(e, "`Either reply to any file or give any text`") + k = await async_searcher(link) + with open("file.html", "w+") as f: + f.write(k) + await e.reply(file="file.html") + + +@ultroid_cmd(pattern="image( (.*)|$)") +async def f2i(e): + txt = e.pattern_match.group(1).strip() + html = None + if txt: + html = e.text.split(maxsplit=1)[1] + elif e.reply_to: + r = await e.get_reply_message() + if r.media: + html = await e.client.download_media(r.media) + elif r.text: + html = r.text + if not html: + return await eod(e, "`Either reply to any file or give any text`") + html = html.replace("\n", "<br>") + shot = WebShot(quality=85) + css = "body {background: white;} p {color: red;}" + pic = await shot.create_pic_async(html=html, css=css) + await e.reply(file=pic, force_document=True) + os.remove(pic) + if os.path.exists(html): + os.remove(html) + + +@ultroid_cmd(pattern="write( (.*)|$)") +async def writer(e): + if e.reply_to: + reply = await e.get_reply_message() + text = reply.message + elif e.pattern_match.group(1).strip(): + text = e.text.split(maxsplit=1)[1] + else: + return await eod(e, get_string("writer_1")) + k = await e.eor(get_string("com_1")) + img = Image.open("resources/extras/template.jpg") + draw = ImageDraw.Draw(img) + font = ImageFont.truetype("resources/fonts/assfont.ttf", 30) + x, y = 150, 140 + lines = text_set(text) + bbox = font.getbbox("hg") + line_height = bbox[3] - bbox[1] + for line in lines: + draw.text((x, y), line, fill=(1, 22, 55), font=font) + y = y + line_height - 5 + file = "ult.jpg" + img.save(file) + await e.reply(file=file) + os.remove(file) + await k.delete() diff --git a/plugins/youtube.py b/plugins/youtube.py new file mode 100644 index 0000000000000000000000000000000000000000..31418c43e8e3f1a4e97cad3a5e1abe9548bf26c3 --- /dev/null +++ b/plugins/youtube.py @@ -0,0 +1,85 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>. +""" +✘ Commands Available - + +• `{i}yta <(youtube/any) link>` + Download audio from the link. + +• `{i}ytv <(youtube/any) link>` + Download video from the link. + +• `{i}ytsa <(youtube) search query>` + Search and download audio from youtube. + +• `{i}ytsv <(youtube) search query>` + Search and download video from youtube. +""" +from pyUltroid.fns.ytdl import download_yt, get_yt_link + +from . import get_string, requests, ultroid_cmd + + +@ultroid_cmd( + pattern="yt(a|v|sa|sv) ?(.*)", +) +async def download_from_youtube_(event): + ytd = { + "prefer_ffmpeg": True, + "addmetadata": True, + "geo-bypass": True, + "nocheckcertificate": True, + } + opt = event.pattern_match.group(1).strip() + xx = await event.eor(get_string("com_1")) + if opt == "a": + ytd["format"] = "bestaudio" + ytd["outtmpl"] = "%(id)s.m4a" + url = event.pattern_match.group(2) + if not url: + return await xx.eor(get_string("youtube_1")) + try: + requests.get(url) + except BaseException: + return await xx.eor(get_string("youtube_2")) + elif opt == "v": + ytd["format"] = "best" + ytd["outtmpl"] = "%(id)s.mp4" + ytd["postprocessors"] = [{"key": "FFmpegMetadata"}] + url = event.pattern_match.group(2) + if not url: + return await xx.eor(get_string("youtube_3")) + try: + requests.get(url) + except BaseException: + return await xx.eor(get_string("youtube_4")) + elif opt == "sa": + ytd["format"] = "bestaudio" + ytd["outtmpl"] = "%(id)s.m4a" + try: + query = event.text.split(" ", 1)[1] + except IndexError: + return await xx.eor(get_string("youtube_5")) + url = get_yt_link(query) + if not url: + return await xx.edit(get_string("unspl_1")) + await xx.eor(get_string("youtube_6")) + elif opt == "sv": + ytd["format"] = "best" + ytd["outtmpl"] = "%(id)s.mp4" + ytd["postprocessors"] = [{"key": "FFmpegMetadata"}] + try: + query = event.text.split(" ", 1)[1] + except IndexError: + return await xx.eor(get_string("youtube_7")) + url = get_yt_link(query) + if not url: + return await xx.edit(get_string("unspl_1")) + await xx.eor(get_string("youtube_8")) + else: + return + await download_yt(xx, url, ytd) diff --git a/plugins/ziptools.py b/plugins/ziptools.py new file mode 100644 index 0000000000000000000000000000000000000000..c8d69d803beb3a9fc074b756d556d5744a9a51c4 --- /dev/null +++ b/plugins/ziptools.py @@ -0,0 +1,172 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>. +""" +✘ Commands Available + +• `{i}zip <reply to file>` + zip the replied file + To set password on zip: `{i}zip <password>` reply to file + +• `{i}unzip <reply to zip file>` + unzip the replied file. + +• `{i}azip <reply to file>` + add file to batch for batch upload zip + +• `{i}dozip` + upload batch zip the files u added from `{i}azip` + To set Password: `{i}dozip <password>` + +""" +import os +import time + +from . import ( + HNDLR, + ULTConfig, + asyncio, + bash, + downloader, + get_all_files, + get_string, + ultroid_cmd, + uploader, +) + + +@ultroid_cmd(pattern="zip( (.*)|$)") +async def zipp(event): + reply = await event.get_reply_message() + t = time.time() + if not reply: + await event.eor(get_string("zip_1")) + return + xx = await event.eor(get_string("com_1")) + if reply.media: + if hasattr(reply.media, "document"): + file = reply.media.document + image = await downloader( + reply.file.name, reply.media.document, xx, t, get_string("com_5") + ) + file = image.name + else: + file = await event.download_media(reply) + inp = file.replace(file.split(".")[-1], "zip") + if event.pattern_match.group(1).strip(): + await bash( + f"zip -r --password {event.pattern_match.group(1).strip()} {inp} {file}" + ) + else: + await bash(f"zip -r {inp} {file}") + k = time.time() + n_file, _ = await event.client.fast_uploader( + inp, show_progress=True, event=event, message="Uploading...", to_delete=True + ) + await event.client.send_file( + event.chat_id, + n_file, + force_document=True, + thumb=ULTConfig.thumb, + caption=f"`{n_file.name}`", + reply_to=reply, + ) + os.remove(inp) + os.remove(file) + await xx.delete() + + +@ultroid_cmd(pattern="unzip( (.*)|$)") +async def unzipp(event): + reply = await event.get_reply_message() + file = event.pattern_match.group(1).strip() + t = time.time() + if not ((reply and reply.media) or file): + await event.eor(get_string("zip_1")) + return + xx = await event.eor(get_string("com_1")) + if reply.media: + if not hasattr(reply.media, "document"): + return await xx.edit(get_string("zip_3")) + file = reply.media.document + if not reply.file.name.endswith(("zip", "rar", "exe")): + return await xx.edit(get_string("zip_3")) + image = await downloader( + reply.file.name, reply.media.document, xx, t, get_string("com_5") + ) + file = image.name + if os.path.isdir("unzip"): + await bash("rm -rf unzip") + os.mkdir("unzip") + await bash(f"7z x {file} -aoa -ounzip") + await asyncio.sleep(4) + ok = get_all_files("unzip") + for x in ok: + k = time.time() + n_file, _ = await event.client.fast_uploader( + x, show_progress=True, event=event, message="Uploading...", to_delete=True + ) + await event.client.send_file( + event.chat_id, + n_file, + force_document=True, + thumb=ULTConfig.thumb, + caption=f"`{n_file.name}`", + ) + await xx.delete() + + +@ultroid_cmd(pattern="addzip$") +async def azipp(event): + reply = await event.get_reply_message() + t = time.time() + if not (reply and reply.media): + await event.eor(get_string("zip_1")) + return + xx = await event.eor(get_string("com_1")) + if not os.path.isdir("zip"): + os.mkdir("zip") + if reply.media: + if hasattr(reply.media, "document"): + file = reply.media.document + image = await downloader( + f"zip/{reply.file.name}", + reply.media.document, + xx, + t, + get_string("com_5"), + ) + + file = image.name + else: + file = await event.download_media(reply.media, "zip/") + await xx.edit( + f"Downloaded `{file}` succesfully\nNow Reply To Other Files To Add And Zip all at once" + ) + + +@ultroid_cmd(pattern="dozip( (.*)|$)") +async def do_zip(event): + if not os.path.isdir("zip"): + return await event.eor(get_string("zip_2").format(HNDLR)) + xx = await event.eor(get_string("com_1")) + if event.pattern_match.group(1).strip(): + await bash( + f"zip -r --password {event.pattern_match.group(1).strip()} ultroid.zip zip/*" + ) + else: + await bash("zip -r ultroid.zip zip/*") + k = time.time() + xxx = await uploader("ultroid.zip", "ultroid.zip", k, xx, get_string("com_6")) + await event.client.send_file( + event.chat_id, + xxx, + force_document=True, + thumb=ULTConfig.thumb, + ) + await bash("rm -rf zip") + os.remove("ultroid.zip") + await xx.delete() diff --git a/public/app.js b/public/app.js deleted file mode 100644 index 4261220977d3953e8036f3734123e4449ac75156..0000000000000000000000000000000000000000 --- a/public/app.js +++ /dev/null @@ -1,426 +0,0 @@ -let token = sessionStorage.getItem('shellular_token') || null; -let evtSource = null; -let fullOutput = ''; -let shellStatus = 'stopped'; -let qrRendered = false; - -const $ = (id) => document.getElementById(id); - -const loginPage = $('login-page'); -const dashPage = $('dashboard-page'); -const loginForm = $('login-form'); -const keyInput = $('key-input'); -const loginBtn = $('login-btn'); -const loginLabel = $('login-label'); -const loginSpinner = $('login-spinner'); -const loginError = $('login-error'); -const toggleVisBtn = $('toggle-vis'); -const eyeOpen = $('eye-open'); -const eyeClosed = $('eye-closed'); -const statusBadge = $('status-badge'); -const restartBtn = $('restart-btn'); -const logoutBtn = $('logout-btn'); -const clearLogBtn = $('clear-log-btn'); -const qrLoading = $('qr-loading'); -const qrReady = $('qr-ready'); -const qrError = $('qr-error'); -const qrErrorMsg = $('qr-error-msg'); -const qrCanvas = $('qr-canvas'); -const logPre = $('log-pre'); - -function showLogin() { - loginPage.classList.remove('hidden'); - dashPage.classList.add('hidden'); - keyInput.focus(); -} - -function showDashboard() { - loginPage.classList.add('hidden'); - dashPage.classList.remove('hidden'); - connectStream(); - ensureShellularRunning(); -} - -toggleVisBtn.addEventListener('click', () => { - const isPassword = keyInput.type === 'password'; - keyInput.type = isPassword ? 'text' : 'password'; - eyeOpen.classList.toggle('hidden', isPassword); - eyeClosed.classList.toggle('hidden', !isPassword); -}); - -loginForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const key = keyInput.value.trim(); - if (!key) return; - - loginBtn.disabled = true; - loginLabel.classList.add('hidden'); - loginSpinner.classList.remove('hidden'); - loginError.classList.add('hidden'); - - try { - const res = await fetch('/api/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ key }), - }); - - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.error || 'Login failed'); - } - - token = data.token; - sessionStorage.setItem('shellular_token', token); - keyInput.value = ''; - showDashboard(); - } catch (err) { - loginError.textContent = err.message; - loginError.classList.remove('hidden'); - } finally { - loginBtn.disabled = false; - loginLabel.classList.remove('hidden'); - loginSpinner.classList.add('hidden'); - } -}); - -logoutBtn.addEventListener('click', async () => { - if (evtSource) { evtSource.close(); evtSource = null; } - await fetch('/api/logout', { - method: 'POST', - headers: { Authorization: `Bearer ${token}` }, - }).catch(() => {}); - token = null; - sessionStorage.removeItem('shellular_token'); - fullOutput = ''; - logPre.textContent = ''; - qrPre.textContent = ''; - showLogin(); -}); - -function connectStream() { - if (evtSource) { evtSource.close(); } - - evtSource = new EventSource(`/api/stream?t=${Date.now()}`, {}); - - - - - evtSource.close(); - evtSource = null; - startFetchSSE(); -} - -async function startFetchSSE() { - try { - const res = await fetch('/api/stream', { - headers: { Authorization: `Bearer ${token}` }, - cache: 'no-store', - }); - - if (res.status === 401) { - sessionStorage.removeItem('shellular_token'); - token = null; - showLogin(); - return; - } - - if (!res.ok || !res.body) { - setTimeout(startFetchSSE, 3000); - return; - } - - const reader = res.body.getReader(); - const decoder = new TextDecoder(); - let partial = ''; - - while (true) { - const { value, done } = await reader.read(); - if (done) break; - - partial += decoder.decode(value, { stream: true }); - const parts = partial.split('\n\n'); - partial = parts.pop(); - - for (const part of parts) { - const line = part.trim(); - if (!line.startsWith('data:')) continue; - try { - const payload = JSON.parse(line.slice(5).trim()); - handleEvent(payload); - } catch { } - } - } - } catch { - - } - - setTimeout(startFetchSSE, 2000); -} - -function handleEvent(payload) { - if (payload.type === 'status') { - updateStatus(payload.status); - } else if (payload.type === 'output') { - appendOutput(payload.text); - } else if (payload.type === 'clear') { - fullOutput = ''; - logPre.textContent = ''; - qrRendered = false; - setQrState('loading'); - } -} - -function updateStatus(status) { - shellStatus = status; - - const labels = { - running: 'Running', - starting: 'Starting', - retrying: 'Retrying…', - stopped: 'Stopped', - error: 'Error', - }; - statusBadge.textContent = labels[status] || status; - statusBadge.className = `badge badge-${status}`; - - if (status === 'retrying') { - if (!qrRendered) setQrState('loading'); - return; - } - - if (status === 'stopped' || status === 'error') { - if (!qrRendered) { - setQrState('error'); - qrErrorMsg.textContent = - status === 'error' - ? 'Shellular failed to start. Check the output log for details.' - : 'Shellular stopped. Click "Try again" to restart.'; - } - } - - if (status === 'starting') { - if (!qrRendered) setQrState('loading'); - } - - - if (status === 'running' && !qrRendered) { - fetchAndRenderQR(); - } -} - -function setQrState(state) { - qrLoading.classList.toggle('hidden', state !== 'loading'); - qrReady.classList.toggle('hidden', state !== 'ready'); - qrError.classList.toggle('hidden', state !== 'error'); -} - -async function fetchAndRenderQR() { - for (let i = 0; i < 8; i++) { - await new Promise(r => setTimeout(r, i === 0 ? 1000 : 2500)); - try { - const res = await authFetch('/api/shellular/qr-data'); - if (!res || !res.ok) continue; - const json = await res.json(); - const qrData = json.qrData; - if (!qrData) continue; - - - setQrState('ready'); - - - qrCanvas.innerHTML = ''; - - new QRCode(qrCanvas, { - text: qrData, - width: 220, - height: 220, - colorDark: '#000000', - colorLight: '#ffffff', - correctLevel: QRCode.CorrectLevel.M, - }); - qrRendered = true; - return; - } catch (err) { - console.warn('fetchAndRenderQR attempt', i, err); - } - } - if (!qrRendered) { - setQrState('error'); - qrErrorMsg.textContent = 'Could not render QR. Try restarting.'; - } -} - -function appendOutput(text) { - if (!text) return; - fullOutput += text; - logPre.textContent = fullOutput; - logPre.scrollTop = logPre.scrollHeight; - - - if (text.includes('rate-limited') || text.includes('Registration rate-limited')) { - rateLimitCount++; - if (rateLimitCount >= 1) loadManualCard(); - } -} - -restartBtn.addEventListener('click', restartShellular); - -async function restartShellular() { - fullOutput = ''; - logPre.textContent = ''; - qrRendered = false; - setQrState('loading'); - await authFetch('/api/shellular/restart', 'POST'); -} - -clearLogBtn.addEventListener('click', () => { - logPre.textContent = ''; -}); - -async function authFetch(url, method = 'GET') { - const res = await fetch(url, { - method, - headers: { Authorization: `Bearer ${token}` }, - }); - if (res.status === 401) { - token = null; - sessionStorage.removeItem('shellular_token'); - showLogin(); - } - return res; -} - -const manualCard = $('manual-reg-card'); -const manualCurlCmd = $('manual-curl-cmd'); -const manualHostInput = $('manual-host-id'); -const manualSubmitBtn = $('manual-submit-btn'); -const manualError = $('manual-error'); - -let rateLimitCount = 0; -let machineIdLoaded = false; - -async function loadManualCard() { - if (machineIdLoaded) { manualCard.classList.remove('hidden'); return; } - try { - const res = await fetch('/api/shellular/machine-id'); - const data = await res.json(); - const machineId = data.machineId; - if (!machineId) { - manualCurlCmd.textContent = 'Set the MACHINE_ID_RAW environment variable first, then restart the service.'; - manualCard.classList.remove('hidden'); - return; - } - const cmd = `curl -s -X POST "https://api.shellular.dev/host/register" -H "Content-Type: application/json" -H "User-Agent: shellular/0.0.19" -d '{"machineId":"${machineId}","platform":"linux"}'`; - manualCurlCmd.textContent = cmd; - machineIdLoaded = true; - manualCard.classList.remove('hidden'); - } catch { } -} - -manualSubmitBtn.addEventListener('click', async () => { - const hostId = manualHostInput.value.trim(); - if (!hostId) { - manualError.textContent = 'Please enter the hostId from the curl response.'; - manualError.classList.remove('hidden'); - return; - } - manualError.classList.add('hidden'); - manualSubmitBtn.disabled = true; - manualSubmitBtn.textContent = 'Connecting…'; - - try { - const r = await fetch('/api/shellular/seed-host', { - method: 'POST', - headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, - body: JSON.stringify({ hostId }), - }); - const data = await r.json(); - if (!r.ok) throw new Error(data.error || 'Failed'); - - manualCard.classList.add('hidden'); - rateLimitCount = 0; - - fullOutput = ''; - logPre.textContent = ''; - qrRendered = false; - setQrState('loading'); - } catch (err) { - manualError.textContent = err.message; - manualError.classList.remove('hidden'); - } finally { - manualSubmitBtn.disabled = false; - manualSubmitBtn.textContent = 'Connect'; - } -}); - -const setupCard = $('setup-card'); -let setupDone = false; - -async function checkSetup() { - if (setupDone || !token) return; - try { - const r1 = await authFetch('/api/setup-status'); - if (!r1 || !r1.ok) return; - const { seeded } = await r1.json(); - if (seeded) { setupDone = true; return; } - - - const r2 = await authFetch('/api/shellular/credentials'); - if (!r2 || !r2.ok) return; - const data = await r2.json(); - if (!data.hostId) return; - - - const r3 = await fetch('/api/shellular/raw-machine-id'); - const { raw } = r3.ok ? await r3.json() : {}; - - $('val-raw-machine-id').textContent = raw || ''; - $('val-host-id').textContent = data.hostId; - $('val-machine-id').textContent = data.machineId; - $('val-key').textContent = data.keyB64; - setupCard.classList.remove('hidden'); - setupDone = true; - } catch { } -} - -setInterval(checkSetup, 4000); - -document.addEventListener('click', (e) => { - const btn = e.target.closest('.btn-copy'); - if (!btn) return; - const val = $( btn.dataset.target )?.textContent || ''; - navigator.clipboard.writeText(val).then(() => { - btn.textContent = 'Copied!'; - btn.classList.add('copied'); - setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000); - }); -}); - -async function ensureShellularRunning() { - const res = await authFetch('/api/status'); - if (!res) return; - const { running } = await res.json(); - if (!running) { - await authFetch('/api/shellular/start', 'POST'); - } -} - -if (token) { - fetch('/api/status', { headers: { Authorization: `Bearer ${token}` } }) - .then((r) => { - if (r.status === 401) { - token = null; - sessionStorage.removeItem('shellular_token'); - showLogin(); - } else { - showDashboard(); - } - }) - .catch(() => showLogin()); -} else { - showLogin(); -} - -window.restartShellular = restartShellular; diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 520340e1d8f61eee056b588741389f92d15c16eb..0000000000000000000000000000000000000000 --- a/public/index.html +++ /dev/null @@ -1,206 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Shellular - - - - - -
- -
- - - - - - - - diff --git a/public/qrcode.min.js b/public/qrcode.min.js deleted file mode 100644 index 993e88f396640f881b69f98db7a4d17401ef83ca..0000000000000000000000000000000000000000 --- a/public/qrcode.min.js +++ /dev/null @@ -1 +0,0 @@ -var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/public/style.css b/public/style.css deleted file mode 100644 index e3208769cb0e227b1c0fa7a3da3a0951f915a233..0000000000000000000000000000000000000000 --- a/public/style.css +++ /dev/null @@ -1,489 +0,0 @@ -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - -:root { - --bg: #0d0f14; - --surface: #161b25; - --surface2: #1e2533; - --border: #2a3347; - --accent: #00c4cc; - --accent-dk: #009ba1; - --text: #e2e8f0; - --muted: #8896ab; - --error: #ff5f6d; - --success: #4ade80; - --warning: #facc15; - --radius: 12px; - --radius-sm: 8px; - --font-mono: 'Cascadia Code', 'Fira Code', 'Courier New', Courier, monospace; -} - -html, body { - height: 100%; - background: var(--bg); - color: var(--text); - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif; - font-size: 15px; - line-height: 1.6; - -webkit-font-smoothing: antialiased; -} - -.page { min-height: 100vh; } -.hidden { display: none !important; } - -#login-page { - display: flex; - align-items: center; - justify-content: center; - padding: 24px; - background: - radial-gradient(ellipse at 20% 60%, rgba(0,196,204,.08) 0%, transparent 60%), - radial-gradient(ellipse at 80% 20%, rgba(0,196,204,.05) 0%, transparent 50%), - var(--bg); -} - -.login-card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 40px 36px; - width: 100%; - max-width: 400px; - box-shadow: 0 24px 64px rgba(0,0,0,.5); -} - -.brand { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 8px; -} - -.brand h1 { - font-size: 24px; - font-weight: 700; - letter-spacing: -0.5px; -} - -.brand-icon { - width: 40px; - height: 40px; - border-radius: 10px; - flex-shrink: 0; -} -.brand-icon.small { width: 28px; height: 28px; border-radius: 7px; } - -.tagline { - color: var(--muted); - font-size: 13.5px; - margin-bottom: 28px; -} - -.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; } - -.field label { - font-size: 13px; - font-weight: 500; - color: var(--muted); - letter-spacing: .4px; - text-transform: uppercase; -} - -.input-wrap { position: relative; display: flex; } - -.input-wrap input { - width: 100%; - background: var(--surface2); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - color: var(--text); - font-size: 15px; - padding: 11px 44px 11px 14px; - outline: none; - transition: border-color .15s, box-shadow .15s; -} - -.input-wrap input:focus { - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(0,196,204,.15); -} - -.input-wrap input::placeholder { color: var(--muted); opacity: .6; } - -.eye-btn { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - cursor: pointer; - color: var(--muted); - display: flex; - align-items: center; - padding: 4px; - border-radius: 4px; - transition: color .15s; -} -.eye-btn:hover { color: var(--text); } -.eye-btn svg { width: 18px; height: 18px; } - -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border: none; - border-radius: var(--radius-sm); - cursor: pointer; - font-size: 14px; - font-weight: 600; - padding: 10px 18px; - transition: background .15s, opacity .15s, transform .1s; - white-space: nowrap; - text-decoration: none; -} -.btn:active { transform: scale(.97); } -.btn:disabled { opacity: .5; cursor: not-allowed; } - -.btn-primary, #login-btn { - background: var(--accent); - color: #0d0f14; - width: 100%; - padding: 12px; - font-size: 15px; - margin-top: 8px; - border: none; - border-radius: var(--radius-sm); - cursor: pointer; - font-weight: 700; - transition: background .15s, transform .1s; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; -} -.btn-primary:hover, #login-btn:hover { background: var(--accent-dk); } -.btn-primary:active, #login-btn:active { transform: scale(.98); } -.btn-primary:disabled, #login-btn:disabled { opacity: .5; cursor: not-allowed; } - -.btn-secondary { - background: var(--surface2); - color: var(--text); - border: 1px solid var(--border); -} -.btn-secondary:hover { background: var(--border); } - -.btn-ghost { - background: transparent; - color: var(--muted); - border: none; - padding: 8px 12px; -} -.btn-ghost:hover { color: var(--text); background: var(--surface2); } -.btn-ghost.small { font-size: 12px; padding: 4px 10px; } - -.error-msg { - color: var(--error); - font-size: 13px; - margin-top: 10px; - text-align: center; - min-height: 20px; -} - -.spinner { - width: 16px; - height: 16px; - border: 2px solid rgba(0,0,0,.25); - border-top-color: #0d0f14; - border-radius: 50%; - animation: spin .7s linear infinite; - flex-shrink: 0; -} -@keyframes spin { to { transform: rotate(360deg); } } - -.topbar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 14px 24px; - background: var(--surface); - border-bottom: 1px solid var(--border); - position: sticky; - top: 0; - z-index: 10; -} - -.topbar-brand { - display: flex; - align-items: center; - gap: 10px; - font-weight: 700; - font-size: 16px; - letter-spacing: -0.3px; -} - -.topbar-actions { display: flex; align-items: center; gap: 10px; } - -.badge { - font-size: 12px; - font-weight: 600; - padding: 3px 10px; - border-radius: 20px; - letter-spacing: .3px; - text-transform: uppercase; -} -.badge-running { background: rgba(74,222,128,.15); color: var(--success); } -.badge-starting { background: rgba(250,204,21,.15); color: var(--warning); } -.badge-retrying { background: rgba(250,204,21,.15); color: var(--warning); } -.badge-stopped { background: rgba(255,95,109,.12); color: var(--error); } -.badge-error { background: rgba(255,95,109,.12); color: var(--error); } - -.dashboard { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 20px; - padding: 24px; - max-width: 1100px; - margin: 0 auto; -} - -@media (max-width: 720px) { - .dashboard { grid-template-columns: 1fr; } -} - -.setup-card { grid-column: 1 / -1; } - -.card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius); - overflow: hidden; - display: flex; - flex-direction: column; -} - -.card-header { - display: flex; - align-items: baseline; - justify-content: space-between; - padding: 18px 20px 12px; - border-bottom: 1px solid var(--border); -} - -.card-header h2 { - font-size: 16px; - font-weight: 700; -} - -.card-header p { - font-size: 13px; - color: var(--muted); - margin-top: 2px; -} - -.qr-area { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - padding: 28px 20px; - min-height: 300px; -} - -.qr-state { - display: flex; - flex-direction: column; - align-items: center; - gap: 14px; - text-align: center; -} - -.qr-state p { color: var(--muted); font-size: 14px; } - -.loader { - width: 44px; - height: 44px; - border: 3px solid var(--border); - border-top-color: var(--accent); - border-radius: 50%; - animation: spin .9s linear infinite; -} - -.qr-frame { - background: #ffffff; - border-radius: 10px; - padding: 16px; - box-shadow: 0 0 0 4px rgba(255,255,255,.06); - display: inline-block; -} - -#qr-pre { - font-family: var(--font-mono); - font-size: 7px; - line-height: 1; - color: #000000; - background: transparent; - white-space: pre; - display: block; - letter-spacing: 0; -} - -.qr-hint { - font-size: 12px !important; - color: var(--muted) !important; -} - -.error-icon { - font-size: 36px; - color: var(--error) !important; -} - -.manual-card { grid-column: 1 / -1; border-color: rgba(250,204,21,.3); } - -.manual-body { padding: 16px 20px 22px; display: flex; flex-direction: column; gap: 18px; } - -.manual-intro { font-size: 13.5px; color: var(--muted); line-height: 1.6; } - -.manual-step { - display: flex; - gap: 14px; - align-items: flex-start; -} -.manual-step > div { display: flex; flex-direction: column; gap: 8px; flex: 1; } -.manual-step p { font-size: 13.5px; color: var(--muted); line-height: 1.6; } - -.step-num { - width: 26px; height: 26px; - border-radius: 50%; - background: var(--accent); - color: #0d0f14; - font-size: 13px; - font-weight: 700; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-top: 2px; -} - -.code-block { - display: flex; - align-items: center; - gap: 10px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - padding: 10px 14px; - flex-wrap: wrap; -} -.code-block code { - font-family: var(--font-mono); - font-size: 12px; - color: var(--text); - flex: 1; - word-break: break-all; -} - -.inline-code { - font-family: var(--font-mono); - font-size: 12px; - background: var(--surface2); - border: 1px solid var(--border); - border-radius: 4px; - padding: 1px 5px; -} - -.manual-input-row { - display: flex; - gap: 10px; - align-items: center; - flex-wrap: wrap; -} -.manual-input-row input { - flex: 1; - min-width: 180px; - background: var(--surface2); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - color: var(--text); - font-family: var(--font-mono); - font-size: 14px; - padding: 9px 12px; - outline: none; - transition: border-color .15s; -} -.manual-input-row input:focus { border-color: var(--accent); } -.manual-btn { padding: 9px 20px; } - -.setup-body { padding: 16px 20px 20px; display: flex; flex-direction: column; gap: 14px; } - -.setup-intro { font-size: 13.5px; color: var(--muted); line-height: 1.6; } - -.setup-link { color: var(--accent); text-decoration: none; } -.setup-link:hover { text-decoration: underline; } - -.secret-row { - display: flex; - align-items: center; - gap: 10px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - padding: 10px 14px; - flex-wrap: wrap; -} - -.secret-name { - font-family: var(--font-mono); - font-size: 12px; - color: var(--accent); - min-width: 200px; - font-weight: 600; -} - -.secret-val { - font-family: var(--font-mono); - font-size: 12px; - color: var(--text); - flex: 1; - word-break: break-all; - min-width: 0; -} - -.btn-copy { - background: var(--surface2); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--muted); - cursor: pointer; - font-size: 12px; - font-weight: 600; - padding: 4px 12px; - white-space: nowrap; - transition: background .15s, color .15s; - flex-shrink: 0; -} -.btn-copy:hover { background: var(--border); color: var(--text); } -.btn-copy.copied { color: var(--success); border-color: var(--success); } - -.setup-note { font-size: 12px; color: var(--muted); font-style: italic; } - -.log-card { min-height: 300px; } - -.log-pre { - flex: 1; - font-family: var(--font-mono); - font-size: 12px; - color: var(--muted); - background: var(--bg); - padding: 16px; - overflow-y: auto; - max-height: 480px; - white-space: pre-wrap; - word-break: break-word; - line-height: 1.5; -} - -.log-pre::-webkit-scrollbar { width: 6px; } -.log-pre::-webkit-scrollbar-track { background: transparent; } -.log-pre::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } diff --git a/pyUltroid/__init__.py b/pyUltroid/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a6d8267e6099adb3b964d974949d41fcd26ad6f9 --- /dev/null +++ b/pyUltroid/__init__.py @@ -0,0 +1,108 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import os +import sys +import telethonpatch +from .version import __version__ + +run_as_module = __package__ in sys.argv or sys.argv[0] == "-m" + + +class ULTConfig: + lang = "en" + thumb = "resources/extras/ultroid.jpg" + + +if run_as_module: + import time + + from .configs import Var + from .startup import * + from .startup._database import UltroidDB + from .startup.BaseClient import UltroidClient + from .startup.connections import validate_session, vc_connection + from .startup.funcs import _version_changes, autobot, enable_inline, update_envs + from .version import ultroid_version + + if not os.path.exists("./plugins"): + LOGS.error( + "'plugins' folder not found!\nMake sure that, you are on correct path." + ) + exit() + + start_time = time.time() + _ult_cache = {} + _ignore_eval = [] + + udB = UltroidDB() + update_envs() + + LOGS.info(f"Connecting to {udB.name}...") + if udB.ping(): + LOGS.info(f"Connected to {udB.name} Successfully!") + + BOT_MODE = udB.get_key("BOTMODE") + DUAL_MODE = udB.get_key("DUAL_MODE") + + USER_MODE = udB.get_key("USER_MODE") + if USER_MODE: + DUAL_MODE = False + + if BOT_MODE: + if DUAL_MODE: + udB.del_key("DUAL_MODE") + DUAL_MODE = False + ultroid_bot = None + + if not udB.get_key("BOT_TOKEN"): + LOGS.critical( + '"BOT_TOKEN" not Found! Please add it, in order to use "BOTMODE"' + ) + + sys.exit() + else: + ultroid_bot = UltroidClient( + validate_session(Var.SESSION, LOGS), + udB=udB, + app_version=ultroid_version, + device_model="Ultroid", + ) + ultroid_bot.run_in_loop(autobot()) + + if USER_MODE: + asst = ultroid_bot + else: + asst = UltroidClient("asst", bot_token=udB.get_key("BOT_TOKEN"), udB=udB) + + if BOT_MODE: + ultroid_bot = asst + if udB.get_key("OWNER_ID"): + try: + ultroid_bot.me = ultroid_bot.run_in_loop( + ultroid_bot.get_entity(udB.get_key("OWNER_ID")) + ) + except Exception as er: + LOGS.exception(er) + elif not asst.me.bot_inline_placeholder and asst._bot: + ultroid_bot.run_in_loop(enable_inline(ultroid_bot, asst.me.username)) + + vcClient = vc_connection(udB, ultroid_bot) + + _version_changes(udB) + + HNDLR = udB.get_key("HNDLR") or "." + DUAL_HNDLR = udB.get_key("DUAL_HNDLR") or "/" + SUDO_HNDLR = udB.get_key("SUDO_HNDLR") or HNDLR +else: + print("pyUltroid 2022 © TeamUltroid") + + from logging import getLogger + + LOGS = getLogger("pyUltroid") + + ultroid_bot = asst = udB = vcClient = None diff --git a/pyUltroid/__main__.py b/pyUltroid/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..82e67c856255ee2f8be1795e6d50e13472771e36 --- /dev/null +++ b/pyUltroid/__main__.py @@ -0,0 +1,107 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import * + + +def main(): + import os + import sys + import time + + from .fns.helper import bash, time_formatter, updater + from .startup.funcs import ( + WasItRestart, + autopilot, + customize, + keep_redis_alive, + plug, + ready, + startup_stuff, + ) + from .startup.loader import load_other_plugins + + try: + from apscheduler.schedulers.asyncio import AsyncIOScheduler + except ImportError: + AsyncIOScheduler = None + + # Option to Auto Update On Restarts.. + if ( + udB.get_key("UPDATE_ON_RESTART") + and os.path.exists(".git") + and ultroid_bot.run_in_loop(updater()) + ): + ultroid_bot.run_in_loop(bash("bash installer.sh")) + + os.execl(sys.executable, sys.executable, "-m", "pyUltroid") + + ultroid_bot.run_in_loop(startup_stuff()) + + ultroid_bot.me.phone = None + + if not ultroid_bot.me.bot: + udB.set_key("OWNER_ID", ultroid_bot.uid) + + LOGS.info("Initialising...") + + ultroid_bot.run_in_loop(autopilot()) + ultroid_bot.loop.create_task(keep_redis_alive()) + + pmbot = udB.get_key("PMBOT") + manager = udB.get_key("MANAGER") + addons = udB.get_key("ADDONS") or Var.ADDONS + vcbot = udB.get_key("VCBOT") or Var.VCBOT + if HOSTED_ON == "okteto": + vcbot = False + + if (HOSTED_ON == "termux" or udB.get_key("LITE_DEPLOY")) and udB.get_key( + "EXCLUDE_OFFICIAL" + ) is None: + _plugins = "autocorrect autopic audiotools compressor forcesubscribe fedutils gdrive glitch instagram nsfwfilter nightmode pdftools profanityfilter writer youtube" + udB.set_key("EXCLUDE_OFFICIAL", _plugins) + + load_other_plugins(addons=addons, pmbot=pmbot, manager=manager, vcbot=vcbot) + + suc_msg = """ + ---------------------------------------------------------------------- + Ultroid has been deployed! Visit @TheUltroid for updates!! + ---------------------------------------------------------------------- + """ + + # for channel plugins + plugin_channels = udB.get_key("PLUGIN_CHANNEL") + + # Customize Ultroid Assistant... + ultroid_bot.run_in_loop(customize()) + + # Load Addons from Plugin Channels. + if plugin_channels: + ultroid_bot.run_in_loop(plug(plugin_channels)) + + # Send/Ignore Deploy Message.. + if not udB.get_key("LOG_OFF"): + ultroid_bot.run_in_loop(ready()) + + # Edit Restarting Message (if It's restarting) + ultroid_bot.run_in_loop(WasItRestart(udB)) + + try: + udB.re_cache() + except BaseException: + pass + + LOGS.info( + f"Took {time_formatter((time.time() - start_time)*1000)} to start •ULTROID•" + ) + LOGS.info(suc_msg) + + +if __name__ == "__main__": + main() + + asst.run() diff --git a/pyUltroid/_misc/__init__.py b/pyUltroid/_misc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..771e973ba7964d417217269747ad93c7289ce121 --- /dev/null +++ b/pyUltroid/_misc/__init__.py @@ -0,0 +1,74 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from .. import * + +CMD_HELP = {} +# ----------------------------------------------# + + +class _SudoManager: + def __init__(self): + self.db = None + self.owner = None + self._owner_sudos = [] + + def _init_db(self): + if not self.db: + from .. import udB + + self.db = udB + return self.db + + def get_sudos(self): + db = self._init_db() + SUDOS = db.get_key("SUDOS") + return SUDOS or [] + + @property + def should_allow_sudo(self): + db = self._init_db() + return db.get_key("SUDO") + + def owner_and_sudos(self): + if not self.owner: + db = self._init_db() + self.owner = db.get_key("OWNER_ID") + return [self.owner, *self.get_sudos()] + + @property + def fullsudos(self): + db = self._init_db() + fsudos = db.get("FULLSUDO") + if not self.owner: + self.owner = db.get_key("OWNER_ID") + if not fsudos: + return [self.owner] + fsudos = fsudos.split() + fsudos.append(self.owner) + return [int(_) for _ in fsudos] + + def is_sudo(self, id_): + return bool(id_ in self.get_sudos()) + + +SUDO_M = _SudoManager() +owner_and_sudos = SUDO_M.owner_and_sudos +sudoers = SUDO_M.get_sudos +is_sudo = SUDO_M.is_sudo + +# ------------------------------------------------ # + + +def append_or_update(load, func, name, arggs): + if isinstance(load, list): + return load.append(func) + if isinstance(load, dict): + if load.get(name): + return load[name].append((func, arggs)) + return load.update({name: [(func, arggs)]}) diff --git a/pyUltroid/_misc/_assistant.py b/pyUltroid/_misc/_assistant.py new file mode 100644 index 0000000000000000000000000000000000000000..b5f8032ef0587fe166f7626e8aa94e22debd3b9b --- /dev/null +++ b/pyUltroid/_misc/_assistant.py @@ -0,0 +1,152 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import inspect +import re +from traceback import format_exc + +from telethon import Button +from telethon.errors import QueryIdInvalidError +from telethon.events import CallbackQuery, InlineQuery, NewMessage +from telethon.tl.types import InputWebDocument + +from .. import LOGS, asst, udB, ultroid_bot +from ..fns.admins import admin_check +from . import append_or_update, owner_and_sudos + +OWNER = ultroid_bot.full_name + +MSG = f""" +**Ultroid - UserBot** +➖➖➖➖➖➖➖➖➖➖ +**Owner**: [{OWNER}](tg://user?id={ultroid_bot.uid}) +**Support**: @TeamUltroid +➖➖➖➖➖➖➖➖➖➖ +""" + +IN_BTTS = [ + [ + Button.url( + "Repository", + url="https://github.com/TeamUltroid/Ultroid", + ), + Button.url("Support", url="https://t.me/UltroidSupportChat"), + ] +] + + +# decorator for assistant + + +def asst_cmd(pattern=None, load=None, owner=False, **kwargs): + """Decorator for assistant's command""" + name = inspect.stack()[1].filename.split("/")[-1].replace(".py", "") + kwargs["forwards"] = False + + def ult(func): + if pattern: + kwargs["pattern"] = re.compile(f"^/{pattern}") + + async def handler(event): + if owner and event.sender_id not in owner_and_sudos(): + return + try: + await func(event) + except Exception as er: + LOGS.exception(er) + + asst.add_event_handler(handler, NewMessage(**kwargs)) + if load is not None: + append_or_update(load, func, name, kwargs) + + return ult + + +def callback(data=None, from_users=None, admins=False, owner=False, **kwargs): + """Assistant's callback decorator""" + if from_users is None: + from_users = [] + if "me" in from_users: + from_users.remove("me") + from_users.append(ultroid_bot.uid) + + def ultr(func): + async def wrapper(event): + if admins and not await admin_check(event): + return + if from_users and event.sender_id not in from_users: + return await event.answer("Not for You!", alert=True) + if owner and event.sender_id not in owner_and_sudos(): + return await event.answer(f"This is {OWNER}'s bot!!") + try: + await func(event) + except Exception as er: + LOGS.exception(er) + + asst.add_event_handler(wrapper, CallbackQuery(data=data, **kwargs)) + + return ultr + + +def in_pattern(pattern=None, owner=False, **kwargs): + """Assistant's inline decorator.""" + + def don(func): + async def wrapper(event): + if owner and event.sender_id not in owner_and_sudos(): + res = [ + await event.builder.article( + title="Ultroid Userbot", + url="https://t.me/TeamUltroid", + description="(c) TeamUltroid", + text=MSG, + thumb=InputWebDocument( + "https://graph.org/file/dde85d441fa051a0d7d1d.jpg", + 0, + "image/jpeg", + [], + ), + buttons=IN_BTTS, + ) + ] + return await event.answer( + res, + switch_pm=f"🤖: Assistant of {OWNER}", + switch_pm_param="start", + ) + try: + await func(event) + except QueryIdInvalidError: + pass + except Exception as er: + err = format_exc() + + def error_text(): + return f"**#ERROR #INLINE**\n\nQuery: `{asst.me.username} {pattern}`\n\n**Traceback:**\n`{format_exc()}`" + + LOGS.exception(er) + try: + await event.answer( + [ + await event.builder.article( + title="Unhandled Exception has Occured!", + text=error_text(), + buttons=Button.url( + "Report", "https://t.me/UltroidSupportChat" + ), + ) + ] + ) + except QueryIdInvalidError: + LOGS.exception(err) + except Exception as er: + LOGS.exception(er) + await asst.send_message(udB.get_key("LOG_CHANNEL"), error_text()) + + asst.add_event_handler(wrapper, InlineQuery(pattern=pattern, **kwargs)) + + return don diff --git a/pyUltroid/_misc/_decorators.py b/pyUltroid/_misc/_decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..2a92aa6dce0c1127b7353895a81e282258053667 --- /dev/null +++ b/pyUltroid/_misc/_decorators.py @@ -0,0 +1,346 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import asyncio +import inspect +import re +import sys +from io import BytesIO +from pathlib import Path +from time import gmtime, strftime +from traceback import format_exc + +from telethon import Button +from telethon import __version__ as telever +from telethon import events +from telethon.errors.common import AlreadyInConversationError +from telethon.errors.rpcerrorlist import ( + AuthKeyDuplicatedError, + BotInlineDisabledError, + BotMethodInvalidError, + ChatSendInlineForbiddenError, + ChatSendMediaForbiddenError, + ChatSendStickersForbiddenError, + FloodWaitError, + MessageDeleteForbiddenError, + MessageIdInvalidError, + MessageNotModifiedError, + UserIsBotError, +) +from telethon.events import MessageEdited, NewMessage +from telethon.utils import get_display_name + +from pyUltroid.exceptions import DependencyMissingError +from strings import get_string + +from .. import * +from .. import _ignore_eval +from ..dB import DEVLIST +from ..dB._core import LIST, LOADED +from ..fns.admins import admin_check +from ..fns.helper import bash +from ..fns.helper import time_formatter as tf +from ..version import __version__ as pyver +from ..version import ultroid_version as ult_ver +from . import SUDO_M, owner_and_sudos +from ._wrappers import eod + +MANAGER = udB.get_key("MANAGER") +TAKE_EDITS = udB.get_key("TAKE_EDITS") +black_list_chats = udB.get_key("BLACKLIST_CHATS") +allow_sudo = SUDO_M.should_allow_sudo + + +def compile_pattern(data, hndlr): + if data.startswith("^"): + data = data[1:] + if data.startswith("."): + data = data[1:] + if hndlr in [" ", "NO_HNDLR"]: + # No Hndlr Feature + return re.compile("^" + data) + return re.compile("\\" + hndlr + data) + + +def ultroid_cmd( + pattern=None, manager=False, ultroid_bot=ultroid_bot, asst=asst, **kwargs +): + owner_only = kwargs.get("owner_only", False) + groups_only = kwargs.get("groups_only", False) + admins_only = kwargs.get("admins_only", False) + fullsudo = kwargs.get("fullsudo", False) + only_devs = kwargs.get("only_devs", False) + func = kwargs.get("func", lambda e: not e.via_bot_id) + + def decor(dec): + async def wrapp(ult): + if udB.get_key("COMMAND_LOGGER"): + user_id = ult.sender_id + chat_id = ult.chat_id + command_name = pattern if pattern else ult.text.split()[0].lstrip(HNDLR) + chat_name = get_display_name(ult.chat) + LOGS.info(f"Command '{command_name}' executed by user ID {user_id} in chat {chat_id} ({chat_name})") + log_channel = udB.get_key("LOG_CHANNEL") + if log_channel: + try: + await asst.send_message( + log_channel, + f"Command '{command_name}' executed by user ID {user_id} in chat {chat_id} ({chat_name})" + ) + except Exception as e: + LOGS.warning(f"Failed to send command log to log channel {log_channel}: {e}") + if not ult.out: + if owner_only: + return + if ult.sender_id not in owner_and_sudos(): + return + if ult.sender_id in _ignore_eval: + return await eod( + ult, + get_string("py_d1"), + ) + if fullsudo and ult.sender_id not in SUDO_M.fullsudos: + return await eod(ult, get_string("py_d2"), time=15) + chat = ult.chat + if hasattr(chat, "title"): + if ( + "#noub" in chat.title.lower() + and not (chat.admin_rights or chat.creator) + and not (ult.sender_id in DEVLIST) + ): + return + if ult.is_private and (groups_only or admins_only): + return await eod(ult, get_string("py_d3")) + elif admins_only and not (chat.admin_rights or chat.creator): + return await eod(ult, get_string("py_d5")) + if only_devs and not udB.get_key("I_DEV"): + return await eod( + ult, + get_string("py_d4").format(HNDLR), + time=10, + ) + try: + await dec(ult) + except FloodWaitError as fwerr: + _log_ch = udB.get_key("LOG_CHANNEL") + if _log_ch: + await asst.send_message( + _log_ch, + f"`FloodWaitError:\n{str(fwerr)}\n\nSleeping for {tf((fwerr.seconds + 10)*1000)}`", + ) + await ultroid_bot.disconnect() + await asyncio.sleep(fwerr.seconds + 10) + await ultroid_bot.connect() + if _log_ch: + await asst.send_message( + _log_ch, + "`Bot is working again`", + ) + return + except ChatSendInlineForbiddenError: + return await eod(ult, "`Inline Locked In This Chat.`") + except (ChatSendMediaForbiddenError, ChatSendStickersForbiddenError): + return await eod(ult, get_string("py_d8")) + except (BotMethodInvalidError, UserIsBotError): + return await eod(ult, get_string("py_d6")) + except AlreadyInConversationError: + return await eod( + ult, + get_string("py_d7"), + ) + except (BotInlineDisabledError, DependencyMissingError) as er: + return await eod(ult, f"`{er}`") + except ( + MessageIdInvalidError, + MessageNotModifiedError, + MessageDeleteForbiddenError, + ) as er: + LOGS.exception(er) + except AuthKeyDuplicatedError as er: + LOGS.exception(er) + await asst.send_message( + udB.get_key("LOG_CHANNEL"), + "Session String expired, create new session from 👇", + buttons=[ + Button.url("Bot", "t.me/SessionGeneratorBot?start="), + Button.url( + "Repl", + "https://replit.com/@TheUltroid/UltroidStringSession", + ), + ], + ) + sys.exit() + except events.StopPropagation: + raise events.StopPropagation + except KeyboardInterrupt: + pass + except Exception as e: + LOGS.exception(e) + date = strftime("%Y-%m-%d %H:%M:%S", gmtime()) + naam = get_display_name(chat) + ftext = "**Ultroid Client Error:** `Forward this to` @UltroidSupportChat\n\n" + ftext += "**Py-Ultroid Version:** `" + str(pyver) + ftext += "`\n**Ultroid Version:** `" + str(ult_ver) + ftext += "`\n**Telethon Version:** `" + str(telever) + ftext += f"`\n**Hosted At:** `{HOSTED_ON}`\n\n" + ftext += "--------START ULTROID CRASH LOG--------" + ftext += "\n**Date:** `" + date + ftext += "`\n**Group:** `" + str(ult.chat_id) + "` " + str(naam) + ftext += "\n**Sender ID:** `" + str(ult.sender_id) + ftext += "`\n**Replied:** `" + str(ult.is_reply) + ftext += "`\n\n**Event Trigger:**`\n" + ftext += str(ult.text) + ftext += "`\n\n**Traceback info:**`\n" + ftext += str(format_exc()) + ftext += "`\n\n**Error text:**`\n" + ftext += str(sys.exc_info()[1]) + ftext += "`\n\n--------END ULTROID CRASH LOG--------" + ftext += "\n\n\n**Last 5 commits:**`\n" + + stdout, stderr = await bash('git log --pretty=format:"%an: %s" -5') + result = stdout + (stderr or "") + + ftext += f"{result}`" + + _log_ch = udB.get_key("LOG_CHANNEL") + error_log = None + if _log_ch: + if len(ftext) > 4096: + with BytesIO(ftext.encode()) as file: + file.name = "logs.txt" + error_log = await asst.send_file( + _log_ch, + file, + caption="**Ultroid Client Error:** `Forward this to` @UltroidSupportChat\n\n", + ) + else: + error_log = await asst.send_message( + _log_ch, + ftext, + ) + if ult.out and error_log: + await ult.edit( + f'[An error occurred]', + link_preview=False, + parse_mode="html", + ) + + cmd = None + blacklist_chats = False + chats = None + if black_list_chats: + blacklist_chats = True + chats = list(black_list_chats) + _add_new = allow_sudo and HNDLR != SUDO_HNDLR + if _add_new: + if pattern: + cmd = compile_pattern(pattern, SUDO_HNDLR) + ultroid_bot.add_event_handler( + wrapp, + NewMessage( + pattern=cmd, + incoming=True, + forwards=False, + func=func, + chats=chats, + blacklist_chats=blacklist_chats, + ), + ) + if pattern: + cmd = compile_pattern(pattern, HNDLR) + ultroid_bot.add_event_handler( + wrapp, + NewMessage( + outgoing=True if _add_new else None, + pattern=cmd, + forwards=False, + func=func, + chats=chats, + blacklist_chats=blacklist_chats, + ), + ) + if TAKE_EDITS: + + def func_(x): + return not x.via_bot_id and not (x.is_channel and x.chat.broadcast) + + ultroid_bot.add_event_handler( + wrapp, + MessageEdited( + pattern=cmd, + forwards=False, + func=func_, + chats=chats, + blacklist_chats=blacklist_chats, + ), + ) + if manager and MANAGER: + allow_all = kwargs.get("allow_all", False) + allow_pm = kwargs.get("allow_pm", False) + require = kwargs.get("require", None) + + async def manager_cmd(ult): + if not allow_all and not (await admin_check(ult, require=require)): + return + if not allow_pm and ult.is_private: + return + try: + await dec(ult) + except Exception as er: + if chat := udB.get_key("MANAGER_LOG"): + text = f"**#MANAGER_LOG\n\nChat:** `{get_display_name(ult.chat)}` `{ult.chat_id}`" + text += f"\n**Replied :** `{ult.is_reply}`\n**Command :** {ult.text}\n\n**Error :** `{er}`" + try: + return await asst.send_message( + chat, text, link_preview=False + ) + except Exception as er: + LOGS.exception(er) + LOGS.info(f"• MANAGER [{ult.chat_id}]:") + LOGS.exception(er) + + if pattern: + cmd = compile_pattern(pattern, "/") + asst.add_event_handler( + manager_cmd, + NewMessage( + pattern=cmd, + forwards=False, + incoming=True, + func=func, + chats=chats, + blacklist_chats=blacklist_chats, + ), + ) + if DUAL_MODE and not (manager and DUAL_HNDLR == "/"): + if pattern: + cmd = compile_pattern(pattern, DUAL_HNDLR) + asst.add_event_handler( + wrapp, + NewMessage( + pattern=cmd, + incoming=True, + forwards=False, + func=func, + chats=chats, + blacklist_chats=blacklist_chats, + ), + ) + file = Path(inspect.stack()[1].filename) + if "addons/" in str(file): + if LOADED.get(file.stem): + LOADED[file.stem].append(wrapp) + else: + LOADED.update({file.stem: [wrapp]}) + if pattern: + if LIST.get(file.stem): + LIST[file.stem].append(pattern) + else: + LIST.update({file.stem: [pattern]}) + return wrapp + + return decor diff --git a/pyUltroid/_misc/_supporter.py b/pyUltroid/_misc/_supporter.py new file mode 100644 index 0000000000000000000000000000000000000000..cf22e7b298ccf304c0af2421fb006e17a459b23a --- /dev/null +++ b/pyUltroid/_misc/_supporter.py @@ -0,0 +1,126 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . +# +# To Install Other USERBOTs plugin Support +# +# ULTROID Don't Need This Stuffs +# + +import inspect +import os +from pathlib import Path + +from telethon import events, types + +from pyUltroid._misc._decorators import compile_pattern, ultroid_cmd +from pyUltroid._misc._wrappers import eod, eor + +from .. import * +from ..dB._core import LIST +from . import CMD_HELP, SUDO_M # ignore: pylint + +ALIVE_NAME = ultroid_bot.me.first_name +BOTLOG_CHATID = BOTLOG = udB.get_key("LOG_CHANNEL") + + +bot = borg = catub = friday = ultroid_bot +catub.cat_cmd = ultroid_cmd + +black_list_chats = udB.get_key("BLACKLIST_CHATS") + + +def admin_cmd(pattern=None, command=None, **args): + args["func"] = lambda e: not e.via_bot_id + args["chats"] = black_list_chats + args["blacklist_chats"] = True + args["outgoing"] = True + args["forwards"] = False + if pattern: + args["pattern"] = compile_pattern(pattern, HNDLR) + file = Path(inspect.stack()[1].filename) + if LIST.get(file.stem): + LIST[file.stem].append(pattern) + else: + LIST.update({file.stem: [pattern]}) + return events.NewMessage(**args) + + +friday_on_cmd = admin_cmd +command = ultroid_cmd +register = ultroid_cmd + + +def sudo_cmd(allow_sudo=True, pattern=None, command=None, **args): + args["func"] = lambda e: not e.via_bot_id + args["chats"] = black_list_chats + args["blacklist_chats"] = True + args["forwards"] = False + if pattern: + args["pattern"] = compile_pattern(pattern, SUDO_HNDLR) + if allow_sudo: + args["from_users"] = SUDO_M.get_sudos + args["incoming"] = True + return events.NewMessage(**args) + + +edit_or_reply = eor +edit_delete = eod + + +ENV = bool(os.environ.get("ENV", False)) + + +class Config((object)): + if ENV: + from .. import asst, udB + + LOGGER = True + LOCATION = os.environ.get("LOCATION", None) + OPEN_WEATHER_MAP_APPID = os.environ.get("OPEN_WEATHER_MAP_APPID", None) + SUDO_COMMAND_HAND_LER = SUDO_HNDLR + TMP_DOWNLOAD_DIRECTORY = os.environ.get( + "TMP_DOWNLOAD_DIRECTORY", "resources/downloads/" + ) + TEMP_DOWNLOAD_DIRECTORY = TMP_DOWNLOAD_DIRECTORY + TEMP_DIR = TMP_DOWNLOAD_DIRECTORY + TELEGRAPH_SHORT_NAME = os.environ.get("TELEGRAPH_SHORT_NAME", "Ultroid") + OCR_SPACE_API_KEY = os.environ.get("OCR_SPACE_API_KEY", None) + TG_BOT_USER_NAME_BF_HER = asst.me.username + UB_BLACK_LIST_CHAT = [ + int(blacklist) for blacklist in udB.get_key("BLACKLIST_CHATS") + ] + MAX_ANTI_FLOOD_MESSAGES = 10 + ANTI_FLOOD_WARN_MODE = types.ChatBannedRights( + until_date=None, view_messages=None, send_messages=True + ) + REM_BG_API_KEY = os.environ.get("REM_BG_API_KEY", None) + GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", None) + GIT_REPO_NAME = os.environ.get("GIT_REPO_NAME", None) + PRIVATE_GROUP_BOT_API_ID = BOTLOG + PM_LOGGR_BOT_API_ID = BOTLOG + DB_URI = os.environ.get("DATABASE_URL", None) + HANDLR = HNDLR + SUDO_USERS = SUDO_M.get_sudos() + CHANNEL_ID = int(os.environ.get("CHANNEL_ID", -100)) + BLACKLIST_CHAT = UB_BLACK_LIST_CHAT + MONGO_URI = os.environ.get("MONGO_URI", None) + ALIVE_PHOTTO = os.environ.get("ALIVE_PHOTTO", None) + ALIVE_PIC = os.environ.get("ALIVE_PIC", None) + ALIVE_MSG = os.environ.get("ALIVE_MSG", None) + DEFAULT_BIO = os.environ.get("DEFAULT_BIO", None) + BIO_MSG = os.environ.get("BIO_MSG", None) + LYDIA_API = os.environ.get("LYDIA_API", None) + PLUGIN_CHANNEL = int(os.environ.get("PLUGIN_CHANNEL", -69)) + PM_DATA = os.environ.get("PM_DATA", "ENABLE") + DEEP_AI = os.environ.get("DEEP_AI", None) + TAG_LOG = os.environ.get("TAG_LOG", None) + + else: + DB_URI = None + + +CMD_HNDLR = HNDLR diff --git a/pyUltroid/_misc/_wrappers.py b/pyUltroid/_misc/_wrappers.py new file mode 100644 index 0000000000000000000000000000000000000000..6abc29a8691a0066fe3daf2117eddadb4b95af5b --- /dev/null +++ b/pyUltroid/_misc/_wrappers.py @@ -0,0 +1,65 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from asyncio import sleep + +from telethon.errors import MessageDeleteForbiddenError, MessageNotModifiedError +from telethon.tl.custom import Message +from telethon.tl.types import MessageService + +# edit or reply + + +async def eor(event, text=None, time=None, link_preview=False, edit_time=None, **args): + reply_to = event.reply_to_msg_id or event + if event.out and not isinstance(event, MessageService): + if edit_time: + await sleep(edit_time) + if args.get("file") and not event.media: + await event.delete() + ok = await event.client.send_message( + event.chat_id, + text, + link_preview=link_preview, + reply_to=reply_to, + **args + ) + else: + try: + ok = await event.edit(text, link_preview=link_preview, **args) + except MessageNotModifiedError: + ok = event + else: + ok = await event.client.send_message( + event.chat_id, text, link_preview=link_preview, reply_to=reply_to, **args + ) + + if time: + await sleep(time) + return await ok.delete() + return ok + + +async def eod(event, text=None, **kwargs): + kwargs["time"] = kwargs.get("time", 8) + return await eor(event, text, **kwargs) + + +async def _try_delete(event): + try: + return await event.delete() + except (MessageDeleteForbiddenError): + pass + except BaseException as er: + from . import LOGS + + LOGS.error("Error while Deleting Message..") + LOGS.exception(er) + + +setattr(Message, "eor", eor) +setattr(Message, "try_delete", _try_delete) diff --git a/pyUltroid/configs.py b/pyUltroid/configs.py new file mode 100644 index 0000000000000000000000000000000000000000..94d5718475c187bcc93cde17dd27c92caf89bb48 --- /dev/null +++ b/pyUltroid/configs.py @@ -0,0 +1,57 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import sys + +from decouple import config + +try: + from dotenv import load_dotenv + + load_dotenv() +except ImportError: + pass + + +class Var: + # mandatory + API_ID = ( + int(sys.argv[1]) if len(sys.argv) > 1 else config("API_ID", default=6, cast=int) + ) + API_HASH = ( + sys.argv[2] + if len(sys.argv) > 2 + else config("API_HASH", default="eb06d4abfb49dc3eeb1aeb98ae0f581e") + ) + SESSION = sys.argv[3] if len(sys.argv) > 3 else config("SESSION", default=None) + REDIS_URI = ( + sys.argv[4] + if len(sys.argv) > 4 + else (config("REDIS_URI", default=None) or config("REDIS_URL", default=None)) + ) + REDIS_PASSWORD = ( + sys.argv[5] if len(sys.argv) > 5 else config("REDIS_PASSWORD", default=None) + ) + # extras + BOT_TOKEN = config("BOT_TOKEN", default=None) + LOG_CHANNEL = config("LOG_CHANNEL", default=0, cast=int) + HEROKU_APP_NAME = config("HEROKU_APP_NAME", default=None) + HEROKU_API = config("HEROKU_API", default=None) + VC_SESSION = config("VC_SESSION", default=None) + ADDONS = config("ADDONS", default=False, cast=bool) + VCBOT = config("VCBOT", default=False, cast=bool) + # for railway + REDISPASSWORD = config("REDISPASSWORD", default=None) + REDISHOST = config("REDISHOST", default=None) + REDISPORT = config("REDISPORT", default=None) + REDISUSER = config("REDISUSER", default=None) + # for sql + DATABASE_URL = config("DATABASE_URL", default=None) + # for MONGODB users + MONGO_URI = config("MONGO_URI", default=None) + # for local Telegram DB backup + TGDB_URL = config("TGDB_URL", default=None) diff --git a/pyUltroid/dB/__init__.py b/pyUltroid/dB/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4359b80a8542ea630a44455a2fd2232731694648 --- /dev/null +++ b/pyUltroid/dB/__init__.py @@ -0,0 +1,65 @@ +from .. import run_as_module + +if not run_as_module: + from ..exceptions import RunningAsFunctionLibError + + raise RunningAsFunctionLibError( + "You are running 'pyUltroid' as a functions lib, not as run module. You can't access this folder.." + ) + +from .. import * + +DEVLIST = [ + 719195224, # @xditya + 1322549723, # @danish_00 + 1903729401, # @its_buddhhu + 1303895686, # @Sipak_OP + 611816596, # @Arnab431 + 1318486004, # @sppidy + 803243487, # @hellboi_atul +] + +ULTROID_IMAGES = [ + f"https://graph.org/file/{_}.jpg" + for _ in [ + "ec250c66268b62ee4ade6", + "3c25230ae30d246194eba", + "b01715a61b9e876c0d45d", + "4ceaf720a96a24527ecff", + "a96223b574f29f3f0d184", + "6e081d339a01cc6190393", + ] +] + +stickers = [ + "CAADAQADeAIAAm_BZBQh8owdViocCAI", + "CAADAQADegIAAm_BZBQ6j8GpKtnrSgI", + "CAADAQADfAIAAm_BZBQpqC84n9JNXgI", + "CAADAQADfgIAAm_BZBSxLmTyuHvlzgI", + "CAADAQADgAIAAm_BZBQ3TZaueMkS-gI", + "CAADAQADggIAAm_BZBTPcbJMorVVsQI", + "CAADAQADhAIAAm_BZBR3lnMZRdsYxAI", + "CAADAQADhgIAAm_BZBQGQRx4iaM4pQI", + "CAADAQADiAIAAm_BZBRRF-cjJi_QywI", + "CAADAQADigIAAm_BZBQQJwfzkqLM0wI", + "CAADAQADjAIAAm_BZBQSl5GSAT0viwI", + "CAADAQADjgIAAm_BZBQ2xU688gfHhQI", + "CAADAQADkAIAAm_BZBRGuPNgVvkoHQI", + "CAADAQADpgIAAm_BZBQAAZr0SJ5EKtQC", + "CAADAQADkgIAAm_BZBTvuxuayqvjhgI", + "CAADAQADlAIAAm_BZBSMZdWN2Yew1AI", + "CAADAQADlQIAAm_BZBRXyadiwWGNkwI", + "CAADAQADmAIAAm_BZBQDoB15A1jS1AI", + "CAADAQADmgIAAm_BZBTnOLQ8_d72vgI", + "CAADAQADmwIAAm_BZBTve1kgdG0Y5gI", + "CAADAQADnAIAAm_BZBQUMyFiylJSqQI", + "CAADAQADnQIAAm_BZBSMAe2V4pwhNgI", + "CAADAQADngIAAm_BZBQ06D92QL_vywI", + "CAADAQADnwIAAm_BZBRw7UAbr6vtEgI", + "CAADAQADoAIAAm_BZBRkv9DnGPXh_wI", + "CAADAQADoQIAAm_BZBQwI2NgQdyKlwI", + "CAADAQADogIAAm_BZBRPHJF3XChVLgI", + "CAADAQADowIAAm_BZBThpas7rZD6DAI", + "CAADAQADpAIAAm_BZBQcC2DpZcCw1wI", + "CAADAQADpQIAAm_BZBQKruTcEU4ntwI", +] diff --git a/pyUltroid/dB/_core.py b/pyUltroid/dB/_core.py new file mode 100644 index 0000000000000000000000000000000000000000..d268941c50941bf526778f1279acef525653569d --- /dev/null +++ b/pyUltroid/dB/_core.py @@ -0,0 +1,13 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +PLUGINS = [] +ADDONS = [] +HELP = {} +LOADED = {} +LIST = {} +VC_HELP = {} diff --git a/pyUltroid/dB/afk_db.py b/pyUltroid/dB/afk_db.py new file mode 100644 index 0000000000000000000000000000000000000000..a0cfdb6f3dc6fb7a84311a0a66caada2ec21edbb --- /dev/null +++ b/pyUltroid/dB/afk_db.py @@ -0,0 +1,33 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from datetime import datetime as dt + +from .. import udB + + +def get_stuff(): + return udB.get_key("AFK_DB") or [] + + +def add_afk(msg, media_type, media): + time = dt.now().strftime("%b %d %Y %I:%M:%S%p") + udB.set_key("AFK_DB", [msg, media_type, media, time]) + return + + +def is_afk(): + afk = get_stuff() + if afk: + start_time = dt.strptime(afk[3], "%b %d %Y %I:%M:%S%p") + afk_since = str(dt.now().replace(microsecond=0) - start_time) + return afk[0], afk[1], afk[2], afk_since + return False + + +def del_afk(): + return udB.del_key("AFK_DB") diff --git a/pyUltroid/dB/antiflood_db.py b/pyUltroid/dB/antiflood_db.py new file mode 100644 index 0000000000000000000000000000000000000000..a67986ceab4a92357bff2a8aca08e49c7f3369f4 --- /dev/null +++ b/pyUltroid/dB/antiflood_db.py @@ -0,0 +1,32 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from .. import udB + + +def get_flood(): + return udB.get_key("ANTIFLOOD") or {} + + +def set_flood(chat_id, limit): + omk = get_flood() + omk.update({chat_id: limit}) + return udB.set_key("ANTIFLOOD", omk) + + +def get_flood_limit(chat_id): + omk = get_flood() + if chat_id in omk.keys(): + return omk[chat_id] + + +def rem_flood(chat_id): + omk = get_flood() + if chat_id in omk.keys(): + del omk[chat_id] + return udB.set_key("ANTIFLOOD", omk) diff --git a/pyUltroid/dB/asstcmd_db.py b/pyUltroid/dB/asstcmd_db.py new file mode 100644 index 0000000000000000000000000000000000000000..38df94a80cd5ca1308325fdff4c5e74862d2905b --- /dev/null +++ b/pyUltroid/dB/asstcmd_db.py @@ -0,0 +1,39 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from .. import udB + + +def get_stuff(): + return udB.get_key("ASST_CMDS") or {} + + +def add_cmd(cmd, msg, media, button): + ok = get_stuff() + ok.update({cmd: {"msg": msg, "media": media, "button": button}}) + return udB.set_key("ASST_CMDS", ok) + + +def rem_cmd(cmd): + ok = get_stuff() + if ok.get(cmd): + ok.pop(cmd) + return udB.set_key("ASST_CMDS", ok) + + +def cmd_reply(cmd): + ok = get_stuff() + if ok.get(cmd): + okk = ok[cmd] + return okk["msg"], okk["media"], okk["button"] if ok.get("button") else None + return + + +def list_cmds(): + ok = get_stuff() + return ok.keys() diff --git a/pyUltroid/dB/base.py b/pyUltroid/dB/base.py new file mode 100644 index 0000000000000000000000000000000000000000..de26c1542de1ee1144bd70f458e83a3f45e8b3d5 --- /dev/null +++ b/pyUltroid/dB/base.py @@ -0,0 +1,44 @@ +from .. import udB + + +class KeyManager: + def __init__(self, key, cast=None) -> None: + self._key = key + self._cast = cast + + def get(self): + _data = udB.get_key(self._key) + if self._cast and not isinstance(_data, self._cast): + return [_data] if self._cast == list else self._cast(_data) + return _data or (self._cast() if callable(self._cast) else self._cast) + + def get_child(self, key): + return self.get()[key] + + def count(self): + return len(self.get()) + + def add(self, item): + content = self.get() + if content == None and callable(type(item)): + content = type(item)() + if isinstance(content, dict) and isinstance(item, dict): + content.update(item) + elif isinstance(content, list) and item not in content: + content.append(item) + else: + return + udB.set_key(self._key, content) + + def remove(self, item): + content = self.get() + if isinstance(content, list) and item in content: + content.remove(item) + elif isinstance(content, dict) and content.get(item): + del content[item] + else: + return + udB.set_key(self._key, content) + + def contains(self, item): + return item in self.get() diff --git a/pyUltroid/dB/blacklist_chat_db.py b/pyUltroid/dB/blacklist_chat_db.py new file mode 100644 index 0000000000000000000000000000000000000000..e8bd874037353f0aaf52c7a38d3c5ec490a35642 --- /dev/null +++ b/pyUltroid/dB/blacklist_chat_db.py @@ -0,0 +1,15 @@ +from .. import udB + + +def add_black_chat(chat_id): + chat = udB.get_key("BLACKLIST_CHATS") or [] + if chat_id not in chat: + chat.append(chat_id) + return udB.set_key("BLACKLIST_CHATS", chat) + + +def rem_black_chat(chat_id): + chat = udB.get_key("BLACKLIST_CHATS") or [] + if chat_id in chat: + chat.remove(chat_id) + return udB.set_key("BLACKLIST_CHATS", chat) diff --git a/pyUltroid/dB/blacklist_db.py b/pyUltroid/dB/blacklist_db.py new file mode 100644 index 0000000000000000000000000000000000000000..3620b0197f6c4e73eb2dc7def7d0092695d8f35c --- /dev/null +++ b/pyUltroid/dB/blacklist_db.py @@ -0,0 +1,44 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_stuff(): + return udB.get_key("BLACKLIST_DB") or {} + + +def add_blacklist(chat, word): + ok = get_stuff() + if ok.get(chat): + for z in word.split(): + if z not in ok[chat]: + ok[chat].append(z) + else: + ok.update({chat: [word]}) + return udB.set_key("BLACKLIST_DB", ok) + + +def rem_blacklist(chat, word): + ok = get_stuff() + if ok.get(chat) and word in ok[chat]: + ok[chat].remove(word) + return udB.set_key("BLACKLIST_DB", ok) + + +def list_blacklist(chat): + ok = get_stuff() + if ok.get(chat): + txt = "".join(f"👉`{z}`\n" for z in ok[chat]) + if txt: + return txt + + +def get_blacklist(chat): + ok = get_stuff() + if ok.get(chat): + return ok[chat] diff --git a/pyUltroid/dB/botchat_db.py b/pyUltroid/dB/botchat_db.py new file mode 100644 index 0000000000000000000000000000000000000000..9b4b07f0b9f642f03bdada6671e395fe22695432 --- /dev/null +++ b/pyUltroid/dB/botchat_db.py @@ -0,0 +1,41 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from .. import udB + + +def get_stuff(): + return udB.get_key("BOTCHAT") or {} + + +def add_stuff(msg_id, user_id): + ok = get_stuff() + ok.update({msg_id: user_id}) + return udB.set_key("BOTCHAT", ok) + + +def get_who(msg_id): + ok = get_stuff() + if ok.get(msg_id): + return ok[msg_id] + + +def tag_add(msg, chat, user): + ok = get_stuff() + if not ok.get("TAG"): + ok.update({"TAG": {msg: [chat, user]}}) + else: + ok["TAG"].update({msg: [chat, user]}) + return udB.set_key("BOTCHAT", ok) + + +def who_tag(msg): + ok = get_stuff() + if ok.get("TAG") and ok["TAG"].get(msg): + return ok["TAG"][msg] + return False, False diff --git a/pyUltroid/dB/echo_db.py b/pyUltroid/dB/echo_db.py new file mode 100644 index 0000000000000000000000000000000000000000..e13774afaefbce0dcddc09ae9ff8680cfa12745f --- /dev/null +++ b/pyUltroid/dB/echo_db.py @@ -0,0 +1,43 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_stuff(): + return udB.get_key("ECHO") or {} + + +def add_echo(chat, user): + x = get_stuff() + if k := x.get(int(chat)): + if user not in k: + k.append(int(user)) + x.update({int(chat): k}) + else: + x.update({int(chat): [int(user)]}) + return udB.set_key("ECHO", x) + + +def rem_echo(chat, user): + x = get_stuff() + if k := x.get(int(chat)): + if user in k: + k.remove(int(user)) + x.update({int(chat): k}) + return udB.set_key("ECHO", x) + + +def check_echo(chat, user): + x = get_stuff() + if (k := x.get(int(chat))) and int(user) in k: + return True + + +def list_echo(chat): + x = get_stuff() + return x.get(int(chat)) diff --git a/pyUltroid/dB/filestore_db.py b/pyUltroid/dB/filestore_db.py new file mode 100644 index 0000000000000000000000000000000000000000..a6c1a7764c79505ee6ef20c964607677add90234 --- /dev/null +++ b/pyUltroid/dB/filestore_db.py @@ -0,0 +1,35 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_stored(): + return udB.get_key("FILE_STORE") or {} + + +def store_msg(hash, msg_id): + all = get_stored() + all.update({hash: msg_id}) + return udB.set_key("FILE_STORE", all) + + +def list_all_stored_msgs(): + all = get_stored() + return list(all.keys()) + + +def get_stored_msg(hash): + all = get_stored() + if all.get(hash): + return all[hash] + + +def del_stored(hash): + all = get_stored() + all.pop(hash) + return udB.set_key("FILE_STORE", all) diff --git a/pyUltroid/dB/filter_db.py b/pyUltroid/dB/filter_db.py new file mode 100644 index 0000000000000000000000000000000000000000..ea8d3f40c4ffb8823c1da7db40c12a0a8c1c15a4 --- /dev/null +++ b/pyUltroid/dB/filter_db.py @@ -0,0 +1,47 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_stuff(): + return udB.get_key("FILTERS") or {} + + +def add_filter(chat, word, msg, media, button): + ok = get_stuff() + if ok.get(chat): + ok[chat].update({word: {"msg": msg, "media": media, "button": button}}) + else: + ok.update({chat: {word: {"msg": msg, "media": media, "button": button}}}) + udB.set_key("FILTERS", ok) + + +def rem_filter(chat, word): + ok = get_stuff() + if ok.get(chat) and ok[chat].get(word): + ok[chat].pop(word) + udB.set_key("FILTERS", ok) + + +def rem_all_filter(chat): + ok = get_stuff() + if ok.get(chat): + ok.pop(chat) + udB.set_key("FILTERS", ok) + + +def get_filter(chat): + ok = get_stuff() + if ok.get(chat): + return ok[chat] + + +def list_filter(chat): + ok = get_stuff() + if ok.get(chat): + return "".join(f"👉 `{z}`\n" for z in ok[chat]) diff --git a/pyUltroid/dB/forcesub_db.py b/pyUltroid/dB/forcesub_db.py new file mode 100644 index 0000000000000000000000000000000000000000..77bc3c167aa1e19786f1ccfa5cd1c126c4d0301f --- /dev/null +++ b/pyUltroid/dB/forcesub_db.py @@ -0,0 +1,35 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from .. import udB + + +def get_chats(): + return udB.get_key("FORCESUB") or {} + + +def add_forcesub(chat_id, chattojoin): + omk = get_chats() + omk.update({chat_id: chattojoin}) + return udB.set_key("FORCESUB", omk) + + +def get_forcesetting(chat_id): + omk = get_chats() + if chat_id in omk.keys(): + return omk[chat_id] + + +def rem_forcesub(chat_id): + omk = get_chats() + if chat_id in omk.keys(): + try: + del omk[chat_id] + return udB.set_key("FORCESUB", omk) + except KeyError: + return False diff --git a/pyUltroid/dB/gban_mute_db.py b/pyUltroid/dB/gban_mute_db.py new file mode 100644 index 0000000000000000000000000000000000000000..50a14cca323a222bb0ca018ac6a44df47e91836f --- /dev/null +++ b/pyUltroid/dB/gban_mute_db.py @@ -0,0 +1,52 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def list_gbanned(): + return udB.get_key("GBAN") or {} + + +def gban(user, reason): + ok = list_gbanned() + ok.update({int(user): reason or "No Reason. "}) + return udB.set_key("GBAN", ok) + + +def ungban(user): + ok = list_gbanned() + if ok.get(int(user)): + del ok[int(user)] + return udB.set_key("GBAN", ok) + + +def is_gbanned(user): + ok = list_gbanned() + if ok.get(int(user)): + return ok[int(user)] + + +def gmute(user): + ok = list_gmuted() + ok.append(int(user)) + return udB.set_key("GMUTE", ok) + + +def ungmute(user): + ok = list_gmuted() + if user in ok: + ok.remove(int(user)) + return udB.set_key("GMUTE", ok) + + +def is_gmuted(user): + return int(user) in list_gmuted() + + +def list_gmuted(): + return udB.get_key("GMUTE") or [] diff --git a/pyUltroid/dB/greetings_db.py b/pyUltroid/dB/greetings_db.py new file mode 100644 index 0000000000000000000000000000000000000000..89c7e43ff209842dc09b8f84a7297df592826b40 --- /dev/null +++ b/pyUltroid/dB/greetings_db.py @@ -0,0 +1,66 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_stuff(key=None): + return udB.get_key(key) or {} + + +def add_welcome(chat, msg, media, button): + ok = get_stuff("WELCOME") + ok.update({chat: {"welcome": msg, "media": media, "button": button}}) + return udB.set_key("WELCOME", ok) + + +def get_welcome(chat): + ok = get_stuff("WELCOME") + return ok.get(chat) + + +def delete_welcome(chat): + ok = get_stuff("WELCOME") + if ok.get(chat): + ok.pop(chat) + return udB.set_key("WELCOME", ok) + + +def add_goodbye(chat, msg, media, button): + ok = get_stuff("GOODBYE") + ok.update({chat: {"goodbye": msg, "media": media, "button": button}}) + return udB.set_key("GOODBYE", ok) + + +def get_goodbye(chat): + ok = get_stuff("GOODBYE") + return ok.get(chat) + + +def delete_goodbye(chat): + ok = get_stuff("GOODBYE") + if ok.get(chat): + ok.pop(chat) + return udB.set_key("GOODBYE", ok) + + +def add_thanks(chat): + x = get_stuff("THANK_MEMBERS") + x.update({chat: True}) + return udB.set_key("THANK_MEMBERS", x) + + +def remove_thanks(chat): + x = get_stuff("THANK_MEMBERS") + if x.get(chat): + x.pop(chat) + return udB.set_key("THANK_MEMBERS", x) + + +def must_thank(chat): + x = get_stuff("THANK_MEMBERS") + return x.get(chat) diff --git a/pyUltroid/dB/mute_db.py b/pyUltroid/dB/mute_db.py new file mode 100644 index 0000000000000000000000000000000000000000..7f838a793ceb15324aceadc48b10aea9f8bde3dd --- /dev/null +++ b/pyUltroid/dB/mute_db.py @@ -0,0 +1,34 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_muted(): + return udB.get_key("MUTE") or {} + + +def mute(chat, id): + ok = get_muted() + if ok.get(chat): + if id not in ok[chat]: + ok[chat].append(id) + else: + ok.update({chat: [id]}) + return udB.set_key("MUTE", ok) + + +def unmute(chat, id): + ok = get_muted() + if ok.get(chat) and id in ok[chat]: + ok[chat].remove(id) + return udB.set_key("MUTE", ok) + + +def is_muted(chat, id): + ok = get_muted() + return bool(ok.get(chat) and id in ok[chat]) diff --git a/pyUltroid/dB/notes_db.py b/pyUltroid/dB/notes_db.py new file mode 100644 index 0000000000000000000000000000000000000000..0518411e5d8839dc8308c02cad4aed408fba86f3 --- /dev/null +++ b/pyUltroid/dB/notes_db.py @@ -0,0 +1,47 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_stuff(): + return udB.get_key("NOTE") or {} + + +def add_note(chat, word, msg, media, button): + ok = get_stuff() + if ok.get(int(chat)): + ok[int(chat)].update({word: {"msg": msg, "media": media, "button": button}}) + else: + ok.update({int(chat): {word: {"msg": msg, "media": media, "button": button}}}) + udB.set_key("NOTE", ok) + + +def rem_note(chat, word): + ok = get_stuff() + if ok.get(int(chat)) and ok[int(chat)].get(word): + ok[int(chat)].pop(word) + return udB.set_key("NOTE", ok) + + +def rem_all_note(chat): + ok = get_stuff() + if ok.get(int(chat)): + ok.pop(int(chat)) + return udB.set_key("NOTE", ok) + + +def get_notes(chat, word): + ok = get_stuff() + if ok.get(int(chat)) and ok[int(chat)].get(word): + return ok[int(chat)][word] + + +def list_note(chat): + ok = get_stuff() + if ok.get(int(chat)): + return "".join(f"👉 #{z}\n" for z in ok[chat]) diff --git a/pyUltroid/dB/nsfw_db.py b/pyUltroid/dB/nsfw_db.py new file mode 100644 index 0000000000000000000000000000000000000000..8fe3714dce546fa4e8e3a9eaeb68ca95372b85cb --- /dev/null +++ b/pyUltroid/dB/nsfw_db.py @@ -0,0 +1,51 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +from .. import udB + + +def get_stuff(key="NSFW"): + return udB.get_key(key) or {} + + +def nsfw_chat(chat, action): + x = get_stuff() + x.update({chat: action}) + return udB.set_key("NSFW", x) + + +def rem_nsfw(chat): + x = get_stuff() + if x.get(chat): + x.pop(chat) + return udB.set_key("NSFW", x) + + +def is_nsfw(chat): + x = get_stuff() + if x.get(chat): + return x[chat] + + +def profan_chat(chat, action): + x = get_stuff("PROFANITY") + x.update({chat: action}) + return udB.set_key("PROFANITY", x) + + +def rem_profan(chat): + x = get_stuff("PROFANITY") + if x.get(chat): + x.pop(chat) + return udB.set_key("PROFANITY", x) + + +def is_profan(chat): + x = get_stuff("PROFANITY") + if x.get(chat): + return x[chat] diff --git a/pyUltroid/dB/snips_db.py b/pyUltroid/dB/snips_db.py new file mode 100644 index 0000000000000000000000000000000000000000..090437a8c6cc80ebd5e8104bc80531cb2ccede69 --- /dev/null +++ b/pyUltroid/dB/snips_db.py @@ -0,0 +1,36 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_all_snips(): + return udB.get_key("SNIP") or {} + + +def add_snip(word, msg, media, button): + ok = get_all_snips() + ok.update({word: {"msg": msg, "media": media, "button": button}}) + udB.set_key("SNIP", ok) + + +def rem_snip(word): + ok = get_all_snips() + if ok.get(word): + ok.pop(word) + udB.set_key("SNIP", ok) + + +def get_snips(word): + ok = get_all_snips() + if ok.get(word): + return ok[word] + return False + + +def list_snip(): + return "".join(f"👉 ${z}\n" for z in get_all_snips()) diff --git a/pyUltroid/dB/vc_sudos.py b/pyUltroid/dB/vc_sudos.py new file mode 100644 index 0000000000000000000000000000000000000000..900d7360522945a068dafc83a7f7ebbf2f111e93 --- /dev/null +++ b/pyUltroid/dB/vc_sudos.py @@ -0,0 +1,29 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_vcsudos(): + return udB.get_key("VC_SUDOS") or [] + + +def is_vcsudo(id): + return id in get_vcsudos() + + +def add_vcsudo(id): + sudos = get_vcsudos() + sudos.append(id) + return udB.set_key("VC_SUDOS", sudos) + + +def del_vcsudo(id): + if is_vcsudo(id): + sudos = get_vcsudos() + sudos.remove(id) + return udB.set_key("VC_SUDOS", sudos) diff --git a/pyUltroid/dB/warn_db.py b/pyUltroid/dB/warn_db.py new file mode 100644 index 0000000000000000000000000000000000000000..abb2b0f002f660c62399d24181eb8cc238886619 --- /dev/null +++ b/pyUltroid/dB/warn_db.py @@ -0,0 +1,39 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import udB + + +def get_stuff(): + return udB.get_key("WARNS") or {} + + +def add_warn(chat, user, count, reason): + x = get_stuff() + try: + x[chat].update({user: [count, reason]}) + except BaseException: + x.update({chat: {user: [count, reason]}}) + return udB.set_key("WARNS", x) + + +def warns(chat, user): + x = get_stuff() + try: + count, reason = x[chat][user][0], x[chat][user][1] + return count, reason + except BaseException: + return 0, None + + +def reset_warn(chat, user): + x = get_stuff() + try: + x[chat].pop(user) + return udB.set_key("WARNS", x) + except BaseException: + return diff --git a/pyUltroid/exceptions.py b/pyUltroid/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..8e3e98712df83ae70ec7871559b05c8989812f23 --- /dev/null +++ b/pyUltroid/exceptions.py @@ -0,0 +1,22 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +Exceptions which can be raised by py-Ultroid Itself. +""" + + +class pyUltroidError(Exception): + ... + + +class DependencyMissingError(ImportError): + ... + + +class RunningAsFunctionLibError(pyUltroidError): + ... diff --git a/pyUltroid/fns/FastTelethon.py b/pyUltroid/fns/FastTelethon.py new file mode 100644 index 0000000000000000000000000000000000000000..fe6524a61755bc3a46ee98f632a9191ce015ea58 --- /dev/null +++ b/pyUltroid/fns/FastTelethon.py @@ -0,0 +1,404 @@ +# copied from https://github.com/tulir/mautrix-telegram/blob/master/mautrix_telegram/util/parallel_file_transfer.py +# Copyright (C) 2021-2026 Tulir Asokan + +import asyncio +import hashlib +import logging +import math +import os +from collections import defaultdict +from typing import ( + AsyncGenerator, + Awaitable, + BinaryIO, + DefaultDict, + List, + Optional, + Tuple, + Union, +) + +from telethon import TelegramClient, helpers, utils +from telethon.crypto import AuthKey +from telethon.helpers import _maybe_await +from telethon.network import MTProtoSender +from telethon.tl.alltlobjects import LAYER +from telethon.tl.functions import InvokeWithLayerRequest +from telethon.tl.functions.auth import ( + ExportAuthorizationRequest, + ImportAuthorizationRequest, +) +from telethon.tl.functions.upload import ( + GetFileRequest, + SaveBigFilePartRequest, + SaveFilePartRequest, +) +from telethon.tl.types import ( + Document, + InputDocumentFileLocation, + InputFile, + InputFileBig, + InputFileLocation, + InputPeerPhotoFileLocation, + InputPhotoFileLocation, + TypeInputFile, +) + +log: logging.Logger = logging.getLogger("_FastTelethon") + +TypeLocation = Union[ + Document, + InputDocumentFileLocation, + InputPeerPhotoFileLocation, + InputFileLocation, + InputPhotoFileLocation, +] + + +class DownloadSender: + client: TelegramClient + sender: MTProtoSender + request: GetFileRequest + remaining: int + stride: int + + def __init__( + self, + client: TelegramClient, + sender: MTProtoSender, + file: TypeLocation, + offset: int, + limit: int, + stride: int, + count: int, + ) -> None: + self.sender = sender + self.client = client + self.request = GetFileRequest(file, offset=offset, limit=limit) + self.stride = stride + self.remaining = count + + async def next(self) -> Optional[bytes]: + if not self.remaining: + return None + result = await self.client._call(self.sender, self.request) + self.remaining -= 1 + self.request.offset += self.stride + return result.bytes + + def disconnect(self) -> Awaitable[None]: + return self.sender.disconnect() + + +class UploadSender: + client: TelegramClient + sender: MTProtoSender + request: Union[SaveFilePartRequest, SaveBigFilePartRequest] + part_count: int + stride: int + previous: Optional[asyncio.Task] + loop: asyncio.AbstractEventLoop + + def __init__( + self, + client: TelegramClient, + sender: MTProtoSender, + file_id: int, + part_count: int, + big: bool, + index: int, + stride: int, + loop: asyncio.AbstractEventLoop, + ) -> None: + self.client = client + self.sender = sender + self.part_count = part_count + if big: + self.request = SaveBigFilePartRequest(file_id, index, part_count, b"") + else: + self.request = SaveFilePartRequest(file_id, index, b"") + self.stride = stride + self.previous = None + self.loop = loop + + async def next(self, data: bytes) -> None: + if self.previous: + await self.previous + self.previous = self.loop.create_task(self._next(data)) + + async def _next(self, data: bytes) -> None: + self.request.bytes = data + await self.client._call(self.sender, self.request) + self.request.file_part += self.stride + + async def disconnect(self) -> None: + if self.previous: + await self.previous + return await self.sender.disconnect() + + +class ParallelTransferrer: + client: TelegramClient + loop: asyncio.AbstractEventLoop + dc_id: int + senders: Optional[List[Union[DownloadSender, UploadSender]]] + auth_key: AuthKey + upload_ticker: int + + def __init__(self, client: TelegramClient, dc_id: Optional[int] = None) -> None: + self.client = client + try: + self.client.refresh_auth(client) + except AttributeError: + pass + self.loop = self.client.loop + self.dc_id = dc_id or self.client.session.dc_id + self.auth_key = ( + None + if dc_id and self.client.session.dc_id != dc_id + else self.client.session.auth_key + ) + self.senders = None + self.upload_ticker = 0 + try: + self.client.clear_auth(self.client) + except AttributeError: + pass + + async def _cleanup(self) -> None: + await asyncio.gather(*[sender.disconnect() for sender in self.senders]) + self.senders = None + + @staticmethod + def _get_connection_count( + file_size: int, + ) -> int: + full_size = 100 * (1024 ** 2) + if file_size > full_size: + return 20 + return math.ceil((file_size / full_size) * 20) + + async def _init_download( + self, connections: int, file: TypeLocation, part_count: int, part_size: int + ) -> None: + minimum, remainder = divmod(part_count, connections) + + def get_part_count() -> int: + nonlocal remainder + if remainder > 0: + remainder -= 1 + return minimum + 1 + return minimum + + # The first cross-DC sender will export+import the authorization, so we always create it + # before creating any other senders. + self.senders = [ + await self._create_download_sender( + file, 0, part_size, connections * part_size, get_part_count() + ), + *await asyncio.gather( + *[ + self._create_download_sender( + file, i, part_size, connections * part_size, get_part_count() + ) + for i in range(1, connections) + ] + ), + ] + + async def _create_download_sender( + self, + file: TypeLocation, + index: int, + part_size: int, + stride: int, + part_count: int, + ) -> DownloadSender: + return DownloadSender( + self.client, + await self._create_sender(), + file, + index * part_size, + part_size, + stride, + part_count, + ) + + async def _init_upload( + self, connections: int, file_id: int, part_count: int, big: bool + ) -> None: + self.senders = [ + await self._create_upload_sender(file_id, part_count, big, 0, connections), + *await asyncio.gather( + *[ + self._create_upload_sender(file_id, part_count, big, i, connections) + for i in range(1, connections) + ] + ), + ] + + async def _create_upload_sender( + self, file_id: int, part_count: int, big: bool, index: int, stride: int + ) -> UploadSender: + return UploadSender( + self.client, + await self._create_sender(), + file_id, + part_count, + big, + index, + stride, + loop=self.loop, + ) + + async def _create_sender(self) -> MTProtoSender: + dc = await self.client._get_dc(self.dc_id) + sender = MTProtoSender(self.auth_key, loggers=self.client._log) + await sender.connect( + self.client._connection( + dc.ip_address, + dc.port, + dc.id, + loggers=self.client._log, + proxy=self.client._proxy, + ) + ) + if not self.auth_key: + auth = await self.client(ExportAuthorizationRequest(self.dc_id)) + self.client._init_request.query = ImportAuthorizationRequest( + id=auth.id, bytes=auth.bytes + ) + req = InvokeWithLayerRequest(LAYER, self.client._init_request) + await sender.send(req) + self.auth_key = sender.auth_key + return sender + + async def init_upload( + self, + file_id: int, + file_size: int, + part_size_kb: Optional[float] = None, + connection_count: Optional[int] = None, + ) -> Tuple[int, int, bool]: + connection_count = connection_count or self._get_connection_count(file_size) + part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024 + part_count = (file_size + part_size - 1) // part_size + is_large = file_size > 10 * (1024 ** 2) + await self._init_upload(connection_count, file_id, part_count, is_large) + return part_size, part_count, is_large + + async def upload(self, part: bytes) -> None: + await self.senders[self.upload_ticker].next(part) + self.upload_ticker = (self.upload_ticker + 1) % len(self.senders) + + async def finish_upload(self) -> None: + await self._cleanup() + + async def download( + self, + file: TypeLocation, + file_size: int, + part_size_kb: Optional[float] = None, + connection_count: Optional[int] = None, + ) -> AsyncGenerator[bytes, None]: + connection_count = connection_count or self._get_connection_count(file_size) + part_size = (part_size_kb or utils.get_appropriated_part_size(file_size)) * 1024 + part_count = math.ceil(file_size / part_size) + await self._init_download(connection_count, file, part_count, part_size) + + part = 0 + while part < part_count: + tasks = [self.loop.create_task(sender.next()) for sender in self.senders] + for task in tasks: + data = await task + if not data: + break + yield data + part += 1 + await self._cleanup() + + +parallel_transfer_locks: DefaultDict[int, asyncio.Lock] = defaultdict( + lambda: asyncio.Lock() +) + + +def stream_file(file_to_stream: BinaryIO, chunk_size=1024): + while True: + data_read = file_to_stream.read(chunk_size) + if not data_read: + break + yield data_read + + +async def _internal_transfer_to_telegram( + client: TelegramClient, + response: BinaryIO, + filename: str, + progress_callback: callable, +) -> Tuple[TypeInputFile, int]: + file_id = helpers.generate_random_long() + file_size = os.path.getsize(response.name) + + hash_md5 = hashlib.md5() + uploader = ParallelTransferrer(client) + part_size, part_count, is_large = await uploader.init_upload(file_id, file_size) + buffer = bytearray() + for data in stream_file(response): + if progress_callback: + try: + await _maybe_await(progress_callback(response.tell(), file_size)) + except BaseException: + pass + if not is_large: + hash_md5.update(data) + if len(buffer) == 0 and len(data) == part_size: + await uploader.upload(data) + continue + new_len = len(buffer) + len(data) + if new_len >= part_size: + cutoff = part_size - len(buffer) + buffer.extend(data[:cutoff]) + await uploader.upload(bytes(buffer)) + buffer.clear() + buffer.extend(data[cutoff:]) + else: + buffer.extend(data) + if len(buffer) > 0: + await uploader.upload(bytes(buffer)) + await uploader.finish_upload() + if is_large: + return InputFileBig(file_id, part_count, filename), file_size + return InputFile(file_id, part_count, filename, hash_md5.hexdigest()), file_size + + +async def download_file( + client: TelegramClient, + location: TypeLocation, + out: BinaryIO, + progress_callback: callable = None, +) -> BinaryIO: + size = location.size + dc_id, location = utils.get_input_location(location) + # We lock the transfers because telegram has connection count limits + downloader = ParallelTransferrer(client, dc_id) + downloaded = downloader.download(location, size) + async for x in downloaded: + out.write(x) + if progress_callback: + try: + await _maybe_await(progress_callback(out.tell(), size)) + except BaseException: + pass + return out + + +async def upload_file( + client: TelegramClient, + file: BinaryIO, + filename: str, + progress_callback: callable = None, +) -> TypeInputFile: + return ( + await _internal_transfer_to_telegram(client, file, filename, progress_callback) + )[0] diff --git a/pyUltroid/fns/__init__.py b/pyUltroid/fns/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4e761a87af9978c9dcb640820179191cf5229615 --- /dev/null +++ b/pyUltroid/fns/__init__.py @@ -0,0 +1,23 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from .. import * # ignore: pylint + +# https://github.com/bisoncorps/search-engine-parser/blob/ede1355a1f63398d9217b8e502fbd6c52b53bf09/search_engine_parser/core/utils.py#L11 + +some_random_headers = [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/72.0.3626.121 Safari/537.36", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100 101 Firefox/22.0", + "Mozilla/5.0 (Windows NT 6.1; rv:11.0) Gecko/20100101 Firefox/11.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.5 (KHTML, like Gecko) " + "Chrome/19.0.1084.46 Safari/536.5", + "Mozilla/5.0 (Windows; Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) " + "Chrome/19.0.1084.46 Safari/536.5", + "Mozilla/5.0 (X11; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0", +] diff --git a/pyUltroid/fns/admins.py b/pyUltroid/fns/admins.py new file mode 100644 index 0000000000000000000000000000000000000000..771c1be9bb5c2041c8e2ef4c04ea03a4fee3b8e6 --- /dev/null +++ b/pyUltroid/fns/admins.py @@ -0,0 +1,166 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import asyncio +import time +import uuid + +from telethon import Button +from telethon.errors.rpcerrorlist import UserNotParticipantError +from telethon.tl import functions, types + +try: + from .. import _ult_cache + from .._misc import SUDO_M +except ImportError: + _ult_cache = {} + SUDO_M = None + + +def ban_time(time_str): + """Simplify ban time from text""" + if not any(time_str.endswith(unit) for unit in ("s", "m", "h", "d")): + time_str += "s" + unit = time_str[-1] + time_int = time_str[:-1].strip() + if not time_int.isdigit(): + raise Exception("Invalid time amount specified.") + to_return = "" + if unit == "s": + to_return = int(time.time() + int(time_int)) + elif unit == "m": + to_return = int(time.time() + int(time_int) * 60) + elif unit == "h": + to_return = int(time.time() + int(time_int) * 60 * 60) + elif unit == "d": + to_return = int(time.time() + int(time_int) * 24 * 60 * 60) + return to_return + + +# ------------------Admin Check--------------- # + + +async def _callback_check(event): + id_ = str(uuid.uuid1()).split("-")[0] + time.time() + msg = await event.reply( + "Click Below Button to prove self as Admin!", + buttons=Button.inline("Click Me", f"cc_{id_}"), + ) + if not _ult_cache.get("admin_callback"): + _ult_cache.update({"admin_callback": {id_: None}}) + else: + _ult_cache["admin_callback"].update({id_: None}) + while not _ult_cache["admin_callback"].get(id_): + await asyncio.sleep(1) + key = _ult_cache.get("admin_callback", {}).get(id_) + del _ult_cache["admin_callback"][id_] + return key + + +async def get_update_linked_chat(event): + if _ult_cache.get("LINKED_CHATS") and _ult_cache["LINKED_CHATS"].get(event.chat_id): + _ignore = _ult_cache["LINKED_CHATS"][event.chat_id]["linked_chat"] + else: + channel = await event.client( + functions.channels.GetFullChannelRequest(event.chat_id) + ) + _ignore = channel.full_chat.linked_chat_id + if _ult_cache.get("LINKED_CHATS"): + _ult_cache["LINKED_CHATS"].update({event.chat_id: {"linked_chat": _ignore}}) + else: + _ult_cache.update( + {"LINKED_CHATS": {event.chat_id: {"linked_chat": _ignore}}} + ) + return _ignore + + +async def admin_check(event, require=None, silent: bool = False): + if SUDO_M and event.sender_id in SUDO_M.owner_and_sudos(): + return True + callback = None + + # for Anonymous Admin Support + if ( + isinstance(event.sender, (types.Chat, types.Channel)) + and event.sender_id == event.chat_id + ): + if not require: + return True + callback = True + if isinstance(event.sender, types.Channel): + _ignore = await get_update_linked_chat(event) + if _ignore and event.sender.id == _ignore: + return False + callback = True + if callback: + if silent: + # work silently, same check is used for antiflood + # and should not ask for Button Verification. + return + get_ = await _callback_check(event) + if not get_: + return + user, perms = get_ + event._sender_id = user.id + event._sender = user + else: + user = event.sender + try: + perms = await event.client.get_permissions(event.chat_id, user.id) + except UserNotParticipantError: + if not silent: + await event.reply("You need to join this chat First!") + return False + if not perms.is_admin: + if not silent: + await event.eor("Only Admins can use this command!", time=8) + return + if require and not getattr(perms, require, False): + if not silent: + await event.eor(f"You are missing the right of `{require}`", time=8) + return False + return True + + +# ------------------Lock Unlock---------------- + + +def lock_unlock(query, lock=True): + """ + `Used in locks plugin` + Is there any better way to do this? + """ + rights = types.ChatBannedRights(None) + _do = lock + if query == "msgs": + for i in ["send_messages", "invite_users", "pin_messages" "change_info"]: + setattr(rights, i, _do) + elif query == "media": + setattr(rights, "send_media", _do) + elif query == "sticker": + setattr(rights, "send_stickers", _do) + elif query == "gif": + setattr(rights, "send_gifs", _do) + elif query == "games": + setattr(rights, "send_games", _do) + elif query == "inline": + setattr(rights, "send_inline", _do) + elif query == "polls": + setattr(rights, "send_polls", _do) + elif query == "invites": + setattr(rights, "invite_users", _do) + elif query == "pin": + setattr(rights, "pin_messages", _do) + elif query == "changeinfo": + setattr(rights, "change_info", _do) + else: + return None + return rights + + +# ---------------- END ---------------- # diff --git a/pyUltroid/fns/executor.py b/pyUltroid/fns/executor.py new file mode 100644 index 0000000000000000000000000000000000000000..06226842b577d0d8d8c3d29d026812227e1c8def --- /dev/null +++ b/pyUltroid/fns/executor.py @@ -0,0 +1,76 @@ +from asyncio import create_subprocess_exec, subprocess + + +class Terminal: + """ + Class for running terminal commands asynchronously. + + Methods: + + run(commands: str) + commands: Terminal Commands. + Returns Process id (int) + + terminate(pid: int) + pid: Process id returned in `run` method. + Returns True if terminated else False (bool) + + output(pid: int) + pid: Process id returned in `run` method. + Returns Output of process (str) + + error(pid: int) + pid: Process id returned in `run` method. + Returns Error of process (str) + """ + + def __init__(self) -> None: + self._processes = {} + + @staticmethod + def _to_str(data: bytes) -> str: + return data.decode("utf-8").strip() + + async def run(self, *args) -> int: + process = await create_subprocess_exec( + *args, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + pid = process.pid + self._processes[pid] = process + return pid + + def terminate(self, pid: int) -> bool: + try: + self._processes.pop(pid) + self._processes[pid].kill() + return True + except KeyError: + return False + + async def output(self, pid: int) -> str: + output = [] + while True: + out = self._to_str(await self._processes[pid].stdout.readline()) + if not out: + break + output.append(out) + return "\n".join(output) + + async def error(self, pid: int) -> str: + error = [] + while True: + err = self._to_str(await self._processes[pid].stderr.readline()) + if not err: + break + error.append(err) + return "\n".join(error) + + @property + def _auto_remove_processes(self) -> None: + while self._processes: + for proc in self._processes.keys(): + if proc.returncode is not None: # process is still running + try: + self._processes.pop(proc) + except KeyError: + pass diff --git a/pyUltroid/fns/gDrive.py b/pyUltroid/fns/gDrive.py new file mode 100644 index 0000000000000000000000000000000000000000..1c50d83f9fb67f0dc30e9aa4b4d5327115eba771 --- /dev/null +++ b/pyUltroid/fns/gDrive.py @@ -0,0 +1,238 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import time +from io import FileIO +from logging import WARNING +from mimetypes import guess_type + +from apiclient.http import LOGGER, MediaFileUpload, MediaIoBaseDownload +from googleapiclient.discovery import build, logger +from httplib2 import Http +from oauth2client.client import OOB_CALLBACK_URN, OAuth2WebServerFlow +from oauth2client.client import logger as _logger +from oauth2client.file import Storage + +from .. import udB +from .helper import humanbytes, time_formatter + +for log in [LOGGER, logger, _logger]: + log.setLevel(WARNING) + + +class GDriveManager: + def __init__(self): + self._flow = {} + self.gdrive_creds = { + "oauth_scope": [ + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/drive.file", + "https://www.googleapis.com/auth/drive.metadata", + ], + "dir_mimetype": "application/vnd.google-apps.folder", + "redirect_uri": OOB_CALLBACK_URN, + } + self.auth_token = udB.get_key("GDRIVE_AUTH_TOKEN") + self.folder_id = udB.get_key("GDRIVE_FOLDER_ID") + self.token_file = "resources/auth/gdrive_creds.json" + + @staticmethod + def _create_download_link(fileId: str): + return f"https://drive.google.com/uc?id={fileId}&export=download" + + @staticmethod + def _create_folder_link(folderId: str): + return f"https://drive.google.com/folderview?id={folderId}" + + def _create_token_file(self, code: str = None): + if code and self._flow: + _auth_flow = self._flow["_"] + credentials = _auth_flow.step2_exchange(code) + Storage(self.token_file).put(credentials) + return udB.set_key("GDRIVE_AUTH_TOKEN", str(open(self.token_file).read())) + try: + _auth_flow = OAuth2WebServerFlow( + udB.get_key("GDRIVE_CLIENT_ID") + or "458306970678-jhfbv6o5sf1ar63o1ohp4c0grblp8qba.apps.googleusercontent.com", + udB.get_key("GDRIVE_CLIENT_SECRET") + or "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", + self.gdrive_creds["oauth_scope"], + redirect_uri=self.gdrive_creds["redirect_uri"], + ) + self._flow["_"] = _auth_flow + except KeyError: + return "Fill GDRIVE client credentials" + return _auth_flow.step1_get_authorize_url() + + @property + def _http(self): + storage = Storage(self.token_file) + creds = storage.get() + http = Http() + http.redirect_codes = http.redirect_codes - {308} + creds.refresh(http) + return creds.authorize(http) + + @property + def _build(self): + return build("drive", "v2", http=self._http, cache_discovery=False) + + def _set_permissions(self, fileId: str): + _permissions = { + "role": "reader", + "type": "anyone", + "value": None, + "withLink": True, + } + self._build.permissions().insert( + fileId=fileId, body=_permissions, supportsAllDrives=True + ).execute(http=self._http) + + async def _upload_file( + self, event, path: str, filename: str = None, folder_id: str = None + ): + last_txt = "" + if not filename: + filename = path.split("/")[-1] + mime_type = guess_type(path)[0] or "application/octet-stream" + media_body = MediaFileUpload(path, mimetype=mime_type, resumable=True) + body = { + "title": filename, + "description": "Uploaded using Ultroid Userbot", + "mimeType": mime_type, + } + if folder_id: + body["parents"] = [{"id": folder_id}] + elif self.folder_id: + body["parents"] = [{"id": self.folder_id}] + upload = self._build.files().insert( + body=body, media_body=media_body, supportsAllDrives=True + ) + start = time.time() + _status = None + while not _status: + _progress, _status = upload.next_chunk(num_retries=3) + if _progress: + diff = time.time() - start + completed = _progress.resumable_progress + total_size = _progress.total_size + percentage = round((completed / total_size) * 100, 2) + speed = round(completed / diff, 2) + eta = round((total_size - completed) / speed, 2) * 1000 + crnt_txt = ( + f"`Uploading {filename} to GDrive...\n\n" + + f"Status: {humanbytes(completed)}/{humanbytes(total_size)} »» {percentage}%\n" + + f"Speed: {humanbytes(speed)}/s\n" + + f"ETA: {time_formatter(eta)}`" + ) + if round((diff % 10.00) == 0) or last_txt != crnt_txt: + await event.edit(crnt_txt) + last_txt = crnt_txt + fileId = _status.get("id") + try: + self._set_permissions(fileId=fileId) + except BaseException: + pass + _url = self._build.files().get(fileId=fileId, supportsAllDrives=True).execute() + return _url.get("webContentLink") + + async def _download_file(self, event, fileId: str, filename: str = None): + last_txt = "" + if fileId.startswith("http"): + if "=download" in fileId: + fileId = fileId.split("=")[1][:-7] + elif "/view" in fileId: + fileId = fileId.split("/")[::-1][1] + try: + if not filename: + filename = ( + self._build.files() + .get(fileId=fileId, supportsAllDrives=True) + .execute()["title"] + ) + downloader = self._build.files().get_media( + fileId=fileId, supportsAllDrives=True + ) + except Exception as ex: + return False, str(ex) + with FileIO(filename, "wb") as file: + start = time.time() + download = MediaIoBaseDownload(file, downloader) + _status = None + while not _status: + _progress, _status = download.next_chunk(num_retries=3) + if _progress: + diff = time.time() - start + completed = _progress.resumable_progress + total_size = _progress.total_size + percentage = round((completed / total_size) * 100, 2) + speed = round(completed / diff, 2) + eta = round((total_size - completed) / speed, 2) * 1000 + crnt_txt = ( + f"`Downloading {filename} from GDrive...\n\n" + + f"Status: {humanbytes(completed)}/{humanbytes(total_size)} »» {percentage}%\n" + + f"Speed: {humanbytes(speed)}/s\n" + + f"ETA: {time_formatter(eta)}`" + ) + if round((diff % 10.00) == 0) or last_txt != crnt_txt: + await event.edit(crnt_txt) + last_txt = crnt_txt + return True, filename + + @property + def _list_files(self): + _items = ( + self._build.files() + .list( + supportsTeamDrives=True, + includeTeamDriveItems=True, + spaces="drive", + fields="nextPageToken, items(id, title, mimeType)", + pageToken=None, + ) + .execute() + ) + _files = {} + for files in _items["items"]: + if files["mimeType"] == self.gdrive_creds["dir_mimetype"]: + _files[self._create_folder_link(files["id"])] = files["title"] + else: + _files[self._create_download_link(files["id"])] = files["title"] + return _files + + def create_directory(self, directory): + body = { + "title": directory, + "mimeType": self.gdrive_creds["dir_mimetype"], + } + if self.folder_id: + body["parents"] = [{"id": self.folder_id}] + file = self._build.files().insert(body=body, supportsAllDrives=True).execute() + fileId = file.get("id") + self._set_permissions(fileId=fileId) + return fileId + + def search(self, title): + query = f"title contains '{title}'" + if self.folder_id: + query = f"'{self.folder_id}' in parents and (title contains '{title}')" + _items = ( + self._build.files() + .list( + supportsTeamDrives=True, + includeTeamDriveItems=True, + q=query, + spaces="drive", + fields="nextPageToken, items(id, title, mimeType)", + pageToken=None, + ) + .execute() + ) + _files = {} + for files in _items["items"]: + _files[self._create_download_link(files["id"])] = files["title"] + return _files diff --git a/pyUltroid/fns/helper.py b/pyUltroid/fns/helper.py new file mode 100644 index 0000000000000000000000000000000000000000..8d1149ba1e18b5378758e2e2f0df4b37258cbf28 --- /dev/null +++ b/pyUltroid/fns/helper.py @@ -0,0 +1,639 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import asyncio +import math +import os +import re +import sys +import time +from traceback import format_exc +from urllib.parse import unquote +from urllib.request import urlretrieve + +from .. import run_as_module + +if run_as_module: + from ..configs import Var + + +try: + from aiohttp import ClientSession as aiohttp_client, ClientTimeout as aiohttp_timeout +except ImportError: + aiohttp_client = None + aiohttp_timeout = None + try: + import requests + except ImportError: + requests = None + +try: + import heroku3 +except ImportError: + heroku3 = None + +try: + from git import Repo + from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError +except ImportError: + Repo = None + + +import asyncio +import multiprocessing +from concurrent.futures import ThreadPoolExecutor +from functools import partial, wraps + +from telethon.helpers import _maybe_await +from telethon.tl import types +from telethon.utils import get_display_name + +from .._misc import CMD_HELP +from .._misc._wrappers import eod, eor +from ..exceptions import DependencyMissingError +from . import * + +if run_as_module: + from ..dB._core import ADDONS, HELP, LIST, LOADED + +from ..version import ultroid_version +from .FastTelethon import download_file as downloadable +from .FastTelethon import upload_file as uploadable + + +def run_async(function): + @wraps(function) + async def wrapper(*args, **kwargs): + return await asyncio.get_event_loop().run_in_executor( + ThreadPoolExecutor(max_workers=multiprocessing.cpu_count() * 5), + partial(function, *args, **kwargs), + ) + + return wrapper + + +# ~~~~~~~~~~~~~~~~~~~~ small funcs ~~~~~~~~~~~~~~~~~~~~ # + + +class KEEP_SAFE: + """Patterns to scan for in plugins before allowing installation.""" + + All = [ + r"os\.system", + r"subprocess\.(run|call|Popen|check_output)", + r"exec\s*\(", + r"eval\s*\(", + r"__import__\s*\(", + r"open\s*\(.*['\"]w['\"]", + r"shutil\.(rmtree|move|copy)", + r"session\.string", + r"get_me\(\)", + r"api_id", + r"api_hash", + ] + + +def make_mention(user, custom=None): + if user.username: + return f"@{user.username}" + return inline_mention(user, custom=custom) + + +def inline_mention(user, custom=None, html=False): + mention_text = get_display_name(user) or "Deleted Account" if not custom else custom + if isinstance(user, types.User): + if html: + return f'{mention_text}' + return f"[{mention_text}](tg://user?id={user.id})" + if isinstance(user, types.Channel) and user.username: + if html: + return f'{mention_text}' + return f"[{mention_text}](https://t.me/{user.username})" + return mention_text + + +# ----------------- Load \\ Unloader ---------------- # + + +def un_plug(shortname): + from .. import asst, ultroid_bot + + try: + all_func = LOADED[shortname] + for client in [ultroid_bot, asst]: + for x, _ in client.list_event_handlers(): + if x in all_func: + client.remove_event_handler(x) + del LOADED[shortname] + del LIST[shortname] + ADDONS.remove(shortname) + except (ValueError, KeyError): + name = f"addons.{shortname}" + for client in [ultroid_bot, asst]: + for i in reversed(range(len(client._event_builders))): + ev, cb = client._event_builders[i] + if cb.__module__ == name: + del client._event_builders[i] + try: + del LOADED[shortname] + del LIST[shortname] + ADDONS.remove(shortname) + except KeyError: + pass + + +if run_as_module: + + async def safeinstall(event): + from .. import HNDLR + from ..startup.utils import load_addons + + if not event.reply_to: + return await eod( + event, f"Please use `{HNDLR}install` as reply to a .py file." + ) + ok = await eor(event, "`Installing...`") + reply = await event.get_reply_message() + if not ( + reply.media + and hasattr(reply.media, "document") + and reply.file.name + and reply.file.name.endswith(".py") + ): + return await eod(ok, "`Please reply to any python plugin`") + plug = reply.file.name.replace(".py", "") + if plug in list(LOADED): + return await eod(ok, f"Plugin `{plug}` is already installed.") + sm = reply.file.name.replace("_", "-").replace("|", "-") + dl = await reply.download_media(f"addons/{sm}") + if event.text[9:] != "f": + read = open(dl).read() + for dan in KEEP_SAFE().All: + if re.search(dan, read): + os.remove(dl) + return await ok.edit( + f"**Installation Aborted.**\n**Reason:** Occurance of `{dan}` in `{reply.file.name}`.\n\nIf you trust the provider and/or know what you're doing, use `{HNDLR}install f` to force install.", + ) + try: + load_addons(dl) # dl.split("/")[-1].replace(".py", "")) + except BaseException: + os.remove(dl) + return await eor(ok, f"**ERROR**\n\n`{format_exc()}`", time=30) + plug = sm.replace(".py", "") + if plug in HELP: + output = "**Plugin** - `{}`\n".format(plug) + for i in HELP[plug]: + output += i + output += "\n© @TeamUltroid" + await eod(ok, f"✓ `Ultroid - Installed`: `{plug}` ✓\n\n{output}") + elif plug in CMD_HELP: + output = f"Plugin Name-{plug}\n\n✘ Commands Available-\n\n" + output += str(CMD_HELP[plug]) + await eod(ok, f"✓ `Ultroid - Installed`: `{plug}` ✓\n\n{output}") + else: + try: + x = f"Plugin Name-{plug}\n\n✘ Commands Available-\n\n" + for d in LIST[plug]: + x += HNDLR + d + "\n" + await eod(ok, f"✓ `Ultroid - Installed`: `{plug}` ✓\n\n`{x}`") + except BaseException: + await eod(ok, f"✓ `Ultroid - Installed`: `{plug}` ✓") + + async def heroku_logs(event): + """ + post heroku logs + """ + from .. import LOGS + + xx = await eor(event, "`Processing...`") + if not (Var.HEROKU_API and Var.HEROKU_APP_NAME): + return await xx.edit( + "Please set `HEROKU_APP_NAME` and `HEROKU_API` in vars." + ) + try: + app = (heroku3.from_key(Var.HEROKU_API)).app(Var.HEROKU_APP_NAME) + except BaseException as se: + LOGS.info(se) + return await xx.edit( + "`HEROKU_API` and `HEROKU_APP_NAME` is wrong! Kindly re-check in config vars." + ) + await xx.edit("`Downloading Logs...`") + ok = app.get_log() + with open("ultroid-heroku.log", "w") as log: + log.write(ok) + await event.client.send_file( + event.chat_id, + file="ultroid-heroku.log", + thumb=ULTConfig.thumb, + caption="**Ultroid Heroku Logs.**", + ) + + os.remove("ultroid-heroku.log") + await xx.delete() + + async def def_logs(ult, file): + await ult.respond( + "**Ultroid Logs.**", + file=file, + thumb=ULTConfig.thumb, + ) + + async def updateme_requirements(): + """Update requirements..""" + await bash( + f"{sys.executable} -m pip install --no-cache-dir -r requirements.txt" + ) + + @run_async + def gen_chlog(repo, diff): + """Generate Changelogs...""" + UPSTREAM_REPO_URL = ( + Repo().remotes[0].config_reader.get("url").replace(".git", "") + ) + ac_br = repo.active_branch.name + ch_log = tldr_log = "" + ch = f"Ultroid {ultroid_version} updates for [{ac_br}]:" + ch_tl = f"Ultroid {ultroid_version} updates for {ac_br}:" + d_form = "%d/%m/%y || %H:%M" + for c in repo.iter_commits(diff): + ch_log += f"\n\n💬 {c.count()} 🗓 [{c.committed_datetime.strftime(d_form)}]\n[{c.summary}] 👨‍💻 {c.author}" + tldr_log += f"\n\n💬 {c.count()} 🗓 [{c.committed_datetime.strftime(d_form)}]\n[{c.summary}] 👨‍💻 {c.author}" + if ch_log: + return str(ch + ch_log), str(ch_tl + tldr_log) + return ch_log, tldr_log + + +# --------------------------------------------------------------------- # + + +async def bash(cmd, run_code=0): + """ + run any command in subprocess and get output or error.""" + process = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await process.communicate() + err = stderr.decode().strip() or None + out = stdout.decode().strip() + if not run_code and err: + if match := re.match("\/bin\/sh: (.*): ?(\w+): not found", err): + return out, f"{match.group(2).upper()}_NOT_FOUND" + return out, err + + +# ---------------------------UPDATER-------------------------------- # +# Will add in class + + +async def updater(): + from .. import LOGS + + try: + off_repo = Repo().remotes[0].config_reader.get("url").replace(".git", "") + except Exception as er: + LOGS.exception(er) + return + try: + repo = Repo() + except NoSuchPathError as error: + LOGS.info(f"`directory {error} is not found`") + Repo().__del__() + return + except GitCommandError as error: + LOGS.info(f"`Early failure! {error}`") + Repo().__del__() + return + except InvalidGitRepositoryError: + repo = Repo.init() + origin = repo.create_remote("upstream", off_repo) + origin.fetch() + repo.create_head("main", origin.refs.main) + repo.heads.main.set_tracking_branch(origin.refs.main) + repo.heads.main.checkout(True) + ac_br = repo.active_branch.name + repo.create_remote("upstream", off_repo) if "upstream" not in repo.remotes else None + ups_rem = repo.remote("upstream") + ups_rem.fetch(ac_br) + changelog, tl_chnglog = await gen_chlog(repo, f"HEAD..upstream/{ac_br}") + return bool(changelog) + + +# ----------------Fast Upload/Download---------------- +# @1danish_00 @new-dev0 @buddhhu + + +async def uploader(file, name, taime, event, msg): + with open(file, "rb") as f: + result = await uploadable( + client=event.client, + file=f, + filename=name, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + event, + taime, + msg, + ), + ), + ) + return result + + +async def downloader(filename, file, event, taime, msg): + with open(filename, "wb") as fk: + result = await downloadable( + client=event.client, + location=file, + out=fk, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + event, + taime, + msg, + ), + ), + ) + return result + + +# ~~~~~~~~~~~~~~~Async Searcher~~~~~~~~~~~~~~~ +# @buddhhu + + +async def async_searcher( + url: str, + post: bool = False, + head: bool = False, + headers: dict = None, + evaluate=None, + object: bool = False, + re_json: bool = False, + re_content: bool = False, + *args, + **kwargs, +): + if aiohttp_client: + async with aiohttp_client(headers=headers) as client: + method = client.head if head else (client.post if post else client.get) + data = await method(url, *args, **kwargs) + if evaluate: + return await evaluate(data) + if re_json: + return await data.json(content_type=None) + if re_content: + return await data.read() + if head or object: + return data + return await data.text() + # elif requests: + # method = requests.head if head else (requests.post if post else requests.get) + # data = method(url, headers=headers, *args, **kwargs) + # if re_json: + # return data.json() + # if re_content: + # return data.content + # if head or object: + # return data + # return data.text + else: + raise DependencyMissingError("install 'aiohttp' to use this.") + + +# ~~~~~~~~~~~~~~~~~~~~DDL Downloader~~~~~~~~~~~~~~~~~~~~ +# @buddhhu @new-dev0 + + +async def download_file(link, name, validate=False): + """for files, without progress callback with aiohttp""" + + async def _download(content): + if validate and "application/json" in content.headers.get("Content-Type"): + return None, await content.json() + with open(name, "wb") as file: + file.write(await content.read()) + return name, "" + + return await async_searcher(link, evaluate=_download) + + +async def fast_download(download_url, filename=None, progress_callback=None): + if not aiohttp_client: + return await download_file(download_url, filename)[0], None + _timeout = aiohttp_timeout(total=None) if aiohttp_timeout else None + async with aiohttp_client(timeout=_timeout) as session: + async with session.get(download_url) as response: + if not filename: + filename = unquote(download_url.rpartition("/")[-1]) + total_size = int(response.headers.get("content-length", 0)) or None + downloaded_size = 0 + start_time = time.time() + with open(filename, "wb") as f: + async for chunk in response.content.iter_chunked(1024): + if chunk: + f.write(chunk) + downloaded_size += len(chunk) + if progress_callback and total_size: + await _maybe_await( + progress_callback(downloaded_size, total_size) + ) + return filename, time.time() - start_time + + +# --------------------------Media Funcs-------------------------------- # + + +def mediainfo(media): + xx = str((str(media)).split("(", maxsplit=1)[0]) + m = "" + if xx == "MessageMediaDocument": + mim = media.document.mime_type + if mim == "application/x-tgsticker": + m = "sticker animated" + elif "image" in mim: + if mim == "image/webp": + m = "sticker" + elif mim == "image/gif": + m = "gif as doc" + else: + m = "pic as doc" + elif "video" in mim: + if "DocumentAttributeAnimated" in str(media): + m = "gif" + elif "DocumentAttributeVideo" in str(media): + i = str(media.document.attributes[0]) + if "supports_streaming=True" in i: + m = "video" + m = "video as doc" + else: + m = "video" + elif "audio" in mim: + m = "audio" + else: + m = "document" + elif xx == "MessageMediaPhoto": + m = "pic" + elif xx == "MessageMediaWebPage": + m = "web" + return m + + +# ------------------Some Small Funcs---------------- + + +def time_formatter(milliseconds): + minutes, seconds = divmod(int(milliseconds / 1000), 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + weeks, days = divmod(days, 7) + tmp = ( + ((str(weeks) + "w:") if weeks else "") + + ((str(days) + "d:") if days else "") + + ((str(hours) + "h:") if hours else "") + + ((str(minutes) + "m:") if minutes else "") + + ((str(seconds) + "s") if seconds else "") + ) + if not tmp: + return "0s" + + if tmp.endswith(":"): + return tmp[:-1] + return tmp + + +def humanbytes(size): + if not size: + return "0 B" + for unit in ["", "K", "M", "G", "T"]: + if size < 1024: + break + size /= 1024 + if isinstance(size, int): + size = f"{size}{unit}B" + elif isinstance(size, float): + size = f"{size:.2f}{unit}B" + return size + + +def numerize(number): + if not number: + return None + unit = "" + for unit in ["", "K", "M", "B", "T"]: + if number < 1000: + break + number /= 1000 + if isinstance(number, int): + number = f"{number}{unit}" + elif isinstance(number, float): + number = f"{number:.2f}{unit}" + return number + + +No_Flood = {} + + +async def progress(current, total, event, start, type_of_ps, file_name=None): + now = time.time() + if No_Flood.get(event.chat_id): + if No_Flood[event.chat_id].get(event.id): + if (now - No_Flood[event.chat_id][event.id]) < 1.1: + return + else: + No_Flood[event.chat_id].update({event.id: now}) + else: + No_Flood.update({event.chat_id: {event.id: now}}) + diff = time.time() - start + if round(diff % 10.00) == 0 or current == total: + percentage = current * 100 / total + speed = current / diff + time_to_completion = round((total - current) / speed) * 1000 + progress_str = "`[{0}{1}] {2}%`\n\n".format( + "".join("●" for i in range(math.floor(percentage / 5))), + "".join("" for i in range(20 - math.floor(percentage / 5))), + round(percentage, 2), + ) + + tmp = ( + progress_str + + "`{0} of {1}`\n\n`✦ Speed: {2}/s`\n\n`✦ ETA: {3}`\n\n".format( + humanbytes(current), + humanbytes(total), + humanbytes(speed), + time_formatter(time_to_completion), + ) + ) + if file_name: + await event.edit( + "`✦ {}`\n\n`File Name: {}`\n\n{}".format(type_of_ps, file_name, tmp) + ) + else: + await event.edit("`✦ {}`\n\n{}".format(type_of_ps, tmp)) + + +# ------------------System\\Heroku stuff---------------- +# @xditya @sppidy @techierror + + +async def restart(ult=None): + if Var.HEROKU_APP_NAME and Var.HEROKU_API: + try: + Heroku = heroku3.from_key(Var.HEROKU_API) + app = Heroku.apps()[Var.HEROKU_APP_NAME] + if ult: + await ult.edit("`Restarting your app, please wait for a minute!`") + app.restart() + except BaseException as er: + if ult: + return await eor( + ult, + "`HEROKU_API` or `HEROKU_APP_NAME` is wrong! Kindly re-check in config vars.", + ) + LOGS.exception(er) + else: + if len(sys.argv) == 1: + os.execl(sys.executable, sys.executable, "-m", "pyUltroid") + else: + os.execl( + sys.executable, + sys.executable, + "-m", + "pyUltroid", + sys.argv[1], + sys.argv[2], + sys.argv[3], + sys.argv[4], + sys.argv[5], + sys.argv[6], + ) + + +async def shutdown(ult): + from .. import HOSTED_ON, LOGS + + ult = await eor(ult, "Shutting Down") + if HOSTED_ON == "heroku": + if not (Var.HEROKU_APP_NAME and Var.HEROKU_API): + return await ult.edit("Please Fill `HEROKU_APP_NAME` and `HEROKU_API`") + dynotype = os.getenv("DYNO").split(".")[0] + try: + Heroku = heroku3.from_key(Var.HEROKU_API) + app = Heroku.apps()[Var.HEROKU_APP_NAME] + await ult.edit("`Shutting Down your app, please wait for a minute!`") + app.process_formation()[dynotype].scale(0) + except BaseException as e: + LOGS.exception(e) + return await ult.edit( + "`HEROKU_API` and `HEROKU_APP_NAME` is wrong! Kindly re-check in config vars." + ) + else: + sys.exit() diff --git a/pyUltroid/fns/info.py b/pyUltroid/fns/info.py new file mode 100644 index 0000000000000000000000000000000000000000..7a944225566d29af482e8502b58865915830c304 --- /dev/null +++ b/pyUltroid/fns/info.py @@ -0,0 +1,182 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +# -----------------Random Stuff-------------- + +import math + +from telethon.tl import functions, types + +from .. import LOGS + +# ----------- +# @buddhhu + + +async def get_uinfo(e): + user, data = None, None + reply = await e.get_reply_message() + if reply: + user = await e.client.get_entity(reply.sender_id) + data = e.pattern_match.group(1) + else: + ok = e.pattern_match.group(1).split(maxsplit=1) + if len(ok) > 1: + data = ok[1] + try: + user = await e.client.get_entity(await e.client.parse_id(ok[0])) + except IndexError: + pass + except ValueError as er: + await e.eor(str(er)) + return None, None + return user, data + + +# Random stuffs dk who added + + +async def get_chat_info(chat, event): + if isinstance(chat, types.Channel): + chat_info = await event.client(functions.channels.GetFullChannelRequest(chat)) + elif isinstance(chat, types.Chat): + chat_info = await event.client(functions.messages.GetFullChatRequest(chat)) + else: + return await event.eor("`Use this for Group/Channel.`") + full = chat_info.full_chat + chat_photo = full.chat_photo + broadcast = getattr(chat, "broadcast", False) + chat_type = "Channel" if broadcast else "Group" + chat_title = chat.title + try: + msg_info = await event.client( + functions.messages.GetHistoryRequest( + peer=chat.id, + offset_id=0, + offset_date=None, + add_offset=-0, + limit=0, + max_id=0, + min_id=0, + hash=0, + ) + ) + except Exception as er: + msg_info = None + if not event.client._bot: + LOGS.exception(er) + first_msg_valid = bool( + msg_info and msg_info.messages and msg_info.messages[0].id == 1 + ) + + creator_valid = bool(first_msg_valid and msg_info.users) + creator_id = msg_info.users[0].id if creator_valid else None + creator_firstname = ( + msg_info.users[0].first_name + if creator_valid and msg_info.users[0].first_name is not None + else "Deleted Account" + ) + creator_username = ( + msg_info.users[0].username + if creator_valid and msg_info.users[0].username is not None + else None + ) + created = msg_info.messages[0].date if first_msg_valid else None + if not isinstance(chat.photo, types.ChatPhotoEmpty): + dc_id = chat.photo.dc_id + else: + dc_id = "Null" + + restricted_users = getattr(full, "banned_count", None) + members = getattr(full, "participants_count", chat.participants_count) + admins = getattr(full, "admins_count", None) + banned_users = getattr(full, "kicked_count", None) + members_online = getattr(full, "online_count", 0) + group_stickers = ( + full.stickerset.title if getattr(full, "stickerset", None) else None + ) + messages_viewable = msg_info.count if msg_info else None + messages_sent = getattr(full, "read_inbox_max_id", None) + messages_sent_alt = getattr(full, "read_outbox_max_id", None) + exp_count = getattr(full, "pts", None) + supergroup = "Yes" if getattr(chat, "megagroup", None) else "No" + creator_username = "@{}".format(creator_username) if creator_username else None + + if admins is None: + try: + participants_admins = await event.client( + functions.channels.GetParticipantsRequest( + channel=chat.id, + filter=types.ChannelParticipantsAdmins(), + offset=0, + limit=0, + hash=0, + ) + ) + admins = participants_admins.count if participants_admins else None + except Exception as e: + LOGS.info(f"Exception: {e}") + caption = "ℹ️ [CHAT INFO]\n" + caption += f"🆔 ID: {chat.id}\n" + if chat_title is not None: + caption += f"📛 {chat_type} name: {chat_title}\n" + if chat.username: + caption += f"🔗 Link: @{chat.username}\n" + else: + caption += f"🗳 {chat_type} type: Private\n" + if creator_username: + caption += f"🖌 Creator: {creator_username}\n" + elif creator_valid: + caption += f'🖌 Creator: {creator_firstname}\n' + if created: + caption += f"🖌 Created: {created.date().strftime('%b %d, %Y')} - {created.time()}\n" + else: + caption += f"🖌 Created: {chat.date.date().strftime('%b %d, %Y')} - {chat.date.time()} ⚠\n" + caption += f"🗡 Data Centre ID: {dc_id}\n" + if exp_count is not None: + chat_level = int((1 + math.sqrt(1 + 7 * exp_count / 14)) / 2) + caption += f"⭐️ {chat_type} level: {chat_level}\n" + if messages_viewable is not None: + caption += f"💬 Viewable messages: {messages_viewable}\n" + if messages_sent: + caption += f"💬 Messages sent: {messages_sent}\n" + elif messages_sent_alt: + caption += f"💬 Messages sent: {messages_sent_alt} ⚠\n" + if members is not None: + caption += f"👥 Members: {members}\n" + if admins: + caption += f"👮 Administrators: {admins}\n" + if full.bot_info: + caption += f"🤖 Bots: {len(full.bot_info)}\n" + if members_online: + caption += f"👀 Currently online: {members_online}\n" + if restricted_users is not None: + caption += f"🔕 Restricted users: {restricted_users}\n" + if banned_users: + caption += f"📨 Banned users: {banned_users}\n" + if group_stickers: + caption += f'📹 {chat_type} stickers: {group_stickers}\n' + if not broadcast: + if getattr(chat, "slowmode_enabled", None): + caption += f"👉 Slow mode: True" + caption += f", 🕐 {full.slowmode_seconds}s\n" + else: + caption += f"🦸‍♂ Supergroup: {supergroup}\n" + if getattr(chat, "restricted", None): + caption += f"🎌 Restricted: {chat.restricted}\n" + rist = chat.restriction_reason[0] + caption += f"> Platform: {rist.platform}\n" + caption += f"> Reason: {rist.reason}\n" + caption += f"> Text: {rist.text}\n\n" + if getattr(chat, "scam", None): + caption += "⚠ Scam: Yes\n" + if getattr(chat, "verified", None): + caption += f"✅ Verified by Telegram: Yes\n\n" + if full.about: + caption += f"🗒 Description: \n{full.about}\n" + return chat_photo, caption diff --git a/pyUltroid/fns/misc.py b/pyUltroid/fns/misc.py new file mode 100644 index 0000000000000000000000000000000000000000..ec6068d7aa68364ec54e9069da9fc19fb3526ea8 --- /dev/null +++ b/pyUltroid/fns/misc.py @@ -0,0 +1,452 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import base64 +import os +import random +import re +import string +from logging import WARNING +from random import choice, randrange, shuffle +from traceback import format_exc +from catbox import CatboxUploader + +from pyUltroid.exceptions import DependencyMissingError + +try: + from aiohttp import ContentTypeError +except ImportError: + ContentTypeError = None + +from telethon.tl import types +from telethon.utils import get_display_name, get_peer_id + +from .. import * +from .._misc._wrappers import eor + +if run_as_module: + from ..dB import DEVLIST + from ..dB._core import LIST + +from . import some_random_headers +from .helper import async_searcher +from .tools import check_filename, json_parser + +try: + import aiohttp +except ImportError: + aiohttp = None + +try: + from PIL import Image +except ImportError: + Image = None + +try: + import cv2 +except ImportError: + cv2 = None +try: + import numpy as np +except ImportError: + np = None + +try: + from bs4 import BeautifulSoup +except ImportError: + BeautifulSoup = None + +uploader = CatboxUploader() + +async def randomchannel( + tochat, channel, range1, range2, caption=None, client=ultroid_bot +): + do = randrange(range1, range2) + async for x in client.iter_messages(channel, add_offset=do, limit=1): + caption = caption or x.text + try: + await client.send_message(tochat, caption, file=x.media) + except BaseException: + pass + + +# -------------------------------------------------- + + +async def YtDataScraper(url: str): + to_return = {} + data = json_parser( + BeautifulSoup( + await async_searcher(url), + "html.parser", + ) + .find_all("script")[41] + .text[20:-1] + )["contents"] + _common_data = data["twoColumnWatchNextResults"]["results"]["results"]["contents"] + common_data = _common_data[0]["videoPrimaryInfoRenderer"] + try: + description_data = _common_data[1]["videoSecondaryInfoRenderer"]["description"][ + "runs" + ] + except (KeyError, IndexError): + description_data = [{"text": "U hurrr from here"}] + description = "".join( + description_datum["text"] for description_datum in description_data + ) + to_return["title"] = common_data["title"]["runs"][0]["text"] + to_return["views"] = ( + common_data["viewCount"]["videoViewCountRenderer"]["shortViewCount"][ + "simpleText" + ] + or common_data["viewCount"]["videoViewCountRenderer"]["viewCount"]["simpleText"] + ) + to_return["publish_date"] = common_data["dateText"]["simpleText"] + to_return["likes"] = ( + common_data["videoActions"]["menuRenderer"]["topLevelButtons"][0][ + "toggleButtonRenderer" + ]["defaultText"]["simpleText"] + # or like_dislike[0]["toggleButtonRenderer"]["defaultText"]["accessibility"][ + # "accessibilityData" + # ]["label"] + ) + to_return["description"] = description + return to_return + + +# -------------------------------------------------- + + +async def google_search(query): + query = query.replace(" ", "+") + _base = "https://google.com" + headers = { + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "User-Agent": choice(some_random_headers), + } + con = await async_searcher(_base + "/search?q=" + query, headers=headers) + soup = BeautifulSoup(con, "html.parser") + result = [] + pdata = soup.find_all("a", href=re.compile("url=")) + for data in pdata: + if not data.find("div"): + continue + try: + result.append( + { + "title": data.find("div").text, + "link": data["href"].split("&url=")[1].split("&ved=")[0], + "description": data.find_all("div")[-1].text, + } + ) + except BaseException as er: + LOGS.exception(er) + return result + + +# ---------------------------------------------------- + + +async def allcmds(event, telegraph): + txt = "" + for z in LIST.keys(): + txt += f"PLUGIN NAME: {z}\n" + for zz in LIST[z]: + txt += HNDLR + zz + "\n" + txt += "\n\n" + t = telegraph.create_page(title="Ultroid All Cmds", content=[txt]) + await eor(event, f"All Ultroid Cmds : [Click Here]({t['url']})", link_preview=False) + + +async def ReTrieveFile(input_file_name): + if not aiohttp: + raise DependencyMissingError("This function needs 'aiohttp' to be installed.") + RMBG_API = udB.get_key("RMBG_API") + headers = {"X-API-Key": RMBG_API} + files = {"image_file": open(input_file_name, "rb").read()} + async with aiohttp.ClientSession() as ses: + async with ses.post( + "https://api.remove.bg/v1.0/removebg", headers=headers, data=files + ) as out: + contentType = out.headers.get("content-type") + if "image" not in contentType: + return False, (await out.json()) + + name = check_filename("ult-rmbg.png") + with open(name, "wb") as file: + file.write(await out.read()) + return True, name + + +# ---------------- Unsplash Search ---------------- +# @New-Dev0 + + +async def unsplashsearch(query, limit=None, shuf=True): + query = query.replace(" ", "-") + link = "https://unsplash.com/s/photos/" + query + extra = await async_searcher(link, re_content=True) + res = BeautifulSoup(extra, "html.parser", from_encoding="utf-8") + all_ = res.find_all("img", srcset=re.compile("images.unsplash.com/photo")) + if shuf: + shuffle(all_) + return list(map(lambda e: e['src'], all_[:limit])) + + +# ---------------- Random User Gen ---------------- +# @xditya + + +async def get_random_user_data(): + base_url = "https://randomuser.me/api/" + cc = await async_searcher( + "https://random-data-api.com/api/business_credit_card/random_card", re_json=True + ) + card = ( + "**CARD_ID:** " + + str(cc["credit_card_number"]) + + f" {cc['credit_card_expiry_date']}\n" + + f"**C-ID :** {cc['id']}" + ) + data_ = (await async_searcher(base_url, re_json=True))["results"][0] + _g = data_["gender"] + gender = "🤵🏻‍♂" if _g == "male" else "🤵🏻‍♀" + name = data_["name"] + loc = data_["location"] + dob = data_["dob"] + msg = """ +{} **Name:** {}.{} {} +**Street:** {} {} +**City:** {} +**State:** {} +**Country:** {} +**Postal Code:** {} +**Email:** {} +**Phone:** {} +**Card:** {} +**Birthday:** {} +""".format( + gender, + name["title"], + name["first"], + name["last"], + loc["street"]["number"], + loc["street"]["name"], + loc["city"], + loc["state"], + loc["country"], + loc["postcode"], + data_["email"], + data_["phone"], + card, + dob["date"][:10], + ) + pic = data_["picture"]["large"] + return msg, pic + + +# Dictionary (Synonyms and Antonyms) + + +async def get_synonyms_or_antonyms(word, type_of_words): + if type_of_words not in ["synonyms", "antonyms"]: + return "Dude! Please give a corrent type of words you want." + s = await async_searcher( + f"https://tuna.thesaurus.com/pageData/{word}", re_json=True + ) + li_1 = [ + y + for x in [ + s["data"]["definitionData"]["definitions"][0][type_of_words], + s["data"]["definitionData"]["definitions"][1][type_of_words], + ] + for y in x + ] + return [y["term"] for y in li_1] + + +# Quotly + + +class Quotly: + _API = "https://quoteampi.onrender.com/generate" + _entities = { + types.MessageEntityPhone: "phone_number", + types.MessageEntityMention: "mention", + types.MessageEntityBold: "bold", + types.MessageEntityCashtag: "cashtag", + types.MessageEntityStrike: "strikethrough", + types.MessageEntityHashtag: "hashtag", + types.MessageEntityEmail: "email", + types.MessageEntityMentionName: "text_mention", + types.MessageEntityUnderline: "underline", + types.MessageEntityUrl: "url", + types.MessageEntityTextUrl: "text_link", + types.MessageEntityBotCommand: "bot_command", + types.MessageEntityCode: "code", + types.MessageEntityPre: "pre", + types.MessageEntitySpoiler: "spoiler", + } + + async def _format_quote(self, event, reply=None, sender=None, type_="private"): + async def telegraph(file_): + file = file_ + ".png" + Image.open(file_).save(file, "PNG") + uri = uploader.upload_file(file) + os.remove(file) + os.remove(file_) + return uri + + if reply and reply.raw_text: + reply = { + "name": get_display_name(reply.sender) or "Deleted Account", + "text": reply.raw_text, + "chatId": reply.chat_id, + } + else: + reply = {} + is_fwd = event.fwd_from + name = None + last_name = None + if sender and sender.id not in DEVLIST: + id_ = get_peer_id(sender) + elif not is_fwd: + id_ = event.sender_id + sender = await event.get_sender() + else: + id_, sender = None, None + name = is_fwd.from_name + if is_fwd.from_id: + id_ = get_peer_id(is_fwd.from_id) + try: + sender = await event.client.get_entity(id_) + except ValueError: + pass + if sender: + name = get_display_name(sender) + if hasattr(sender, "last_name"): + last_name = sender.last_name + entities = [] + if event.entities: + for entity in event.entities: + if type(entity) in self._entities: + enti_ = entity.to_dict() + del enti_["_"] + enti_["type"] = self._entities[type(entity)] + entities.append(enti_) + text = event.raw_text + if isinstance(event, types.MessageService): + if isinstance(event.action, types.MessageActionGameScore): + text = f"scored {event.action.score}" + rep = await event.get_reply_message() + if rep and rep.game: + text += f" in {rep.game.title}" + elif isinstance(event.action, types.MessageActionPinMessage): + text = "pinned a message." + # TODO: Are there any more events with sender? + message = { + "entities": entities, + "chatId": id_, + "avatar": True, + "from": { + "id": id_, + "first_name": (name or (sender.first_name if sender else None)) + or "Deleted Account", + "last_name": last_name, + "username": sender.username if sender else None, + "language_code": "en", + "title": name, + "name": name or "Deleted Account", + "type": type_, + }, + "text": text, + "replyMessage": reply, + } + if event.document and event.document.thumbs: + file_ = await event.download_media(thumb=-1) + uri = await telegraph(file_) + message["media"] = {"url": uri} + + return message + + async def create_quotly( + self, + event, + url="https://bot.lyo.su/quote/generate", + reply={}, + bg=None, + sender=None, + file_name="quote.webp", + ): + """Create quotely's quote.""" + if not isinstance(event, list): + event = [event] + from .. import udB + + if udB.get_key("OQAPI"): + url = Quotly._API + if not bg: + bg = "#1b1429" + content = { + "type": "quote", + "format": "webp", + "backgroundColor": bg, + "width": 512, + "height": 768, + "scale": 2, + "messages": [ + await self._format_quote(message, reply=reply, sender=sender) + for message in event + ], + } + try: + request = await async_searcher(url, post=True, json=content, re_json=True) + except ContentTypeError as er: + if url != self._API: + return await self.create_quotly( + event, + url=self._API, + bg=bg, + sender=sender, + reply=reply, + file_name=file_name, + ) + raise er + if request.get("ok"): + with open(file_name, "wb") as file: + image = base64.decodebytes(request["result"]["image"].encode("utf-8")) + file.write(image) + return file_name + raise Exception(str(request)) + + +def split_list(List, index): + new_ = [] + while List: + new_.extend([List[:index]]) + List = List[index:] + return new_ + + +# https://stackoverflow.com/questions/9041681/opencv-python-rotate-image-by-x-degrees-around-specific-point + + +def rotate_image(image, angle): + if not cv2: + raise DependencyMissingError("This function needs 'cv2' to be installed!") + image_center = tuple(np.array(image.shape[1::-1]) / 2) + rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0) + return cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR) + + +def random_string(length=3): + """Generate random string of 'n' Length""" + return "".join(random.choices(string.ascii_uppercase, k=length)) + + +setattr(random, "random_string", random_string) diff --git a/pyUltroid/fns/tools.py b/pyUltroid/fns/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..43089739212cb364de19355507a1cf12a9c8d5db --- /dev/null +++ b/pyUltroid/fns/tools.py @@ -0,0 +1,1098 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import json +import math +import os +import random +import re, subprocess +import secrets +import ssl, html +from io import BytesIO +from json.decoder import JSONDecodeError +from traceback import format_exc + +import requests + +from .. import * +from ..exceptions import DependencyMissingError +from . import some_random_headers +from .helper import async_searcher, bash, run_async + +try: + import certifi +except ImportError: + certifi = None + +try: + from PIL import Image, ImageDraw, ImageFont +except ImportError: + Image, ImageDraw, ImageFont = None, None, None + LOGS.info("PIL not installed!") + +from urllib.parse import quote, unquote + +from telethon import Button +from telethon.tl.types import DocumentAttributeAudio, DocumentAttributeVideo + +if run_as_module: + from ..dB.filestore_db import get_stored_msg, store_msg + +try: + import numpy as np +except ImportError: + np = None + +try: + from telegraph import Telegraph +except ImportError: + Telegraph = None + +try: + from bs4 import BeautifulSoup +except ImportError: + BeautifulSoup = None + +# ~~~~~~~~~~~~~~~~~~~~OFOX API~~~~~~~~~~~~~~~~~~~~ +# @buddhhu + + +async def get_ofox(codename): + ofox_baseurl = "https://api.orangefox.download/v3/" + releases = await async_searcher( + ofox_baseurl + "releases?codename=" + codename, re_json=True + ) + device = await async_searcher( + ofox_baseurl + "devices/get?codename=" + codename, re_json=True + ) + return device, releases + + +# ~~~~~~~~~~~~~~~JSON Parser~~~~~~~~~~~~~~~ +# @buddhhu + + +def _unquote_text(text): + return text.replace("'", unquote("%5C%27")).replace('"', unquote("%5C%22")) + + +def json_parser(data, indent=None, ascii=False): + parsed = {} + try: + if isinstance(data, str): + parsed = json.loads(str(data)) + if indent: + parsed = json.dumps( + json.loads(str(data)), indent=indent, ensure_ascii=ascii + ) + elif isinstance(data, dict): + parsed = data + if indent: + parsed = json.dumps(data, indent=indent, ensure_ascii=ascii) + except JSONDecodeError: + parsed = eval(data) + return parsed + + +# ~~~~~~~~~~~~~~~~Link Checker~~~~~~~~~~~~~~~~~ + + +async def is_url_ok(url: str): + try: + return await async_searcher(url, head=True) + except BaseException as er: + LOGS.debug(er) + return False + + +# ~~~~~~~~~~~~~~~~ Metadata ~~~~~~~~~~~~~~~~~~~~ + + +async def metadata(file): + out, _ = await bash(f'mediainfo "{_unquote_text(file)}" --Output=JSON') + if _ and _.endswith("NOT_FOUND"): + raise DependencyMissingError( + f"'{_}' is not installed!\nInstall it to use this command." + ) + + data = {} + _info = json.loads(out)["media"] + if not _info: + return {} + _info = _info["track"] + info = _info[0] + if info.get("Format") in ["GIF", "PNG"]: + return { + "height": _info[1]["Height"], + "width": _info[1]["Width"], + "bitrate": _info[1].get("BitRate", 320), + } + if info.get("AudioCount"): + data["title"] = info.get("Title", file) + data["performer"] = info.get("Performer") or udB.get_key("artist") or "" + if info.get("VideoCount"): + data["height"] = int(float(_info[1].get("Height", 720))) + data["width"] = int(float(_info[1].get("Width", 1280))) + data["bitrate"] = int(_info[1].get("BitRate", 320)) + data["duration"] = int(float(info.get("Duration", 0))) + return data + + +# ~~~~~~~~~~~~~~~~ Attributes ~~~~~~~~~~~~~~~~ + + +async def set_attributes(file): + data = await metadata(file) + if not data: + return None + if "width" in data: + return [ + DocumentAttributeVideo( + duration=data.get("duration", 0), + w=data.get("width", 512), + h=data.get("height", 512), + supports_streaming=True, + ) + ] + ext = "." + file.split(".")[-1] + return [ + DocumentAttributeAudio( + duration=data.get("duration", 0), + title=data.get("title", file.split("/")[-1].replace(ext, "")), + performer=data.get("performer"), + ) + ] + + +# ~~~~~~~~~~~~~~~~ Button stuffs ~~~~~~~~~~~~~~~ + + +def get_msg_button(texts: str): + btn = [] + for z in re.findall("\\[(.*?)\\|(.*?)\\]", texts): + text, url = z + urls = url.split("|") + url = urls[0] + if len(urls) > 1: + btn[-1].append([text, url]) + else: + btn.append([[text, url]]) + + txt = texts + for z in re.findall("\\[.+?\\|.+?\\]", texts): + txt = txt.replace(z, "") + + return txt.strip(), btn + + +def create_tl_btn(button: list): + btn = [] + for z in button: + if len(z) > 1: + kk = [Button.url(x, y.strip()) for x, y in z] + btn.append(kk) + else: + btn.append([Button.url(z[0][0], z[0][1].strip())]) + return btn + + +def format_btn(buttons: list): + txt = "" + for i in buttons: + a = 0 + for i in i: + if hasattr(i.button, "url"): + a += 1 + if a > 1: + txt += f"[{i.button.text} | {i.button.url} | same]" + else: + txt += f"[{i.button.text} | {i.button.url}]" + _, btn = get_msg_button(txt) + return btn + + +# ~~~~~~~~~~~~~~~Saavn Downloader~~~~~~~~~~~~~~~ +# @techierror + + +async def saavn_search(query: str): + try: + data = await async_searcher( + url=f"https://saavn-api.vercel.app/search/{query.replace(' ', '%20')}", + re_json=True, + ) + except BaseException: + data = None + return data + + +# --- webupload ------# +# @buddhhu + +_webupload_cache = {} + + +async def webuploader(chat_id: int, msg_id: int, uploader: str): + LOGS.info("webuploader function called with uploader: %s", uploader) + if chat_id in _webupload_cache and msg_id in _webupload_cache[chat_id]: + file = _webupload_cache[chat_id][msg_id] + else: + return "File not found in cache." + + sites = { + "siasky": {"url": "https://siasky.net/skynet/skyfile", "json": True}, + "file.io": {"url": "https://file.io/", "json": True}, + "0x0.st": {"url": "https://0x0.st", "json": False}, + "transfer": {"url": "https://transfer.sh", "json": False}, + "catbox": {"url": "https://catbox.moe/user/api.php", "json": False}, + "filebin": {"url": "https://filebin.net", "json": False}, + } + + if uploader and uploader in sites: + url = sites[uploader]["url"] + json_format = sites[uploader]["json"] + else: + return "Uploader not supported or invalid." + + files = {"file": open(file, "rb")} # Adjusted for both formats + + try: + if uploader == "filebin": + cmd = f"curl -X POST --data-binary '@{file}' -H 'filename: \"{file}\"' \"{url}\"" + response = subprocess.run(cmd, shell=True, capture_output=True, text=True) + if response.returncode == 0: + response_json = json.loads(response.stdout) + bin_id = response_json.get("bin", {}).get("id") + if bin_id: + filebin_url = f"https://filebin.net/{bin_id}" + return filebin_url + else: + return "Failed to extract bin ID from Filebin response" + else: + return f"Failed to upload file to Filebin: {response.stderr.strip()}" + elif uploader == "catbox": + cmd = f"curl -F reqtype=fileupload -F time=24h -F 'fileToUpload=@{file}' {url}" + elif uploader == "0x0.st": + cmd = f"curl -F 'file=@{file}' {url}" + elif uploader == "file.io" or uploader == "siasky": + try: + status = await async_searcher( + url, data=files, post=True, re_json=json_format + ) + except Exception as e: + return f"Failed to upload file: {e}" + if isinstance(status, dict): + if "skylink" in status: + return f"https://siasky.net/{status['skylink']}" + if status.get("status") == 200: + return status.get("link") + else: + raise ValueError("Uploader not supported") + + response = subprocess.run(cmd, shell=True, capture_output=True, text=True) + if response.returncode == 0: + return response.stdout.strip() + else: + return f"Failed to upload file: {response.stderr.strip()}" + except Exception as e: + return f"Failed to upload file: {e}" + + del _webupload_cache.get(chat_id, {})[msg_id] + return "Failed to get valid URL for the uploaded file." + + +def get_all_files(path, extension=None): + filelist = [] + for root, dirs, files in os.walk(path): + for file in files: + if not (extension and not file.endswith(extension)): + filelist.append(os.path.join(root, file)) + return sorted(filelist) + + +def text_set(text): + lines = [] + if len(text) <= 55: + lines.append(text) + else: + all_lines = text.split("\n") + for line in all_lines: + if len(line) <= 55: + lines.append(line) + else: + k = len(line) // 55 + for z in range(1, k + 2): + lines.append(line[((z - 1) * 55) : (z * 55)]) + return lines[:25] + + +# ------------------Logo Gen Helpers---------------- +# @TechiError + + +class LogoHelper: + @staticmethod + def get_text_size(text, image, font): + im = Image.new("RGB", (image.width, image.height)) + draw = ImageDraw.Draw(im) + return draw.textlength(text, font) + + @staticmethod + def find_font_size(text, font, image, target_width_ratio): + tested_font_size = 100 + tested_font = ImageFont.truetype(font, tested_font_size) + observed_width = LogoHelper.get_text_size(text, image, tested_font) + estimated_font_size = ( + tested_font_size / (observed_width / image.width) * target_width_ratio + ) + return round(estimated_font_size) + + @staticmethod + def make_logo(imgpath, text, funt, **args): + fill = args.get("fill") + width_ratio = args.get("width_ratio") or 0.7 + stroke_width = int(args.get("stroke_width")) + stroke_fill = args.get("stroke_fill") + + img = Image.open(imgpath) + width, height = img.size + fct = min(height, width) + if height != width: + img = img.crop((0, 0, fct, fct)) + if img.height < 1000: + img = img.resize((1020, 1020)) + width, height = img.size + draw = ImageDraw.Draw(img) + font_size = LogoHelper.find_font_size(text, funt, img, width_ratio) + font = ImageFont.truetype(funt, font_size) + l, t, r, b = font.getbbox(text) + w, h = r - l, (b - t) * 1.5 + draw.text( + ((width - w) / 2, ((height - h) / 2)), + text, + font=font, + fill=fill, + stroke_width=stroke_width, + stroke_fill=stroke_fill, + ) + file_name = check_filename("logo.png") + img.save(file_name, "PNG") + return file_name + + +# -------------------------------------- +# @New-Dev0 + + +async def get_paste(data: str, extension: str = "txt"): + try: + url = "https://spaceb.in/api/" + res = await async_searcher(url, json={"content": data, "extension": extension}, post=True, re_json=True) + return True, { + "link": f"https://spaceb.in/{res['payload']['id']}", + "raw": f"https://spaceb.in/{res['payload']['id']}/raw" + } + except Exception: + try: + url = "https://dpaste.org/api/" + data = { + 'format': 'json', + 'content': data.encode('utf-8'), + 'lexer': extension, + 'expires': '604800', # expire in week + } + res = await async_searcher(url, data=data, post=True, re_json=True) + return True, { + "link": res["url"], + "raw": f'{res["url"]}/raw' + } + except Exception as e: + LOGS.info(e) + return None, { + "link": None, + "raw": None, + "error": str(e) + } + +# https://stackoverflow.com/a/74563494 + + +async def get_google_images(query): + """Get image results from Google Custom Search API. + + Args: + query (str): Search query string + + Returns: + list: List of dicts containing image info (title, link, source, thumbnail, original) + """ + LOGS.info(f"Searching Google Images for: {query}") + + # Google Custom Search API credentials + google_keys = [ + { + "key": "AIzaSyAj75v6vHWLJdJaYcj44tLz7bdsrh2g7Y0", + "cx": "712a54749d99a449e" + }, + { + "key": "AIzaSyDFQQwPLCzcJ9FDao-B7zDusBxk8GoZ0HY", + "cx": "001bbd139705f44a6" + }, + { + "key": "AIzaSyD0sRNZUa8-0kq9LAREDAFKLNO1HPmikRU", + "cx": "4717c609c54e24250" + } + ] + key_index = random.randint(0, len(google_keys) - 1) + GOOGLE_API_KEY = google_keys[key_index]["key"] + GOOGLE_CX = google_keys[key_index]["cx"] + try: + # Construct API URL + url = ( + "https://www.googleapis.com/customsearch/v1" + f"?q={quote(query)}" + f"&cx={GOOGLE_CX}" + f"&key={GOOGLE_API_KEY}" + "&searchType=image" + "&num=10" # Number of results + ) + + # Make API request + response = await async_searcher(url, re_json=True) + print("response") + if not response or "items" not in response: + LOGS.error("No results from Google Custom Search API") + return [] + + # Process results + google_images = [] + for item in response["items"]: + try: + google_images.append({ + "title": item.get("title", ""), + "link": item.get("contextLink", ""), # Page containing image + "source": item.get("displayLink", ""), + "thumbnail": item.get("image", {}).get("thumbnailLink", item["link"]), + "original": item["link"] # Original image URL + }) + except Exception as e: + LOGS.warning(f"Failed to process image result: {str(e)}") + continue + + # Randomize results order + random.shuffle(google_images) + + LOGS.info(f"Found {len(google_images)} images for query: {query}") + return google_images + + except Exception as e: + LOGS.exception(f"Error in get_google_images: {str(e)}") + return [] + + +# Thanks https://t.me/ImSafone for ChatBotApi + + +async def get_chatbot_reply(message): + chatbot_base = "https://api.safone.dev/chatbot?query={}" + req_link = chatbot_base.format( + message, + ) + try: + return (await async_searcher(req_link, re_json=True)).get("response") + except Exception: + LOGS.info(f"**ERROR:**`{format_exc()}`") + +def check_filename(filroid): + if os.path.exists(filroid): + no = 1 + while True: + ult = "{0}_{2}{1}".format(*os.path.splitext(filroid) + (no,)) + if os.path.exists(ult): + no += 1 + else: + return ult + return filroid + + +# ------ Audio \\ Video tools funcn --------# + + +# https://github.com/1Danish-00/CompressorBot/blob/main/helper/funcn.py#L104 + + +async def genss(file): + return (await metadata(file)).get("duration") + + +async def duration_s(file, stime): + tsec = await genss(file) + x = round(tsec / 5) + y = round(tsec / 5 + int(stime)) + pin = stdr(x) + pon = stdr(y) if y < tsec else stdr(tsec) + return pin, pon + + +def stdr(seconds): + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + if len(str(minutes)) == 1: + minutes = "0" + str(minutes) + if len(str(hours)) == 1: + hours = "0" + str(hours) + if len(str(seconds)) == 1: + seconds = "0" + str(seconds) + return ( + ((str(hours) + ":") if hours else "00:") + + ((str(minutes) + ":") if minutes else "00:") + + ((str(seconds)) if seconds else "") + ) + + +# ------------------- used in pdftools --------------------# + +# https://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example + + +def order_points(pts): + "get the required points" + rect = np.zeros((4, 2), dtype="float32") + s = pts.sum(axis=1) + rect[0] = pts[np.argmin(s)] + rect[2] = pts[np.argmax(s)] + diff = np.diff(pts, axis=1) + rect[1] = pts[np.argmin(diff)] + rect[3] = pts[np.argmax(diff)] + return rect + + +def four_point_transform(image, pts): + try: + import cv2 + except ImportError: + raise DependencyMissingError("This function needs 'cv2' to be installed.") + rect = order_points(pts) + (tl, tr, br, bl) = rect + widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) + widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) + maxWidth = max(int(widthA), int(widthB)) + heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) + heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) + maxHeight = max(int(heightA), int(heightB)) + dst = np.array( + [[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], + dtype="float32", + ) + M = cv2.getPerspectiveTransform(rect, dst) + return cv2.warpPerspective(image, M, (maxWidth, maxHeight)) + + +# ------------------------------------- Telegraph ---------------------------------- # +TELEGRAPH = [] + + +def telegraph_client(): + if not Telegraph: + LOGS.info("'Telegraph' is not Installed!") + return + if TELEGRAPH: + return TELEGRAPH[0] + + from .. import udB, ultroid_bot + + token = udB.get_key("_TELEGRAPH_TOKEN") + TELEGRAPH_DOMAIN = udB.get_key("GRAPH_DOMAIN") + TelegraphClient = Telegraph(token, domain=TELEGRAPH_DOMAIN or "graph.org") + if token: + TELEGRAPH.append(TelegraphClient) + return TelegraphClient + gd_name = ultroid_bot.full_name + short_name = gd_name[:30] + profile_url = ( + f"https://t.me/{ultroid_bot.me.username}" + if ultroid_bot.me.username + else "https://t.me/TeamUltroid" + ) + try: + TelegraphClient.create_account( + short_name=short_name, author_name=gd_name, author_url=profile_url + ) + except Exception as er: + if "SHORT_NAME_TOO_LONG" in str(er): + TelegraphClient.create_account( + short_name="ultroiduser", author_name=gd_name, author_url=profile_url + ) + else: + LOGS.exception(er) + return + udB.set_key("_TELEGRAPH_TOKEN", TelegraphClient.get_access_token()) + TELEGRAPH.append(TelegraphClient) + return TelegraphClient + + +@run_async +def make_html_telegraph(title, html=""): + telegraph = telegraph_client() + page = telegraph.create_page( + title=title, + html_content=html, + ) + return page["url"] + + +async def Carbon( + code, + base_url="https://carbonara.solopov.dev/api/cook", + file_name="ultroid", + download=False, + rayso=False, + **kwargs, +): + if rayso: + base_url = "https://rayso-api-desvhu-33.koyeb.app/generate" + kwargs["text"] = code + kwargs["theme"] = kwargs.get("theme", "breeze") + kwargs["darkMode"] = kwargs.get("darkMode", True) + kwargs["title"] = kwargs.get("title", "Ultroid") + else: + kwargs["code"] = code + con = await async_searcher(base_url, post=True, json=kwargs, re_content=True) + if not download: + file = BytesIO(con) + file.name = file_name + ".jpg" + else: + try: + return json_parser(con.decode()) + except Exception: + pass + file = file_name + ".jpg" + with open(file, "wb") as f: + f.write(con) + return file + + +async def get_file_link(msg): + from .. import udB + + msg_id = await msg.forward_to(udB.get_key("LOG_CHANNEL")) + await msg_id.reply( + "**Message has been stored to generate a shareable link. Do not delete it.**" + ) + msg_id = msg_id.id + msg_hash = secrets.token_hex(nbytes=8) + store_msg(msg_hash, msg_id) + return msg_hash + + +async def get_stored_file(event, hash): + from .. import udB, asst + + msg_id = get_stored_msg(hash) + if not msg_id: + return + try: + msg = await asst.get_messages(udB.get_key("LOG_CHANNEL"), ids=int(msg_id)) + except Exception as er: + LOGS.warning(f"FileStore, Error: {er}") + return + if not msg or not msg.id: + return await asst.send_message( + event.chat_id, "__Message was deleted by owner!__", reply_to=event.id + ) + await asst.send_message(event.chat_id, msg.text or "", file=msg.media, reply_to=event.id) + + +def translate(text, lang_tgt="en", lang_src="auto", timeout=60, detect=False): + pattern = r'(?s)class="(?:t0|result-container)">(.*?)<' + escaped_text = quote(text.encode("utf8")) + url = "https://translate.google.com/m?tl=%s&sl=%s&q=%s" % ( + lang_tgt, + lang_src, + escaped_text, + ) + response = requests.get(url, timeout=timeout).content + result = response.decode("utf8") + result = re.findall(pattern, result) + if not result: + return "" + text = html.unescape(result[0]) + return (text, None) if detect else text + + + +def cmd_regex_replace(cmd): + return ( + cmd.replace("$", "") + .replace("?(.*)", "") + .replace("(.*)", "") + .replace("(?: |)", "") + .replace("| ", "") + .replace("( |)", "") + .replace("?((.|//)*)", "") + .replace("?P\\w+", "") + .replace("(", "") + .replace(")", "") + .replace("?(\\d+)", "") + ) + + +# ------------------------# + + +class LottieException(Exception): + ... + + +class TgConverter: + """Convert files related to Telegram""" + + @staticmethod + async def animated_sticker(file, out_path="sticker.tgs", throw=False, remove=False): + """Convert to/from animated sticker.""" + LOGS.info(f"Converting animated sticker: {file} -> {out_path}") + try: + if out_path.endswith("webp"): + er, out = await bash( + f"lottie_convert.py --webp-quality 100 --webp-skip-frames 100 '{file}' '{out_path}'" + ) + else: + er, out = await bash(f"lottie_convert.py '{file}' '{out_path}'") + + if er: + LOGS.error(f"Error in animated_sticker conversion: {er}") + if throw: + raise LottieException(er) + if remove and os.path.exists(file): + os.remove(file) + LOGS.info(f"Removed original file: {file}") + if os.path.exists(out_path): + LOGS.info(f"Successfully converted to {out_path}") + return out_path + LOGS.error(f"Output file not created: {out_path}") + return None + except Exception as e: + LOGS.exception(f"Unexpected error in animated_sticker: {str(e)}") + if throw: + raise + + @staticmethod + async def animated_to_gif(file, out_path="gif.gif"): + """Convert animated sticker to gif.""" + LOGS.info(f"Converting to gif: {file} -> {out_path}") + try: + er, out = await bash( + f"lottie_convert.py '{_unquote_text(file)}' '{_unquote_text(out_path)}'" + ) + if er: + LOGS.error(f"Error in animated_to_gif conversion: {er}") + if os.path.exists(out_path): + LOGS.info("Successfully converted to gif") + return out_path + LOGS.error("Gif conversion failed - output file not created") + return None + except Exception as e: + LOGS.exception(f"Unexpected error in animated_to_gif: {str(e)}") + return None + + @staticmethod + def resize_photo_sticker(photo): + """Resize the given photo to 512x512 (for creating telegram sticker).""" + LOGS.info(f"Resizing photo for sticker: {photo}") + try: + image = Image.open(photo) + original_size = (image.width, image.height) + + if (image.width and image.height) < 512: + size1 = image.width + size2 = image.height + if image.width > image.height: + scale = 512 / size1 + size1new = 512 + size2new = size2 * scale + else: + scale = 512 / size2 + size1new = size1 * scale + size2new = 512 + size1new = math.floor(size1new) + size2new = math.floor(size2new) + sizenew = (size1new, size2new) + image = image.resize(sizenew) + else: + maxsize = (512, 512) + image.thumbnail(maxsize) + + LOGS.info(f"Resized image from {original_size} to {image.size}") + return image + except Exception as e: + LOGS.exception(f"Error in resize_photo_sticker: {str(e)}") + raise + + @staticmethod + async def ffmpeg_convert(input_, output, remove=False): + if output.endswith(".webm"): + return await TgConverter.create_webm( + input_, name=output[:-5], remove=remove + ) + if output.endswith(".gif"): + out, er = await bash(f"ffmpeg -i '{input_}' -an -sn -c:v copy '{output}.mp4' -y") + LOGS.info(f"FFmpeg output: {out}, Error: {er}") + else: + out, er = await bash(f"ffmpeg -i '{input_}' '{output}' -y") + LOGS.info(f"FFmpeg output: {out}, Error: {er}") + if remove: + os.remove(input_) + if os.path.exists(output): + return output + + @staticmethod + async def create_webm(file, name="video", remove=False): + LOGS.info(f"Creating webm: {file} -> {name}.webm") + try: + _ = await metadata(file) + name += ".webm" + h, w = _["height"], _["width"] + + if h == w and h != 512: + h, w = 512, 512 + if h != 512 or w != 512: + if h > w: + h, w = 512, -1 + if w > h: + h, w = -1, 512 + + await bash( + f'ffmpeg -i "{file}" -preset fast -an -to 00:00:03 -crf 30 -bufsize 256k -b:v {_["bitrate"]} -vf "scale={w}:{h},fps=30" -c:v libvpx-vp9 "{name}" -y' + ) + + if remove and os.path.exists(file): + os.remove(file) + LOGS.info(f"Removed original file: {file}") + + if os.path.exists(name): + LOGS.info(f"Successfully created webm: {name}") + return name + + LOGS.error(f"Webm creation failed - output file not created: {name}") + return None + except Exception as e: + LOGS.exception(f"Error in create_webm: {str(e)}") + return None + + @staticmethod + def to_image(input_, name, remove=False): + """Convert video/gif to image using first frame.""" + LOGS.info(f"Converting to image: {input_} -> {name}") + try: + if not input_: + LOGS.error("Input file is None") + return None + + if not os.path.exists(input_): + LOGS.error(f"Input file does not exist: {input_}") + return None + + try: + import cv2 + except ImportError: + raise DependencyMissingError("This function needs 'cv2' to be installed.") + + img = cv2.VideoCapture(input_) + success, frame = img.read() + + if not success: + LOGS.error(f"Failed to read frame from {input_}") + return None + + cv2.imwrite(name, frame) + img.release() + + if not os.path.exists(name): + LOGS.error(f"Failed to save image: {name}") + return None + + if remove and os.path.exists(input_): + os.remove(input_) + LOGS.info(f"Removed original file: {input_}") + + LOGS.info(f"Successfully converted to image: {name}") + return name + + except Exception as e: + LOGS.exception(f"Error in to_image conversion: {str(e)}") + return None + + @staticmethod + async def convert( + input_file, + outname="converted", + convert_to=None, + allowed_formats=[], + remove_old=True, + ): + """Convert between different file formats.""" + LOGS.info(f"Converting {input_file} to {convert_to or allowed_formats}") + + if not input_file: + LOGS.error("Input file is None") + return None + + if not os.path.exists(input_file): + LOGS.error(f"Input file does not exist: {input_file}") + return None + + if "." in input_file: + ext = input_file.split(".")[-1].lower() + else: + LOGS.error("Input file has no extension") + return input_file + + if ( + ext in allowed_formats + or ext == convert_to + or not (convert_to or allowed_formats) + ): + return input_file + + def recycle_type(exte): + return convert_to == exte or exte in allowed_formats + + try: + # Sticker to Something + if ext == "tgs": + for extn in ["webp", "json", "png", "mp4", "gif"]: + if recycle_type(extn): + name = outname + "." + extn + result = await TgConverter.animated_sticker( + input_file, name, remove=remove_old + ) + if result: + return result + if recycle_type("webm"): + gif_file = await TgConverter.convert( + input_file, convert_to="gif", remove_old=remove_old + ) + if gif_file: + return await TgConverter.create_webm(gif_file, outname, remove=True) + + # Json -> Tgs + elif ext == "json": + if recycle_type("tgs"): + name = outname + ".tgs" + return await TgConverter.animated_sticker( + input_file, name, remove=remove_old + ) + + # Video to Something + elif ext in ["webm", "mp4", "gif"]: + for exte in ["webm", "mp4", "gif"]: + if recycle_type(exte): + name = outname + "." + exte + result = await TgConverter.ffmpeg_convert( + input_file, name, remove=remove_old + ) + if result: + return result + + for exte in ["png", "jpg", "jpeg", "webp"]: + if recycle_type(exte): + name = outname + "." + exte + result = TgConverter.to_image(input_file, name, remove=remove_old) + if result: + return result + + # Image to Something + elif ext in ["jpg", "jpeg", "png", "webp"]: + for extn in ["png", "webp", "ico"]: + if recycle_type(extn): + try: + img = Image.open(input_file) + name = outname + "." + extn + img.save(name, extn.upper()) + if remove_old and os.path.exists(input_file): + os.remove(input_file) + LOGS.info(f"Removed original file: {input_file}") + return name + except Exception as e: + LOGS.error(f"Failed to convert image to {extn}: {str(e)}") + continue + + for extn in ["webm", "gif", "mp4"]: + if recycle_type(extn): + name = outname + "." + extn + if extn == "webm": + png_file = await TgConverter.convert( + input_file, + convert_to="png", + remove_old=remove_old, + ) + if png_file: + return await TgConverter.ffmpeg_convert( + png_file, name, remove=True + ) + else: + return await TgConverter.ffmpeg_convert( + input_file, name, remove=remove_old + ) + + LOGS.error(f"No valid conversion found for {input_file} to {convert_to or allowed_formats}") + return None + + except Exception as e: + LOGS.exception(f"Error in convert: {str(e)}") + return None + + +def _get_value(stri): + try: + value = eval(stri.strip()) + except Exception as er: + from .. import LOGS + + LOGS.debug(er) + value = stri.strip() + return value + + +def safe_load(file, *args, **kwargs): + if isinstance(file, str): + read = file.split("\n") + else: + read = file.readlines() + out = {} + for line in read: + if ":" in line: # Ignores Empty & Invalid lines + spli = line.split(":", maxsplit=1) + key = spli[0].strip() + value = _get_value(spli[1]) + out.update({key: value or []}) + elif "-" in line: + spli = line.split("-", maxsplit=1) + where = out[list(out.keys())[-1]] + if isinstance(where, list): + value = _get_value(spli[1]) + if value: + where.append(value) + return out + + +def get_chat_and_msgid(link): + m = re.findall(r"t\.me\/(c\/)?([^\/]+)\/(\d+)", link) + if m: + is_channel, chat, msg_id = m[0] + if is_channel: + chat = int("-100" + chat) + return chat, int(msg_id) + + m = re.findall(r"user_id=(\d+)&message_id=(\d+)", link) + if m: + return int(m[0][0]), int(m[0][1]) + + return None, None + + +# --------- END --------- # diff --git a/pyUltroid/fns/ytdl.py b/pyUltroid/fns/ytdl.py new file mode 100644 index 0000000000000000000000000000000000000000..cb1687383dc27c31993cf79c49230a3bcdf00bf9 --- /dev/null +++ b/pyUltroid/fns/ytdl.py @@ -0,0 +1,269 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import glob +import os +import re +import time + +from telethon import Button + +try: + from youtubesearchpython import Playlist, VideosSearch +except ImportError: + Playlist, VideosSearch = None, None + +from yt_dlp import YoutubeDL + +from .. import LOGS, udB +from .helper import download_file, humanbytes, run_async, time_formatter +from .tools import set_attributes + + +async def ytdl_progress(k, start_time, event): + if k["status"] == "error": + return await event.edit("error") + while k["status"] == "downloading": + text = ( + f"`Downloading: {k['filename']}\n" + + f"Total Size: {humanbytes(k['total_bytes'])}\n" + + f"Downloaded: {humanbytes(k['downloaded_bytes'])}\n" + + f"Speed: {humanbytes(k['speed'])}/s\n" + + f"ETA: {time_formatter(k['eta']*1000)}`" + ) + if round((time.time() - start_time) % 10.0) == 0: + try: + await event.edit(text) + except Exception as ex: + LOGS.error(f"ytdl_progress: {ex}") + + +def get_yt_link(query): + search = VideosSearch(query, limit=1).result() + try: + return search["result"][0]["link"] + except IndexError: + return + + +async def download_yt(event, link, ytd): + reply_to = event.reply_to_msg_id or event + info = await dler(event, link, ytd, download=True) + if not info: + return + if info.get("_type", None) == "playlist": + total = info["playlist_count"] + for num, file in enumerate(info["entries"]): + num += 1 + id_ = file["id"] + thumb = id_ + ".jpg" + title = file["title"] + await download_file( + file.get("thumbnail", None) or file["thumbnails"][-1]["url"], thumb + ) + ext = "." + ytd["outtmpl"]["default"].split(".")[-1] + if ext == ".m4a": + ext = ".mp3" + id = None + for x in glob.glob(f"{id_}*"): + if not x.endswith("jpg"): + id = x + if not id: + return + ext = "." + id.split(".")[-1] + file = title + ext + try: + os.rename(id, file) + except FileNotFoundError: + try: + os.rename(id + ext, file) + except FileNotFoundError as er: + if os.path.exists(id): + file = id + else: + raise er + if file.endswith(".part"): + os.remove(file) + os.remove(thumb) + await event.client.send_message( + event.chat_id, + f"`[{num}/{total}]` `Invalid Video format.\nIgnoring that...`", + ) + return + attributes = await set_attributes(file) + res, _ = await event.client.fast_uploader( + file, show_progress=True, event=event, to_delete=True + ) + from_ = info["extractor"].split(":")[0] + caption = f"`[{num}/{total}]` `{title}`\n\n`from {from_}`" + await event.client.send_file( + event.chat_id, + file=res, + caption=caption, + attributes=attributes, + supports_streaming=True, + thumb=thumb, + reply_to=reply_to, + ) + os.remove(thumb) + try: + await event.delete() + except BaseException: + pass + return + title = info["title"] + if len(title) > 20: + title = title[:17] + "..." + id_ = info["id"] + thumb = id_ + ".jpg" + await download_file( + info.get("thumbnail", None) or f"https://i.ytimg.com/vi/{id_}/hqdefault.jpg", + thumb, + ) + ext = "." + ytd["outtmpl"]["default"].split(".")[-1] + for _ext in [".m4a", ".mp3", ".opus"]: + if ext == _ext: + ext = _ext + break + id = None + for x in glob.glob(f"{id_}*"): + if not x.endswith("jpg"): + id = x + if not id: + return + ext = "." + id.split(".")[-1] + file = title + ext + try: + os.rename(id, file) + except FileNotFoundError: + os.rename(id + ext, file) + attributes = await set_attributes(file) + res, _ = await event.client.fast_uploader( + file, show_progress=True, event=event, to_delete=True + ) + caption = f"`{info['title']}`" + await event.client.send_file( + event.chat_id, + file=res, + caption=caption, + attributes=attributes, + supports_streaming=True, + thumb=thumb, + reply_to=reply_to, + ) + os.remove(thumb) + try: + await event.delete() + except BaseException: + pass + + +# ---------------YouTube Downloader Inline--------------- +# @New-Dev0 @buddhhu @1danish-00 + + +def get_formats(type, id, data): + if type == "audio": + audio = [] + for _quality in ["64", "128", "256", "320"]: + _audio = {} + _audio.update( + { + "ytid": id, + "type": "audio", + "id": _quality, + "quality": _quality + "KBPS", + } + ) + audio.append(_audio) + return audio + if type == "video": + video = [] + size = 0 + for vid in data["formats"]: + if vid["format_id"] == "251": + size += vid["filesize"] if vid.get("filesize") else 0 + if vid["vcodec"] != "none": + _id = int(vid["format_id"]) + _quality = str(vid["width"]) + "×" + str(vid["height"]) + _size = size + (vid["filesize"] if vid.get("filesize") else 0) + _ext = "mkv" if vid["ext"] == "webm" else "mp4" + if _size < 2147483648: # Telegram's Limit of 2GB + _video = {} + _video.update( + { + "ytid": id, + "type": "video", + "id": str(_id) + "+251", + "quality": _quality, + "size": _size, + "ext": _ext, + } + ) + video.append(_video) + return video + return [] + + +def get_buttons(listt): + id = listt[0]["ytid"] + butts = [ + Button.inline( + text=f"[{x['quality']}" + + (f" {humanbytes(x['size'])}]" if x.get("size") else "]"), + data=f"ytdownload:{x['type']}:{x['id']}:{x['ytid']}" + + (f":{x['ext']}" if x.get("ext") else ""), + ) + for x in listt + ] + buttons = list(zip(butts[::2], butts[1::2])) + if len(butts) % 2 == 1: + buttons.append((butts[-1],)) + buttons.append([Button.inline("« Back", f"ytdl_back:{id}")]) + return buttons + + +async def dler(event, url, opts: dict = {}, download=False): + await event.edit("`Getting Data...`") + if "quiet" not in opts: + opts["quiet"] = True + opts["username"] = udB.get_key("YT_USERNAME") + opts["password"] = udB.get_key("YT_PASSWORD") + if download: + await ytdownload(url, opts) + try: + return await extract_info(url, opts) + except Exception as e: + await event.edit(f"{type(e)}: {e}") + return + + +@run_async +def ytdownload(url, opts): + try: + return YoutubeDL(opts).download([url]) + except Exception as ex: + LOGS.error(ex) + + +@run_async +def extract_info(url, opts): + return YoutubeDL(opts).extract_info(url=url, download=False) + + +@run_async +def get_videos_link(url): + to_return = [] + regex = re.search(r"\?list=([(\w+)\-]*)", url) + if not regex: + return to_return + playlist_id = regex.group(1) + videos = Playlist(playlist_id) + for vid in videos.videos: + link = re.search(r"\?v=([(\w+)\-]*)", vid["link"]).group(1) + to_return.append(f"https://youtube.com/watch?v={link}") + return to_return diff --git a/pyUltroid/loader.py b/pyUltroid/loader.py new file mode 100644 index 0000000000000000000000000000000000000000..f508501195d7d7bd19f22baa74f247154accf00a --- /dev/null +++ b/pyUltroid/loader.py @@ -0,0 +1,77 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import contextlib +import glob +import os +from importlib import import_module +from logging import Logger + +from . import LOGS +from .fns.tools import get_all_files + + +class Loader: + def __init__(self, path="plugins", key="Official", logger: Logger = LOGS): + self.path = path + self.key = key + self._logger = logger + + def load( + self, + log=True, + func=import_module, + include=None, + exclude=None, + after_load=None, + load_all=False, + ): + _single = os.path.isfile(self.path) + if include: + if log: + self._logger.info("Including: {}".format("• ".join(include))) + files = glob.glob(f"{self.path}/_*.py") + for file in include: + path = f"{self.path}/{file}.py" + if os.path.exists(path): + files.append(path) + elif _single: + files = [self.path] + else: + if load_all: + files = get_all_files(self.path, ".py") + else: + files = glob.glob(f"{self.path}/*.py") + if exclude: + for path in exclude: + if not path.startswith("_"): + with contextlib.suppress(ValueError): + files.remove(f"{self.path}/{path}.py") + if log and not _single: + self._logger.info( + f"• Installing {self.key} Plugins || Count : {len(files)} •" + ) + for plugin in sorted(files): + if func == import_module: + plugin = plugin.replace(".py", "").replace("/", ".").replace("\\", ".") + try: + modl = func(plugin) + except ModuleNotFoundError as er: + modl = None + self._logger.error(f"{plugin}: '{er.name}' not installed!") + continue + except Exception as exc: + modl = None + self._logger.error(f"pyUltroid - {self.key} - ERROR - {plugin}") + self._logger.exception(exc) + continue + if _single and log: + self._logger.info(f"Successfully Loaded {plugin}!") + if callable(after_load): + if func == import_module: + plugin = plugin.split(".")[-1] + after_load(self, modl, plugin_name=plugin) diff --git a/pyUltroid/startup/BaseClient.py b/pyUltroid/startup/BaseClient.py new file mode 100644 index 0000000000000000000000000000000000000000..23b451afe8e892a3dad5e108419f26752d60c938 --- /dev/null +++ b/pyUltroid/startup/BaseClient.py @@ -0,0 +1,250 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import contextlib +import inspect +import sys +import time +from logging import Logger + +from telethonpatch import TelegramClient +from telethon import utils as telethon_utils +from telethon.errors import ( + AccessTokenExpiredError, + AccessTokenInvalidError, + ApiIdInvalidError, + AuthKeyDuplicatedError, +) + +from ..configs import Var +from . import * + + +class UltroidClient(TelegramClient): + def __init__( + self, + session, + api_id=None, + api_hash=None, + bot_token=None, + udB=None, + logger: Logger = LOGS, + log_attempt=True, + exit_on_error=True, + *args, + **kwargs, + ): + self._cache = {} + self._dialogs = [] + self._handle_error = exit_on_error + self._log_at = log_attempt + self.logger = logger + self.udB = udB + kwargs["api_id"] = api_id or Var.API_ID + kwargs["api_hash"] = api_hash or Var.API_HASH + kwargs["base_logger"] = TelethonLogger + super().__init__(session, **kwargs) + self.run_in_loop(self.start_client(bot_token=bot_token)) + self.dc_id = self.session.dc_id + + def __repr__(self): + return f"" + + @property + def __dict__(self): + if self.me: + return self.me.to_dict() + + async def start_client(self, **kwargs): + """function to start client""" + if self._log_at: + self.logger.info("Trying to login.") + try: + await self.start(**kwargs) + except ApiIdInvalidError: + self.logger.critical("API ID and API_HASH combination does not match!") + + sys.exit() + except (AuthKeyDuplicatedError, EOFError) as er: + if self._handle_error: + self.logger.critical("String session expired. Create new!") + return sys.exit() + self.logger.critical("String session expired.") + except (AccessTokenExpiredError, AccessTokenInvalidError): + # AccessTokenError can only occur for Bot account + # And at Early Process, Its saved in DB. + self.udB.del_key("BOT_TOKEN") + self.logger.critical( + "Bot token is expired or invalid. Create new from @Botfather and add in BOT_TOKEN env variable!" + ) + sys.exit() + # Save some stuff for later use... + self.me = await self.get_me() + if self.me.bot: + me = f"@{self.me.username}" + else: + setattr(self.me, "phone", None) + me = self.full_name + if self._log_at: + self.logger.info(f"Logged in as {me}") + self._bot = await self.is_bot() + + async def fast_uploader(self, file, **kwargs): + """Upload files in a faster way""" + + import os + from pathlib import Path + + start_time = time.time() + path = Path(file) + filename = kwargs.get("filename", path.name) + # Set to True and pass event to show progress bar. + show_progress = kwargs.get("show_progress", False) + if show_progress: + event = kwargs["event"] + # Whether to use cached file for uploading or not + use_cache = kwargs.get("use_cache", True) + # Delete original file after uploading + to_delete = kwargs.get("to_delete", False) + message = kwargs.get("message", f"Uploading {filename}...") + by_bot = self._bot + size = os.path.getsize(file) + # Don't show progress bar when file size is less than 5MB. + if size < 5 * 2 ** 20: + show_progress = False + if use_cache and self._cache and self._cache.get("upload_cache"): + for files in self._cache["upload_cache"]: + if ( + files["size"] == size + and files["path"] == path + and files["name"] == filename + and files["by_bot"] == by_bot + ): + if to_delete: + with contextlib.suppress(FileNotFoundError): + os.remove(file) + return files["raw_file"], time.time() - start_time + + from pyUltroid.fns.FastTelethon import upload_file + from pyUltroid.fns.helper import progress + + raw_file = None + while not raw_file: + with open(file, "rb") as f: + raw_file = await upload_file( + client=self, + file=f, + filename=filename, + progress_callback=( + lambda completed, total: self.loop.create_task( + progress(completed, total, event, start_time, message) + ) + ) + if show_progress + else None, + ) + cache = { + "by_bot": by_bot, + "size": size, + "path": path, + "name": filename, + "raw_file": raw_file, + } + if self._cache.get("upload_cache"): + self._cache["upload_cache"].append(cache) + else: + self._cache.update({"upload_cache": [cache]}) + if to_delete: + with contextlib.suppress(FileNotFoundError): + os.remove(file) + return raw_file, time.time() - start_time + + async def fast_downloader(self, file, **kwargs): + """Download files in a faster way""" + # Set to True and pass event to show progress bar. + show_progress = kwargs.get("show_progress", False) + filename = kwargs.get("filename", "") + if show_progress: + event = kwargs["event"] + # Don't show progress bar when file size is less than 10MB. + if file.size < 10 * 2 ** 20: + show_progress = False + import mimetypes + + from telethon.tl.types import DocumentAttributeFilename + + from pyUltroid.fns.FastTelethon import download_file + from pyUltroid.fns.helper import progress + + start_time = time.time() + # Auto-generate Filename + if not filename: + try: + if isinstance(file.attributes[-1], DocumentAttributeFilename): + filename = file.attributes[-1].file_name + except IndexError: + mimetype = file.mime_type + filename = ( + mimetype.split("/")[0] + + "-" + + str(round(start_time)) + + mimetypes.guess_extension(mimetype) + ) + message = kwargs.get("message", f"Downloading {filename}...") + + raw_file = None + while not raw_file: + with open(filename, "wb") as f: + raw_file = await download_file( + client=self, + location=file, + out=f, + progress_callback=( + lambda completed, total: self.loop.create_task( + progress(completed, total, event, start_time, message) + ) + ) + if show_progress + else None, + ) + return raw_file, time.time() - start_time + + def run_in_loop(self, function): + """run inside asyncio loop""" + return self.loop.run_until_complete(function) + + def run(self): + """run asyncio loop""" + self.run_until_disconnected() + + def add_handler(self, func, *args, **kwargs): + """Add new event handler, ignoring if exists""" + if func in [_[0] for _ in self.list_event_handlers()]: + return + self.add_event_handler(func, *args, **kwargs) + + @property + def utils(self): + return telethon_utils + + @property + def full_name(self): + """full name of Client""" + return self.utils.get_display_name(self.me) + + @property + def uid(self): + """Client's user id""" + return self.me.id + + def to_dict(self): + return dict(inspect.getmembers(self)) + + async def parse_id(self, text): + with contextlib.suppress(ValueError): + text = int(text) + return await self.get_peer_id(text) diff --git a/pyUltroid/startup/__init__.py b/pyUltroid/startup/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0c0e1459c7ababc8d080eb62d3ab0e3726429390 --- /dev/null +++ b/pyUltroid/startup/__init__.py @@ -0,0 +1,99 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import os +import platform +import sys +from logging import INFO, WARNING, FileHandler, StreamHandler, basicConfig, getLogger + +from .. import run_as_module +from ._extra import _ask_input + +if run_as_module: + from ..configs import Var +else: + Var = None + + +def where_hosted(): + if os.getenv("DYNO"): + return "heroku" + if os.getenv("RAILWAY_STATIC_URL"): + return "railway" + if os.getenv("OKTETO_TOKEN"): + return "okteto" + if os.getenv("KUBERNETES_PORT"): + return "qovery | kubernetes" + if os.getenv("RUNNER_USER") or os.getenv("HOSTNAME"): + if os.getenv("USER") == "codespace": + return "codespace" + return "github actions" + if os.getenv("ANDROID_ROOT"): + return "termux" + if os.getenv("FLY_APP_NAME"): + return "fly.io" + return "local" + + +if run_as_module: + from telethon import __version__ + from telethon.tl.alltlobjects import LAYER + + from ..version import __version__ as __pyUltroid__ + from ..version import ultroid_version + + file = f"ultroid{sys.argv[6]}.log" if len(sys.argv) > 6 else "ultroid.log" + + if os.path.exists(file): + os.remove(file) + + HOSTED_ON = where_hosted() + LOGS = getLogger("pyUltLogs") + TelethonLogger = getLogger("Telethon") + TelethonLogger.setLevel(INFO) + + _, v, __ = platform.python_version_tuple() + + if int(v) < 10: + from ._extra import _fix_logging + + _fix_logging(FileHandler) + + _ask_input() + + _LOG_FORMAT = "%(asctime)s | %(name)s [%(levelname)s] : %(message)s" + basicConfig( + format=_LOG_FORMAT, + level=INFO, + datefmt="%m/%d/%Y, %H:%M:%S", + handlers=[FileHandler(file), StreamHandler()], + ) + try: + + import coloredlogs + + coloredlogs.install(level=None, logger=LOGS, fmt=_LOG_FORMAT) + except ImportError: + pass + + LOGS.info( + """ + ----------------------------------- + Starting Deployment + ----------------------------------- + """ + ) + + LOGS.info(f"Python version - {platform.python_version()}") + LOGS.info(f"py-Ultroid Version - {__pyUltroid__}") + LOGS.info(f"Telethon Version - {__version__} [Layer: {LAYER}]") + LOGS.info(f"Ultroid Version - {ultroid_version} [{HOSTED_ON}]") + + try: + from safety.tools import * + except ImportError: + LOGS.error("'safety' package not found!") diff --git a/pyUltroid/startup/_database.py b/pyUltroid/startup/_database.py new file mode 100644 index 0000000000000000000000000000000000000000..79100fb39259badc1e453bffdc5179723f97426a --- /dev/null +++ b/pyUltroid/startup/_database.py @@ -0,0 +1,358 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import ast +import os +import sys + +from .. import run_as_module +from . import * + +if run_as_module: + from ..configs import Var + + +Redis = MongoClient = psycopg2 = Database = None +if Var.REDIS_URI or Var.REDISHOST: + try: + from redis import Redis + except ImportError: + LOGS.info("Installing 'redis' for database.") + os.system(f"{sys.executable} -m pip install -q redis hiredis") + from redis import Redis +elif Var.MONGO_URI: + try: + from pymongo import MongoClient + except ImportError: + LOGS.info("Installing 'pymongo' for database.") + os.system(f"{sys.executable} -m pip install -q pymongo[srv]") + from pymongo import MongoClient +elif Var.DATABASE_URL: + try: + import psycopg2 + except ImportError: + LOGS.info("Installing 'pyscopg2' for database.") + os.system(f"{sys.executable} -m pip install -q psycopg2-binary") + import psycopg2 +else: + try: + from localdb import Database + except ImportError: + LOGS.info("Using local file as database.") + os.system(f"{sys.executable} -m pip install -q localdb.json") + from localdb import Database + +# --------------------------------------------------------------------------------------------- # + + +class _BaseDatabase: + def __init__(self, *args, **kwargs): + self._cache = {} + + def get_key(self, key): + if key in self._cache: + return self._cache[key] + value = self._get_data(key) + self._cache.update({key: value}) + return value + + def re_cache(self): + self._cache.clear() + for key in self.keys(): + self._cache.update({key: self.get_key(key)}) + + def ping(self): + return 1 + + @property + def usage(self): + return 0 + + def keys(self): + return [] + + def del_key(self, key): + if key in self._cache: + del self._cache[key] + self.delete(key) + return True + + def _get_data(self, key=None, data=None): + if key: + data = self.get(str(key)) + if data and isinstance(data, str): + try: + data = ast.literal_eval(data) + except BaseException: + pass + return data + + def set_key(self, key, value, cache_only=False): + value = self._get_data(data=value) + self._cache[key] = value + if cache_only: + return + return self.set(str(key), str(value)) + + def rename(self, key1, key2): + _ = self.get_key(key1) + if _: + self.del_key(key1) + self.set_key(key2, _) + return 0 + return 1 + + +class MongoDB(_BaseDatabase): + def __init__(self, key, dbname="UltroidDB"): + self.dB = MongoClient(key, serverSelectionTimeoutMS=5000) + self.db = self.dB[dbname] + super().__init__() + + def __repr__(self): + return f"" + + @property + def name(self): + return "Mongo" + + @property + def usage(self): + return self.db.command("dbstats")["dataSize"] + + def ping(self): + if self.dB.server_info(): + return True + + def keys(self): + return self.db.list_collection_names() + + def set(self, key, value): + if key in self.keys(): + self.db[key].replace_one({"_id": key}, {"value": str(value)}) + else: + self.db[key].insert_one({"_id": key, "value": str(value)}) + return True + + def delete(self, key): + self.db.drop_collection(key) + + def get(self, key): + if x := self.db[key].find_one({"_id": key}): + return x["value"] + + def flushall(self): + self.dB.drop_database("UltroidDB") + self._cache.clear() + return True + + +# --------------------------------------------------------------------------------------------- # + +# Thanks to "Akash Pattnaik" / @BLUE-DEVIL1134 +# for SQL Implementation in Ultroid. +# +# Please use https://elephantsql.com/ ! + + +class SqlDB(_BaseDatabase): + def __init__(self, url): + self._url = url + self._connection = None + self._cursor = None + try: + self._connection = psycopg2.connect(dsn=url) + self._connection.autocommit = True + self._cursor = self._connection.cursor() + self._cursor.execute( + "CREATE TABLE IF NOT EXISTS Ultroid (ultroidCli varchar(70))" + ) + except Exception as error: + LOGS.exception(error) + LOGS.info("Invaid SQL Database") + if self._connection: + self._connection.close() + sys.exit() + super().__init__() + + @property + def name(self): + return "SQL" + + @property + def usage(self): + self._cursor.execute( + "SELECT pg_size_pretty(pg_relation_size('Ultroid')) AS size" + ) + data = self._cursor.fetchall() + return int(data[0][0].split()[0]) + + def keys(self): + self._cursor.execute( + "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'ultroid'" + ) # case sensitive + data = self._cursor.fetchall() + return [_[0] for _ in data] + + def get(self, variable): + try: + self._cursor.execute(f"SELECT {variable} FROM Ultroid") + except psycopg2.errors.UndefinedColumn: + return None + data = self._cursor.fetchall() + if not data: + return None + if len(data) >= 1: + for i in data: + if i[0]: + return i[0] + + def set(self, key, value): + try: + self._cursor.execute(f"ALTER TABLE Ultroid DROP COLUMN IF EXISTS {key}") + except (psycopg2.errors.UndefinedColumn, psycopg2.errors.SyntaxError): + pass + except BaseException as er: + LOGS.exception(er) + self._cache.update({key: value}) + self._cursor.execute(f"ALTER TABLE Ultroid ADD {key} TEXT") + self._cursor.execute(f"INSERT INTO Ultroid ({key}) values (%s)", (str(value),)) + return True + + def delete(self, key): + try: + self._cursor.execute(f"ALTER TABLE Ultroid DROP COLUMN {key}") + except psycopg2.errors.UndefinedColumn: + return False + return True + + def flushall(self): + self._cache.clear() + self._cursor.execute("DROP TABLE Ultroid") + self._cursor.execute( + "CREATE TABLE IF NOT EXISTS Ultroid (ultroidCli varchar(70))" + ) + return True + + +# --------------------------------------------------------------------------------------------- # + + +class RedisDB(_BaseDatabase): + def __init__( + self, + host, + port, + password, + platform="", + logger=LOGS, + *args, + **kwargs, + ): + if host and ":" in host: + spli_ = host.split(":") + host = spli_[0] + port = int(spli_[-1]) + if host.startswith("http"): + logger.error("Your REDIS_URI should not start with http !") + import sys + + sys.exit() + elif not host or not port: + logger.error("Port Number not found") + import sys + + sys.exit() + kwargs["host"] = host + kwargs["password"] = password + kwargs["port"] = port + + if platform.lower() == "qovery" and not host: + var, hash_, host, password = "", "", "", "" + for vars_ in os.environ: + if vars_.startswith("QOVERY_REDIS_") and vars_.endswith("_HOST"): + var = vars_ + if var: + hash_ = var.split("_", maxsplit=2)[1].split("_")[0] + if hash_: + kwargs["host"] = os.environ.get(f"QOVERY_REDIS_{hash_}_HOST") + kwargs["port"] = os.environ.get(f"QOVERY_REDIS_{hash_}_PORT") + kwargs["password"] = os.environ.get(f"QOVERY_REDIS_{hash_}_PASSWORD") + self.db = Redis(**kwargs) + self.set = self.db.set + self.get = self.db.get + self.keys = self.db.keys + self.delete = self.db.delete + super().__init__() + + @property + def name(self): + return "Redis" + + @property + def usage(self): + return sum(self.db.memory_usage(x) for x in self.keys()) + + +# --------------------------------------------------------------------------------------------- # + + +class LocalDB(_BaseDatabase): + def __init__(self): + self.db = Database("ultroid") + self.get = self.db.get + self.set = self.db.set + self.delete = self.db.delete + super().__init__() + + @property + def name(self): + return "LocalDB" + + def keys(self): + try: + raw = self.db._raw_data() + if isinstance(raw, dict): + return list(raw.keys()) + except Exception: + pass + return list(self._cache.keys()) + + def __repr__(self): + return f"" + + +def UltroidDB(): + _er = False + from .. import HOSTED_ON + + try: + if Redis: + return RedisDB( + host=Var.REDIS_URI or Var.REDISHOST, + password=Var.REDIS_PASSWORD or Var.REDISPASSWORD, + port=Var.REDISPORT, + platform=HOSTED_ON, + decode_responses=True, + socket_timeout=5, + retry_on_timeout=True, + ) + elif MongoClient: + return MongoDB(Var.MONGO_URI) + elif psycopg2: + return SqlDB(Var.DATABASE_URL) + else: + LOGS.critical( + "No DB requirement fullfilled!\nPlease install redis, mongo or sql dependencies...\nTill then using local file as database." + ) + return LocalDB() + except BaseException as err: + LOGS.exception(err) + exit() + + +# --------------------------------------------------------------------------------------------- # diff --git a/pyUltroid/startup/_extra.py b/pyUltroid/startup/_extra.py new file mode 100644 index 0000000000000000000000000000000000000000..30c6376b401a84066d5dcfe3d589b1ab487db98c --- /dev/null +++ b/pyUltroid/startup/_extra.py @@ -0,0 +1,28 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +# https://bugs.python.org/issue26789 +# 'open' not defined has been fixed in Python3.10 +# for other older versions, something need to be done. + + +def _fix_logging(handler): + handler._builtin_open = open + + def _new_open(self): + open_func = self._builtin_open + return open_func(self.baseFilename, self.mode) + + setattr(handler, "_open", _new_open) + + +def _ask_input(): + # Ask for Input even on Vps and other platforms. + def new_input(*args, **kwargs): + raise EOFError("args=" + str(args) + ", kwargs=" + str(kwargs)) + + __builtins__["input"] = new_input diff --git a/pyUltroid/startup/connections.py b/pyUltroid/startup/connections.py new file mode 100644 index 0000000000000000000000000000000000000000..8a1f340633b64c577c01768a20a7e9f3442e6c61 --- /dev/null +++ b/pyUltroid/startup/connections.py @@ -0,0 +1,94 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import base64 +import ipaddress +import struct +import sys + +from telethon.errors.rpcerrorlist import AuthKeyDuplicatedError +from telethon.sessions.string import _STRUCT_PREFORMAT, CURRENT_VERSION, StringSession + +from ..configs import Var +from . import * +from .BaseClient import UltroidClient + +_PYRO_FORM = {351: ">B?256sI?", 356: ">B?256sQ?", 362: ">BI?256sQ?"} + +# https://github.com/pyrogram/pyrogram/blob/master/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst + +DC_IPV4 = { + 1: "149.154.175.53", + 2: "149.154.167.51", + 3: "149.154.175.100", + 4: "149.154.167.91", + 5: "91.108.56.130", +} + + +def validate_session(session, logger=LOGS, _exit=True): + from strings import get_string + + if session: + # Telethon Session + if session.startswith(CURRENT_VERSION): + if len(session.strip()) != 353: + logger.exception(get_string("py_c1")) + sys.exit() + return StringSession(session) + + # Pyrogram Session + elif len(session) in _PYRO_FORM.keys(): + data_ = struct.unpack( + _PYRO_FORM[len(session)], + base64.urlsafe_b64decode(session + "=" * (-len(session) % 4)), + ) + if len(session) in [351, 356]: + auth_id = 2 + else: + auth_id = 3 + dc_id, auth_key = data_[0], data_[auth_id] + return StringSession( + CURRENT_VERSION + + base64.urlsafe_b64encode( + struct.pack( + _STRUCT_PREFORMAT.format(4), + dc_id, + ipaddress.ip_address(DC_IPV4[dc_id]).packed, + 443, + auth_key, + ) + ).decode("ascii") + ) + else: + logger.exception(get_string("py_c1")) + if _exit: + sys.exit() + logger.exception(get_string("py_c2")) + if _exit: + sys.exit() + + +def vc_connection(udB, ultroid_bot): + from strings import get_string + + VC_SESSION = Var.VC_SESSION or udB.get_key("VC_SESSION") + if VC_SESSION and VC_SESSION != Var.SESSION: + LOGS.info("Starting up VcClient.") + try: + return UltroidClient( + validate_session(VC_SESSION, _exit=False), + log_attempt=False, + exit_on_error=False, + ) + except (AuthKeyDuplicatedError, EOFError): + LOGS.info(get_string("py_c3")) + udB.del_key("VC_SESSION") + except Exception as er: + LOGS.info("While creating Client for VC.") + LOGS.exception(er) + return ultroid_bot diff --git a/pyUltroid/startup/funcs.py b/pyUltroid/startup/funcs.py new file mode 100644 index 0000000000000000000000000000000000000000..b893aebd25f349d367e8caa0b5a73d7f7a44352f --- /dev/null +++ b/pyUltroid/startup/funcs.py @@ -0,0 +1,563 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import asyncio +import os +import random +import shutil +import time +from datetime import datetime, timezone as dt_timezone +from random import randint + +from ..configs import Var + +try: + from pytz import timezone +except ImportError: + timezone = None + +from telethon.errors import ( + ChannelsTooMuchError, + ChatAdminRequiredError, + MessageIdInvalidError, + MessageNotModifiedError, + UserNotParticipantError, +) +from telethon.tl.custom import Button +from telethon.tl.functions.channels import ( + CreateChannelRequest, + EditAdminRequest, + EditPhotoRequest, + InviteToChannelRequest, +) +from telethon.tl.functions.channels import JoinChannelRequest +from telethon.tl.functions.contacts import UnblockRequest +from telethon.tl.types import ( + ChatAdminRights, + ChatPhotoEmpty, + InputChatUploadedPhoto, + InputMessagesFilterDocument, +) +from telethon.utils import get_peer_id +from decouple import config, RepositoryEnv +from .. import LOGS, ULTConfig +from ..fns.helper import download_file, inline_mention, updater + +db_url = 0 +REDIS_KEEPALIVE_KEY = "KEEP_ACTIVE" +REDIS_KEEPALIVE_INTERVAL_SECONDS = 7 * 24 * 60 * 60 + + +async def autoupdate_local_database(): + from .. import Var, asst, udB, ultroid_bot + + global db_url + db_url = ( + udB.get_key("TGDB_URL") or Var.TGDB_URL or ultroid_bot._cache.get("TGDB_URL") + ) + if db_url: + _split = db_url.split("/") + _channel = _split[-2] + _id = _split[-1] + try: + await asst.edit_message( + int(_channel) if _channel.isdigit() else _channel, + message=_id, + file="database.json", + text="**Do not delete this file.**", + ) + except MessageNotModifiedError: + return + except MessageIdInvalidError: + pass + try: + LOG_CHANNEL = ( + udB.get_key("LOG_CHANNEL") + or Var.LOG_CHANNEL + or asst._cache.get("LOG_CHANNEL") + or "me" + ) + msg = await asst.send_message( + LOG_CHANNEL, "**Do not delete this file.**", file="database.json" + ) + asst._cache["TGDB_URL"] = msg.message_link + udB.set_key("TGDB_URL", msg.message_link) + except Exception as ex: + LOGS.error(f"Error on autoupdate_local_database: {ex}") + + +def update_envs(): + """Update Var. attributes to udB""" + from .. import udB + _envs = [*list(os.environ)] + env_file = config._find_file(".") + if env_file: + try: + [_envs.append(_) for _ in list(RepositoryEnv(env_file).data)] + except Exception: + pass + for envs in _envs: + if ( + envs in ["LOG_CHANNEL", "BOT_TOKEN", "BOTMODE", "DUAL_MODE", "language"] + or envs in udB.keys() + ): + if _value := os.environ.get(envs): + udB.set_key(envs, _value) + else: + udB.set_key(envs, config(envs, default=None)) + + +async def startup_stuff(): + from .. import udB + + x = ["resources/auth", "resources/downloads"] + for x in x: + if not os.path.isdir(x): + os.mkdir(x) + + CT = udB.get_key("CUSTOM_THUMBNAIL") + if CT: + path = "resources/extras/thumbnail.jpg" + ULTConfig.thumb = path + try: + await download_file(CT, path) + except Exception as er: + LOGS.exception(er) + elif CT is False: + ULTConfig.thumb = None + GT = udB.get_key("GDRIVE_AUTH_TOKEN") + if GT: + with open("resources/auth/gdrive_creds.json", "w") as t_file: + t_file.write(GT) + + if udB.get_key("AUTH_TOKEN"): + udB.del_key("AUTH_TOKEN") + + MM = udB.get_key("MEGA_MAIL") + MP = udB.get_key("MEGA_PASS") + if MM and MP: + with open(".megarc", "w") as mega: + mega.write(f"[Login]\nUsername = {MM}\nPassword = {MP}") + + TZ = udB.get_key("TIMEZONE") + if TZ and timezone: + try: + timezone(TZ) + os.environ["TZ"] = TZ + time.tzset() + except AttributeError as er: + LOGS.debug(er) + except BaseException: + LOGS.critical( + "Incorrect Timezone ,\nCheck Available Timezone From Here https://graph.org/Ultroid-06-18-2\nSo Time is Default UTC" + ) + os.environ["TZ"] = "UTC" + time.tzset() + + +async def keep_redis_alive(): + from .. import udB + + if udB.name != "Redis": + return + + interval = udB.get_key("REDIS_KEEPALIVE_INTERVAL") + try: + interval = int(interval) if interval else REDIS_KEEPALIVE_INTERVAL_SECONDS + except (TypeError, ValueError): + interval = REDIS_KEEPALIVE_INTERVAL_SECONDS + interval = max(interval, 60) + + while True: + try: + now = datetime.now(dt_timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") + udB.set_key(REDIS_KEEPALIVE_KEY, f"Updated value at {now}") + LOGS.debug( + "Redis keepalive updated key '%s' (next run in %s seconds).", + REDIS_KEEPALIVE_KEY, + interval, + ) + except Exception as exc: + LOGS.warning("Redis keepalive update failed: %s", exc) + await asyncio.sleep(interval) + + +async def autobot(): + from .. import udB, ultroid_bot + + if udB.get_key("BOT_TOKEN"): + return + await ultroid_bot.start() + LOGS.info("MAKING A TELEGRAM BOT FOR YOU AT @BotFather, Kindly Wait") + who = ultroid_bot.me + name = who.first_name + "'s Bot" + if who.username: + username = who.username + "_bot" + else: + username = "ultroid_" + (str(who.id))[5:] + "_bot" + bf = "@BotFather" + await ultroid_bot(UnblockRequest(bf)) + await ultroid_bot.send_message(bf, "/cancel") + await asyncio.sleep(1) + await ultroid_bot.send_message(bf, "/newbot") + await asyncio.sleep(1) + isdone = (await ultroid_bot.get_messages(bf, limit=1))[0].text + if isdone.startswith("That I cannot do.") or "20 bots" in isdone: + LOGS.critical( + "Please make a Bot from @BotFather and add it's token in BOT_TOKEN, as an env var and restart me." + ) + import sys + + sys.exit(1) + await ultroid_bot.send_message(bf, name) + await asyncio.sleep(1) + isdone = (await ultroid_bot.get_messages(bf, limit=1))[0].text + if not isdone.startswith("Good."): + await ultroid_bot.send_message(bf, "My Assistant Bot") + await asyncio.sleep(1) + isdone = (await ultroid_bot.get_messages(bf, limit=1))[0].text + if not isdone.startswith("Good."): + LOGS.critical( + "Please make a Bot from @BotFather and add it's token in BOT_TOKEN, as an env var and restart me." + ) + import sys + + sys.exit(1) + await ultroid_bot.send_message(bf, username) + await asyncio.sleep(1) + isdone = (await ultroid_bot.get_messages(bf, limit=1))[0].text + await ultroid_bot.send_read_acknowledge("botfather") + if isdone.startswith("Sorry,"): + ran = randint(1, 100) + username = "ultroid_" + (str(who.id))[6:] + str(ran) + "_bot" + await ultroid_bot.send_message(bf, username) + await asyncio.sleep(1) + isdone = (await ultroid_bot.get_messages(bf, limit=1))[0].text + if isdone.startswith("Done!"): + token = isdone.split("`")[1] + udB.set_key("BOT_TOKEN", token) + await enable_inline(ultroid_bot, username) + LOGS.info( + f"Done. Successfully created @{username} to be used as your assistant bot!" + ) + else: + LOGS.info( + "Please Delete Some Of your Telegram bots at @Botfather or Set Var BOT_TOKEN with token of a bot" + ) + + import sys + + sys.exit(1) + + +async def autopilot(): + from .. import asst, udB, ultroid_bot + + channel = udB.get_key("LOG_CHANNEL") + new_channel = None + if channel: + try: + chat = await ultroid_bot.get_entity(channel) + except BaseException as err: + LOGS.exception(err) + udB.del_key("LOG_CHANNEL") + channel = None + if not channel: + + async def _save(exc): + udB._cache["LOG_CHANNEL"] = ultroid_bot.me.id + await asst.send_message( + ultroid_bot.me.id, f"Failed to Create Log Channel due to {exc}.." + ) + + if ultroid_bot._bot: + msg_ = "'LOG_CHANNEL' not found! Add it in order to use 'BOTMODE'" + LOGS.error(msg_) + return await _save(msg_) + LOGS.info("Creating a Log Channel for You!") + try: + r = await ultroid_bot( + CreateChannelRequest( + title="My Ultroid Logs", + about="My Ultroid Log Group\n\n Join @TeamUltroid", + megagroup=True, + ), + ) + except ChannelsTooMuchError as er: + LOGS.critical( + "You Are in Too Many Channels & Groups , Leave some And Restart The Bot" + ) + return await _save(str(er)) + except BaseException as er: + LOGS.exception(er) + LOGS.info( + "Something Went Wrong , Create A Group and set its id on config var LOG_CHANNEL." + ) + + return await _save(str(er)) + new_channel = True + chat = r.chats[0] + channel = get_peer_id(chat) + udB.set_key("LOG_CHANNEL", channel) + assistant = True + try: + await ultroid_bot.get_permissions(int(channel), asst.me.username) + except UserNotParticipantError: + try: + await ultroid_bot(InviteToChannelRequest(int(channel), [asst.me.username])) + except BaseException as er: + LOGS.info("Error while Adding Assistant to Log Channel") + LOGS.exception(er) + assistant = False + except BaseException as er: + assistant = False + LOGS.exception(er) + if assistant and new_channel: + try: + achat = await asst.get_entity(int(channel)) + except BaseException as er: + achat = None + LOGS.info("Error while getting Log channel from Assistant") + LOGS.exception(er) + if achat and not achat.admin_rights: + rights = ChatAdminRights( + add_admins=True, + invite_users=True, + change_info=True, + ban_users=True, + delete_messages=True, + pin_messages=True, + anonymous=False, + manage_call=True, + ) + try: + await ultroid_bot( + EditAdminRequest( + int(channel), asst.me.username, rights, "Assistant" + ) + ) + except ChatAdminRequiredError: + LOGS.info( + "Failed to promote 'Assistant Bot' in 'Log Channel' due to 'Admin Privileges'" + ) + except BaseException as er: + LOGS.info("Error while promoting assistant in Log Channel..") + LOGS.exception(er) + if isinstance(chat.photo, ChatPhotoEmpty): + photo, _ = await download_file( + "https://graph.org/file/27c6812becf6f376cbb10.jpg", "channelphoto.jpg" + ) + ll = await ultroid_bot.upload_file(photo) + try: + await ultroid_bot( + EditPhotoRequest(int(channel), InputChatUploadedPhoto(ll)) + ) + except BaseException as er: + LOGS.exception(er) + os.remove(photo) + + +# customize assistant + + +async def customize(): + from .. import asst, udB, ultroid_bot + + rem = None + try: + chat_id = udB.get_key("LOG_CHANNEL") + if asst.me.photo: + return + LOGS.info("Customising Your Assistant Bot in @BOTFATHER") + UL = f"@{asst.me.username}" + if not ultroid_bot.me.username: + sir = ultroid_bot.me.first_name + else: + sir = f"@{ultroid_bot.me.username}" + file = random.choice( + [ + "https://graph.org/file/92cd6dbd34b0d1d73a0da.jpg", + "https://graph.org/file/a97973ee0425b523cdc28.jpg", + "resources/extras/ultroid_assistant.jpg", + ] + ) + if not os.path.exists(file): + file, _ = await download_file(file, "profile.jpg") + rem = True + msg = await asst.send_message( + chat_id, "**Auto Customisation** Started on @Botfather" + ) + await asyncio.sleep(1) + await ultroid_bot.send_message("botfather", "/cancel") + await asyncio.sleep(1) + await ultroid_bot.send_message("botfather", "/setuserpic") + await asyncio.sleep(1) + isdone = (await ultroid_bot.get_messages("botfather", limit=1))[0].text + if isdone.startswith("Invalid bot"): + LOGS.info("Error while trying to customise assistant, skipping...") + return + await ultroid_bot.send_message("botfather", UL) + await asyncio.sleep(1) + await ultroid_bot.send_file("botfather", file) + await asyncio.sleep(2) + await ultroid_bot.send_message("botfather", "/setabouttext") + await asyncio.sleep(1) + await ultroid_bot.send_message("botfather", UL) + await asyncio.sleep(1) + await ultroid_bot.send_message( + "botfather", f"✨ Hello ✨!! I'm Assistant Bot of {sir}" + ) + await asyncio.sleep(2) + await ultroid_bot.send_message("botfather", "/setdescription") + await asyncio.sleep(1) + await ultroid_bot.send_message("botfather", UL) + await asyncio.sleep(1) + await ultroid_bot.send_message( + "botfather", + f"✨ Powerful Ultroid Assistant Bot ✨\n✨ Master ~ {sir} ✨\n\n✨ Powered By ~ @TeamUltroid ✨", + ) + await asyncio.sleep(2) + await msg.edit("Completed **Auto Customisation** at @BotFather.") + if rem: + os.remove(file) + LOGS.info("Customisation Done") + except Exception as e: + LOGS.exception(e) + + +async def plug(plugin_channels): + from .. import ultroid_bot + from .utils import load_addons + + if ultroid_bot._bot: + LOGS.info("Plugin Channels can't be used in 'BOTMODE'") + return + if os.path.exists("addons") and not os.path.exists("addons/.git"): + shutil.rmtree("addons") + if not os.path.exists("addons"): + os.mkdir("addons") + if not os.path.exists("addons/__init__.py"): + with open("addons/__init__.py", "w") as f: + f.write("from plugins import *\n\nbot = ultroid_bot") + LOGS.info("• Loading Plugins from Plugin Channel(s) •") + for chat in plugin_channels: + LOGS.info(f"{'•'*4} {chat}") + try: + async for x in ultroid_bot.iter_messages( + chat, search=".py", filter=InputMessagesFilterDocument, wait_time=10 + ): + plugin = "addons/" + x.file.name.replace("_", "-").replace("|", "-") + if not os.path.exists(plugin): + await asyncio.sleep(0.6) + if x.text == "#IGNORE": + continue + plugin = await x.download_media(plugin) + try: + load_addons(plugin) + except Exception as e: + LOGS.info(f"Ultroid - PLUGIN_CHANNEL - ERROR - {plugin}") + LOGS.exception(e) + os.remove(plugin) + except Exception as er: + LOGS.exception(er) + + + +async def ready(): + from .. import asst, udB, ultroid_bot + + chat_id = udB.get_key("LOG_CHANNEL") + spam_sent = None + if not udB.get_key("INIT_DEPLOY"): # Detailed Message at Initial Deploy + MSG = """🎇 **Thanks for Deploying Ultroid Userbot!** +• Here, are the Some Basic stuff from, where you can Know, about its Usage.""" + PHOTO = "https://graph.org/file/54a917cc9dbb94733ea5f.jpg" + BTTS = Button.inline("• Click to Start •", "initft_2") + udB.set_key("INIT_DEPLOY", "Done") + else: + MSG = f"**Ultroid has been deployed!**\n➖➖➖➖➖➖➖➖➖➖\n**UserMode**: {inline_mention(ultroid_bot.me)}\n**Assistant**: @{asst.me.username}\n➖➖➖➖➖➖➖➖➖➖\n**Support**: @TeamUltroid\n➖➖➖➖➖➖➖➖➖➖" + BTTS, PHOTO = None, None + prev_spam = udB.get_key("LAST_UPDATE_LOG_SPAM") + if prev_spam: + try: + await ultroid_bot.delete_messages(chat_id, int(prev_spam)) + except Exception as E: + LOGS.info("Error while Deleting Previous Update Message :" + str(E)) + if await updater(): + BTTS = Button.inline("Update Available", "updtavail") + + try: + spam_sent = await asst.send_message(chat_id, MSG, file=PHOTO, buttons=BTTS) + except ValueError as e: + try: + await (await ultroid_bot.send_message(chat_id, str(e))).delete() + spam_sent = await asst.send_message(chat_id, MSG, file=PHOTO, buttons=BTTS) + except Exception as g: + LOGS.info(g) + except Exception as el: + LOGS.info(el) + try: + spam_sent = await ultroid_bot.send_message(chat_id, MSG) + except Exception as ef: + LOGS.exception(ef) + if spam_sent and not spam_sent.media: + udB.set_key("LAST_UPDATE_LOG_SPAM", spam_sent.id) + + try: + await ultroid_bot(JoinChannelRequest("TheUltroid")) + except Exception as er: + LOGS.exception(er) + + +async def WasItRestart(udb): + key = udb.get_key("_RESTART") + if not key: + return + from .. import asst, ultroid_bot + + try: + data = key.split("_") + who = asst if data[0] == "bot" else ultroid_bot + await who.edit_message( + int(data[1]), int(data[2]), "__Restarted Successfully.__" + ) + except Exception as er: + LOGS.exception(er) + udb.del_key("_RESTART") + + +def _version_changes(udb): + for _ in [ + "BOT_USERS", + "BOT_BLS", + "VC_SUDOS", + "SUDOS", + "CLEANCHAT", + "LOGUSERS", + "PLUGIN_CHANNEL", + "CH_SOURCE", + "CH_DESTINATION", + "BROADCAST", + ]: + key = udb.get_key(_) + if key and not isinstance(key, list): + key_str = str(key) + new_ = [ + int(z) if z.isdigit() or (z.startswith("-") and z[1:].isdigit()) else z + for z in key_str.split() + ] + udb.set_key(_, new_) + + +async def enable_inline(ultroid_bot, username): + bf = "BotFather" + await ultroid_bot.send_message(bf, "/setinline") + await asyncio.sleep(1) + await ultroid_bot.send_message(bf, f"@{username}") + await asyncio.sleep(1) + await ultroid_bot.send_message(bf, "Search") + await ultroid_bot.send_read_acknowledge(bf) diff --git a/pyUltroid/startup/loader.py b/pyUltroid/startup/loader.py new file mode 100644 index 0000000000000000000000000000000000000000..61be2f7fc8b8144d660069441f4340167d8e9b27 --- /dev/null +++ b/pyUltroid/startup/loader.py @@ -0,0 +1,144 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import os +import subprocess +import sys +from shutil import rmtree + +from decouple import config +from git import Repo + +from .. import * +from ..dB._core import HELP +from ..loader import Loader +from . import * +from .utils import load_addons + + +def _after_load(loader, module, plugin_name=""): + if not module or plugin_name.startswith("_"): + return + from strings import get_help + + if doc_ := get_help(plugin_name) or module.__doc__: + try: + doc = doc_.format(i=HNDLR) + except Exception as er: + loader._logger.exception(er) + loader._logger.info(f"Error in {plugin_name}: {module}") + return + if loader.key in HELP.keys(): + update_cmd = HELP[loader.key] + try: + update_cmd.update({plugin_name: doc}) + except BaseException as er: + loader._logger.exception(er) + else: + try: + HELP.update({loader.key: {plugin_name: doc}}) + except BaseException as em: + loader._logger.exception(em) + + +def load_other_plugins(addons=None, pmbot=None, manager=None, vcbot=None): + + # for official + _exclude = udB.get_key("EXCLUDE_OFFICIAL") or config("EXCLUDE_OFFICIAL", None) + _exclude = _exclude.split() if _exclude else [] + + # "INCLUDE_ONLY" was added to reduce Big List in "EXCLUDE_OFFICIAL" Plugin + _in_only = udB.get_key("INCLUDE_ONLY") or config("INCLUDE_ONLY", None) + _in_only = _in_only.split() if _in_only else [] + Loader().load(include=_in_only, exclude=_exclude, after_load=_after_load) + + # for assistant + if not USER_MODE and not udB.get_key("DISABLE_AST_PLUGINS"): + _ast_exc = ["pmbot"] + if _in_only and "games" not in _in_only: + _ast_exc.append("games") + Loader(path="assistant").load( + log=False, exclude=_ast_exc, after_load=_after_load + ) + + # for addons + if addons: + if url := udB.get_key("ADDONS_URL"): + subprocess.run(f"git clone -q {url} addons", shell=True) + if os.path.exists("addons") and not os.path.exists("addons/.git"): + rmtree("addons") + if not os.path.exists("addons"): + try: + branch = str(Repo().active_branch) + except Exception: + branch = "main" + subprocess.run( + f"git clone -q -b {branch} https://github.com/TeamUltroid/UltroidAddons.git addons", + shell=True, + ) + else: + subprocess.run("cd addons && git pull -q && cd ..", shell=True) + + if not os.path.exists("addons"): + subprocess.run( + "git clone -q https://github.com/TeamUltroid/UltroidAddons.git addons", + shell=True, + ) + if os.path.exists("addons/addons.txt"): + # generally addons req already there so it won't take much time + # subprocess.run( + # "rm -rf /usr/local/lib/python3.*/site-packages/pip/_vendor/.wh*" + # ) + subprocess.run( + f"{sys.executable} -m pip install --no-cache-dir -q -r ./addons/addons.txt", + shell=True, + ) + + _exclude = udB.get_key("EXCLUDE_ADDONS") + _exclude = _exclude.split() if _exclude else [] + _in_only = udB.get_key("INCLUDE_ADDONS") + _in_only = _in_only.split() if _in_only else [] + + Loader(path="addons", key="Addons").load( + func=load_addons, + include=_in_only, + exclude=_exclude, + after_load=_after_load, + load_all=True, + ) + + if not USER_MODE: + # group manager + if manager: + Loader(path="assistant/manager", key="Group Manager").load() + + # chat via assistant + if pmbot: + Loader(path="assistant/pmbot.py").load(log=False) + + # vc bot + if vcbot and (vcClient and not vcClient.me.bot): + try: + import pytgcalls # ignore: pylint + + if os.path.exists("vcbot"): + if os.path.exists("vcbot/.git"): + subprocess.run("cd vcbot && git pull", shell=True) + else: + rmtree("vcbot") + if not os.path.exists("vcbot"): + subprocess.run( + "git clone https://github.com/TeamUltroid/VcBot vcbot", shell=True + ) + try: + if not os.path.exists("vcbot/downloads"): + os.mkdir("vcbot/downloads") + Loader(path="vcbot", key="VCBot").load(after_load=_after_load) + except FileNotFoundError as e: + LOGS.error(f"{e} Skipping VCBot Installation.") + except ModuleNotFoundError: + LOGS.error("'pytgcalls' not installed!\nSkipping loading of VCBOT.") diff --git a/pyUltroid/startup/utils.py b/pyUltroid/startup/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..49ce4aa1013f18d94102927e4674838760b1ae49 --- /dev/null +++ b/pyUltroid/startup/utils.py @@ -0,0 +1,99 @@ +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from importlib import util +from sys import modules + +# for addons + +configPaths = [ + "ub", + "var", + "support", + "userbot", + "telebot", + "fridaybot", + "uniborg.util", + "telebot.utils", + "userbot.utils", + "userbot.events", + "userbot.config", + "fridaybot.utils", + "fridaybot.Config", + "userbot.uniborgConfig", +] + + +def load_addons(plugin_name): + base_name = plugin_name.split("/")[-1].split("\\")[-1].replace(".py", "") + if base_name.startswith("__"): + return + from pyUltroid import fns + + from .. import HNDLR, LOGS, asst, udB, ultroid_bot + from .._misc import _supporter as config + from .._misc._assistant import asst_cmd, callback, in_pattern + from .._misc._decorators import ultroid_cmd + from .._misc._supporter import Config, admin_cmd, sudo_cmd + from .._misc._wrappers import eod, eor + from ..configs import Var + from ..dB._core import HELP + + name = plugin_name.replace("/", ".").replace("\\", ".").replace(".py", "") + spec = util.spec_from_file_location(name, plugin_name) + mod = util.module_from_spec(spec) + for path in configPaths: + modules[path] = config + modules["pyUltroid.functions"] = fns + mod.LOG_CHANNEL = udB.get_key("LOG_CHANNEL") + mod.udB = udB + mod.asst = asst + mod.tgbot = asst + mod.ultroid_bot = ultroid_bot + mod.ub = ultroid_bot + mod.bot = ultroid_bot + mod.ultroid = ultroid_bot + mod.borg = ultroid_bot + mod.telebot = ultroid_bot + mod.jarvis = ultroid_bot + mod.friday = ultroid_bot + mod.eod = eod + mod.edit_delete = eod + mod.LOGS = LOGS + mod.in_pattern = in_pattern + mod.hndlr = HNDLR + mod.handler = HNDLR + mod.HNDLR = HNDLR + mod.CMD_HNDLR = HNDLR + mod.Config = Config + mod.Var = Var + mod.eor = eor + mod.edit_or_reply = eor + mod.asst_cmd = asst_cmd + mod.ultroid_cmd = ultroid_cmd + mod.on_cmd = ultroid_cmd + mod.callback = callback + mod.Redis = udB.get_key + mod.admin_cmd = admin_cmd + mod.sudo_cmd = sudo_cmd + mod.HELP = HELP.get("Addons", {}) + mod.CMD_HELP = HELP.get("Addons", {}) + + spec.loader.exec_module(mod) + modules[name] = mod + doc = modules[name].__doc__.format(i=HNDLR) if modules[name].__doc__ else "" + if "Addons" in HELP.keys(): + update_cmd = HELP["Addons"] + try: + update_cmd.update({base_name: doc}) + except BaseException: + pass + else: + try: + HELP.update({"Addons": {base_name: doc}}) + except BaseException as em: + pass diff --git a/pyUltroid/version.py b/pyUltroid/version.py new file mode 100644 index 0000000000000000000000000000000000000000..9752b2c2ec4d2031ce319538298dff67a64cc5d3 --- /dev/null +++ b/pyUltroid/version.py @@ -0,0 +1,2 @@ +__version__ = "2026.04.03" +ultroid_version = "2.1.2" diff --git a/resources/extras/inline.jpg b/resources/extras/inline.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16abdfe7244f1a6cc873bde4f7ddafe34a8aee9d --- /dev/null +++ b/resources/extras/inline.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4422045b0cde7b85e0cfc626b29346281cb44b9de19990d07e1a10e9d1cb1c95 +size 28332 diff --git a/resources/extras/logo_readme.jpg b/resources/extras/logo_readme.jpg new file mode 100644 index 0000000000000000000000000000000000000000..657694d27f0eb2eede94ab2eda290c501aabb5ab --- /dev/null +++ b/resources/extras/logo_readme.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc882e20eaaffc4124258d43691a601a8024cb189fd69a404d6cc8b038bbf1b6 +size 2860101 diff --git a/resources/extras/redistut.md b/resources/extras/redistut.md new file mode 100644 index 0000000000000000000000000000000000000000..62dab63e276f52496b4c55063d8f5743177e1522 --- /dev/null +++ b/resources/extras/redistut.md @@ -0,0 +1,17 @@ +# Tutorial To Get Redis DB URL and Password +![Redis](https://img.shields.io/badge/redis-%23DD0031.svg?style=for-the-badge&logo=redis&logoColor=white) + +## Process For Creating DB :- +- Go To [Redis.com](Https://redis.com) and click "`Try Free`" in Top Right Corner. +- Fill All The Required Details Like email, first and last name, password, etc. +- Tick Below "I agree the corresponding...Privacy Policy." and Click "Get Started". +- Now Check Your Email, and click the "Activate Now" sent by redislabs via email. +- Now Login and Chose Free Plan in "Fixed Size" Area and Write any name in "Subscription Area". +- On the Next Page Write Database Name and click Activate. + +> Congrats! Your DB has been created 🥳 + +## Process For Getting DB Credentials:- +- Wait 5 mins after DB creation. +- Then There Would Be 2 Things Named "`Endpoint`" and "`Access Control & Security`". +- Copy Both Of Them and Paste Endpoint url in `REDIS_URI` and "Access ...Security" in `REDIS_PASSWORD`. diff --git a/resources/extras/teamultroid.jpg b/resources/extras/teamultroid.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c0ea3a524cb5d520af15fb8cf1ba6e842b757611 --- /dev/null +++ b/resources/extras/teamultroid.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69ba198fafae8b2da842c3f7408397d2b89d1b1bf41f430dcf7036e72fbdc695 +size 16174 diff --git a/resources/extras/template.jpg b/resources/extras/template.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2d9998804c828518ef08da46a9e29fb238593063 --- /dev/null +++ b/resources/extras/template.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37993dbb90ff92ddc27eb7692f6d11639d04109f46fcff4bd27cd6756882b402 +size 37920 diff --git a/resources/extras/ultroid.jpg b/resources/extras/ultroid.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d19fa905da8bf5955719dd3fe095d8245fe2f58f --- /dev/null +++ b/resources/extras/ultroid.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa0eb740cccbf935db65b0c8de7413c1f65b399a2fe7c247a6d3ed9980bbacfc +size 109945 diff --git a/resources/extras/ultroid_assistant.jpg b/resources/extras/ultroid_assistant.jpg new file mode 100644 index 0000000000000000000000000000000000000000..431d0e405a58632fdf3ed3c77d163a397c0d9c89 --- /dev/null +++ b/resources/extras/ultroid_assistant.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f170af89f5e1cabe8b32114960410e7b01efffee3fd716a1164ce75bdce66e0a +size 25297 diff --git a/resources/extras/ultroid_blank.png b/resources/extras/ultroid_blank.png new file mode 100644 index 0000000000000000000000000000000000000000..394f50f76dfc70e2124b39d32d19362dd1c389fe --- /dev/null +++ b/resources/extras/ultroid_blank.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c77481114376d6ef4f84db34882514a3e3598eaea25c5603b826f765f2085ef +size 889 diff --git a/resources/fonts/0.ttf b/resources/fonts/0.ttf new file mode 100644 index 0000000000000000000000000000000000000000..351de7b5a976f3080edeae2d032165abbf647414 --- /dev/null +++ b/resources/fonts/0.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afbd30c1ad67286eff72df63b5d563a6d3827c01bca881013ae87982930ac369 +size 56364 diff --git a/resources/fonts/1.ttf b/resources/fonts/1.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8b75138500b4ae153d873fae35fdbc79342186e2 --- /dev/null +++ b/resources/fonts/1.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6af8a998d697a75cedfeda6d1b15eafeb999b79b9a375f3b17ecd731987a4f6a +size 20528 diff --git a/resources/fonts/10.ttf b/resources/fonts/10.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b8c082777305ec04acaef169d7292fbcacbc0a04 --- /dev/null +++ b/resources/fonts/10.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8175476c5960424d18e7e8654eb33f1e5b9260093ad296bbeb319c507e4615f5 +size 17468 diff --git a/resources/fonts/11.otf b/resources/fonts/11.otf new file mode 100644 index 0000000000000000000000000000000000000000..0e57362cfee789dd51e14508d5e042ba5204c7d6 --- /dev/null +++ b/resources/fonts/11.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45d908c5e4d4cda1685d6b1a3b0d8b980794f7c4861ca16356bbb4fe05b785ab +size 341760 diff --git a/resources/fonts/12.otf b/resources/fonts/12.otf new file mode 100644 index 0000000000000000000000000000000000000000..2d29a1aac23b4d6bc6e4faf47d8e59778d5c33e3 --- /dev/null +++ b/resources/fonts/12.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58f09861fdfa43587a9c1253cefa08f0a11cbfdef7d76e557b847de11647b272 +size 43024 diff --git a/resources/fonts/2.ttf b/resources/fonts/2.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fe48fba5b809cfafa06cf54347a320005ba2dad7 --- /dev/null +++ b/resources/fonts/2.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bb0d23e78dc94851b86732c28b9e0da1724893ba9301483bfdfcea50c19fac4 +size 21872 diff --git a/resources/fonts/3.ttf b/resources/fonts/3.ttf new file mode 100644 index 0000000000000000000000000000000000000000..afffe4799e98d56433c7f5b501725273964a8514 --- /dev/null +++ b/resources/fonts/3.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed465235557d0329ed155fb15436d551b79887120cae40dee76421a4373c811f +size 222620 diff --git a/resources/fonts/4.ttf b/resources/fonts/4.ttf new file mode 100644 index 0000000000000000000000000000000000000000..355d01d694d7d63ac18af405c0806ccfade8b83c --- /dev/null +++ b/resources/fonts/4.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbd9d5504cc11332481cdc81f99875cbf11d4f22665815c83d351454857c2b16 +size 36152 diff --git a/resources/fonts/5.ttf b/resources/fonts/5.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3f6e36116f13591cb646572bd04c0808f7a4c386 --- /dev/null +++ b/resources/fonts/5.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e3378e909a49a8718273b7e004315d56822275b1a6f25ef3fb9d1a348d2d887 +size 29536 diff --git a/resources/fonts/6.ttf b/resources/fonts/6.ttf new file mode 100644 index 0000000000000000000000000000000000000000..24ae7906c035fbd8b607ee5cc86b6cfdb73d3092 --- /dev/null +++ b/resources/fonts/6.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdc8a783806a63a7671a24c3cec2b8e1eb15a9a7b3d611d0a704ed2bd1f3d824 +size 53744 diff --git a/resources/fonts/7.ttf b/resources/fonts/7.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c288a645c971e988bb45219a9c169c9524e1d26b --- /dev/null +++ b/resources/fonts/7.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d16362cfb8e604fc701e302b4877291126fe978c671c8619c8ec7a156ab38d6 +size 39100 diff --git a/resources/fonts/8.ttf b/resources/fonts/8.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b347961cc2bebc882cd890dfdc135ab855ff7b47 --- /dev/null +++ b/resources/fonts/8.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91ba1b64e5b370847dfe0f12140b37b41aa8c4dedde04712d95a387aa8aae6e6 +size 18920 diff --git a/resources/fonts/9.ttf b/resources/fonts/9.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3a40159d8934eeda8591c3ac62c2c61ff98282d3 --- /dev/null +++ b/resources/fonts/9.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7570ac30f64b22011a2994ada2f18398683600fb6aa4313e2266d1f765901a3 +size 18528 diff --git a/resources/fonts/DroidSansMono.ttf b/resources/fonts/DroidSansMono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3abb05809c562c75acc149112767c2b0f90dcf6c --- /dev/null +++ b/resources/fonts/DroidSansMono.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db19a1fdaba41cc4a2fec0330e5c15e71c6dd68a3ef074f4f28268828b45c862 +size 108128 diff --git a/resources/fonts/Quivira.otf b/resources/fonts/Quivira.otf new file mode 100644 index 0000000000000000000000000000000000000000..4c59207e30a0d8be42a991014610ebcf834642a2 --- /dev/null +++ b/resources/fonts/Quivira.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03c2f593b224efffceac6f7f3090cdc935f0817399fcc607e4c7cc1b097b3b5a +size 1549016 diff --git a/resources/fonts/Roboto-Italic.ttf b/resources/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1cd51b2c5ae5b6017aa1cf6300b2fc80ad91a22c --- /dev/null +++ b/resources/fonts/Roboto-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cd9337020d0b1593bae42dcca5cfc0fb86f5806ef469d66d09eae1e56ce346b +size 328640 diff --git a/resources/fonts/Roboto-Medium.ttf b/resources/fonts/Roboto-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..38245334ef1ffae8853c94b3e559504a8e0c887b --- /dev/null +++ b/resources/fonts/Roboto-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02868d6a7e80d4e7836b6263dd50d26a63e923a1223c9e027aad6dc2501c27cc +size 160696 diff --git a/resources/fonts/Roboto-Regular.ttf b/resources/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0e3da8ef61a8ab91cd02a6c82418e9060ed20e47 --- /dev/null +++ b/resources/fonts/Roboto-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16466ef65064e6f3885a6d2806b8949ac1ac38b524dd0cf8fc96565eb4cc28e8 +size 158604 diff --git a/resources/fonts/assfont.ttf b/resources/fonts/assfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c9bc3e58a855740804ad1312ba8302933767b153 --- /dev/null +++ b/resources/fonts/assfont.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e92aceba3c95fabe12edf01ae248f613f6d97eef3bdb7009200cb3fae53df609 +size 55300 diff --git a/resources/fonts/default.ttf b/resources/fonts/default.ttf new file mode 100644 index 0000000000000000000000000000000000000000..edc3dbeebeb35c67ce7de21ff400c97eda884b6b --- /dev/null +++ b/resources/fonts/default.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00f1fc230ac99f9b97ba1a7c214eb5b909a78660cb3826fca7d64c3af5a14848 +size 136076 diff --git a/resources/session/session.sh b/resources/session/session.sh new file mode 100644 index 0000000000000000000000000000000000000000..8f824b9a5a5d53910c96320bafa73d523af65a35 --- /dev/null +++ b/resources/session/session.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in . + +clear +echo -e "\e[1m" +echo " _ _ _ _ _ _ " +echo " | | | | | | (_) | |" +echo " | | | | | |_ _ __ ___ _ __| |" +echo " | | | | | __| '__/ _ \| |/ _ |" +echo " | |__| | | |_| | | (_) | | (_| |" +echo " \____/|_|\__|_| \___/|_|\__,_|" +echo -e "\e[0m" +sec=5 +spinner=(⣻ ⢿ ⡿ ⣟ ⣯ ⣷) +while [ $sec -gt 0 ]; do + echo -ne "\e[33m ${spinner[sec]} Starting dependency installation in $sec seconds...\r" + sleep 1 + sec=$(($sec - 1)) +done +echo -e "\e[1;32mInstalling Dependencies ---------------------------\e[0m\n" # Don't Remove Dashes / Fix it +apt-get update +apt-get upgrade -y +pkg upgrade -y +pkg install python wget -y +wget https://raw.githubusercontent.com/TeamUltroid/ultroid/main/resources/session/ssgen.py +pip uninstall telethon -y && install telethon +clear +python3 ssgen.py diff --git a/resources/session/ssgen.py b/resources/session/ssgen.py new file mode 100644 index 0000000000000000000000000000000000000000..a4cbae34a09bda6f0afc9dd48ce1a7aaa893033a --- /dev/null +++ b/resources/session/ssgen.py @@ -0,0 +1,173 @@ +#!/usr/bin/python3 +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import os +from time import sleep + +ULTROID = r""" + _ _ _ _ _ _ + | | | | | | (_) | | + | | | | | |_ _ __ ___ _ __| | + | | | | | __| '__/ _ \| |/ _ | + | |__| | | |_| | | (_) | | (_| | + \____/|_|\__|_| \___/|_|\__,_| +""" + + +def spinner(x): + if x == "tele": + print("Checking if Telethon is installed...") + else: + print("Checking if Pyrogram is installed...") + for _ in range(3): + for frame in r"-\|/-\|/": + print("\b", frame, sep="", end="", flush=True) + sleep(0.1) + + +def clear_screen(): + # https://www.tutorialspoint.com/how-to-clear-screen-in-python#:~:text=In%20Python%20sometimes%20we%20have,screen%20by%20pressing%20Control%20%2B%20l%20. + if os.name == "posix": + os.system("clear") + else: + # for windows platfrom + os.system("cls") + + +def get_api_id_and_hash(): + print( + "Get your API ID and API HASH from my.telegram.org or @ScrapperRoBot to proceed.\n\n", + ) + try: + API_ID = int(input("Please enter your API ID: ")) + except ValueError: + print("APP ID must be an integer.\nQuitting...") + exit(0) + API_HASH = input("Please enter your API HASH: ") + return API_ID, API_HASH + + +def telethon_session(): + try: + spinner("tele") + import telethon + x = "\bFound an existing installation of Telethon...\nSuccessfully Imported.\n\n" + except ImportError: + print("Installing Telethon...") + os.system("pip uninstall telethon -y && pip install -U telethon") + + x = "\bDone. Installed and imported Telethon." + clear_screen() + print(ULTROID) + print(x) + + # the imports + + from telethon.errors.rpcerrorlist import ( + ApiIdInvalidError, + PhoneNumberInvalidError, + UserIsBotError, + ) + from telethon.sessions import StringSession + from telethon.sync import TelegramClient + + API_ID, API_HASH = get_api_id_and_hash() + + # logging in + try: + with TelegramClient(StringSession(), API_ID, API_HASH) as ultroid: + print("Generating a string session for •ULTROID•") + try: + ultroid.send_message( + "me", + f"**ULTROID** `SESSION`:\n\n`{ultroid.session.save()}`\n\n**Do not share this anywhere!**", + ) + print( + "Your SESSION has been generated. Check your Telegram saved messages!" + ) + return + except UserIsBotError: + print("You are trying to Generate Session for your Bot's Account?") + print("Here is That!\n{ultroid.session.save()}\n\n") + print("NOTE: You can't use that as User Session..") + except ApiIdInvalidError: + print( + "Your API ID/API HASH combination is invalid. Kindly recheck.\nQuitting..." + ) + exit(0) + except ValueError: + print("API HASH must not be empty!\nQuitting...") + exit(0) + except PhoneNumberInvalidError: + print("The phone number is invalid!\nQuitting...") + exit(0) + except Exception as er: + print("Unexpected Error Occurred while Creating Session") + print(er) + print("If you think It as a Bug, Report to @UltroidSupportChat.\n\n") + + +def pyro_session(): + try: + spinner("pyro") + from pyrogram import Client + + x = "\bFound an existing installation of Pyrogram...\nSuccessfully Imported.\n\n" + except BaseException: + print("Installing Pyrogram...") + os.system("pip install pyrogram tgcrypto") + x = "\bDone. Installed and imported Pyrogram." + from pyrogram import Client + + clear_screen() + print(ULTROID) + print(x) + + # generate a session + API_ID, API_HASH = get_api_id_and_hash() + print("Enter phone number when asked.\n\n") + try: + with Client(name="ultroid", api_id=API_ID, api_hash=API_HASH, in_memory=True) as pyro: + ss = pyro.export_session_string() + pyro.send_message( + "me", + f"`{ss}`\n\nAbove is your Pyrogram Session String for @TheUltroid. **DO NOT SHARE it.**", + ) + print("Session has been sent to your saved messages!") + exit(0) + except Exception as er: + print("Unexpected error occurred while creating session, make sure to validate your inputs.") + print(er) + + +def main(): + clear_screen() + print(ULTROID) + try: + type_of_ss = int( + input( + "\nUltroid supports both telethon as well as pyrogram sessions.\n\nWhich session do you want to generate?\n1. Telethon Session.\n2. Pyrogram Session.\n\nEnter choice: " + ) + ) + except Exception as e: + print(e) + exit(0) + if type_of_ss == 1: + telethon_session() + elif type_of_ss == 2: + pyro_session() + else: + print("Invalid choice.") + x = input("Run again? (Y/n)") + if x.lower() in ["y", "yes"]: + main() + else: + exit(0) + + +main() diff --git a/resources/startup/_termux.py b/resources/startup/_termux.py new file mode 100644 index 0000000000000000000000000000000000000000..28c7fa2ad9947086b5e5080ab20c0a4c5032a68f --- /dev/null +++ b/resources/startup/_termux.py @@ -0,0 +1,264 @@ +# /usr/bin/python3 +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# Please read the GNU Affero General Public License in +# . + +from datetime import datetime +from os import path, system +from time import sleep + +from colorama import Back, Fore, Style + + +# clear screen +def clear(): + system("clear") + + +MANDATORY_REQS = [ + "https://github.com/New-dev0/Telethon/archive/Cartoon.zip", + "gitpython", + "enhancer", + "telegraph", + "requests", + "python-decouple", + "aiohttp", +] + +OPT_PACKAGES = { + "bs4": "Used for site-scrapping (used in commands like - .gadget and many more)", + "yt-dlp": "Used for Youtuble Related Downloads...", + "youtube-search-python": "Used for youtube video search..", + "pillow": "Used for Image-Conversion related task. (size - approx 50mb ) (required for kang, convert and many more.)", + "psutil": "Used for .usage command.", + "lottie": "Used for animated sticker related conversion.", + "apscheduler": "Used in autopic/nightmode (scheduling tasks.)", + # "git+https://github.com/1danish-00/google_trans_new.git": "Used for translation purposes.", +} + +APT_PACKAGES = ["ffmpeg", "neofetch", "mediainfo"] + +DISCLAIMER_TEXT = "" + +COPYRIGHT = f"©️ TeamUltroid {datetime.now().year}" + +HEADER = f"""{Fore.MAGENTA} +╔╗ ╔╗╔╗ ╔╗ ╔╗ +║║ ║║║║ ╔╝╚╗ ║║ +║║ ║║║║ ╚╗╔╝╔═╗╔══╗╔╗╔═╝║ +║║ ║║║║ ║║ ║╔╝║╔╗║╠╣║╔╗║ +║╚═╝║║╚╗ ║╚╗║║ ║╚╝║║║║╚╝║ +╚═══╝╚═╝ ╚═╝╚╝ ╚══╝╚╝╚══╝\n{Fore.RESET} +""" + +INFO_TEXT = f""" +{Fore.GREEN}# Important points to know. + +{Fore.YELLOW}1. This script will just install basic requirements because of which some command whose requirements are missing won't work. You can view all optional requirements in (./resources/startup/optional-requirements.txt) + +2. You can install that requirement whenever you want with 'pip install' (a very basic python+bash knowledge is required.) + +3. Some of the plugins are disabled for 'Termux Users' to save resources (by adding in EXCLUDE_OFFICIAL). + - Read More - https://t.me/UltroidUpdates/36 + - Also, way to enable the disabled plugins is mentioned in that post. + + # Disabled Plugins Name + - autocorrect - compressor + - Gdrive - instagram + - nsfwfilter - glitch + - pdftools - writer + - youtube - megadl + - autopic - nightmode + - blacklist - forcesubscribe + +4. You can't use 'VCBOT' on Termux. + +5. You can't use 'MongoDB' on Termux (Android). +{Fore.RESET} +* Hope you are smart enought to understand. +* Enter 'A' to Continue, 'E' to Exit..\n +""" + + +def ask_and_wait(text, header: bool = False): + if header: + text = with_header(text) + print(text + "\nPress 'ANY Key' to Continue or 'Ctrl+C' to exit...\n") + input("") + + +def with_header(text): + return HEADER + "\n\n" + text + + +def yes_no_apt(): + yes_no = input("").strip().lower() + if yes_no in ["yes", "y"]: + return True + elif yes_no in ["no", "n"]: + return False + print("Invalid Input\nRe-Enter: ") + return yes_no_apt() + + +def ask_process_info_text(): + strm = input("").lower().strip() + if strm == "e": + print("Exiting...") + exit(0) + elif strm != "a": + print("Invalid Input") + print("Enter 'A' to Continue or 'E' to exit...") + ask_process_info_text() + + +def ask_process_apt_install(): + strm = input("").lower().strip() + if strm == "e": + print("Exiting...") + exit(0) + elif strm == "a": + for apt in APT_PACKAGES: + print(f"* Do you want to install '{apt}'? [Y/N] ") + if yes_no_apt(): + print(f"Installing {apt}...") + system(f"apt install {apt} -y") + else: + print(f"- Discarded {apt}.\n") + elif strm == "i": + names = " ".join(APT_PACKAGES) + print("Installing all apt-packages...") + system(f"apt install {names} -y") + elif strm != "s": + print("Invalid Input\n* Enter Again...") + ask_process_apt_install() + + +def ask_and_wait_opt(): + strm = input("").strip().lower() + if strm == "e": + print("Exiting...") + exit(0) + elif strm == "a": + for opt in OPT_PACKAGES.keys(): + print( + f"* {Fore.YELLOW}Do you want to install '{opt}'? [Y/N]\n- {OPT_PACKAGES[opt]}" + ) + if yes_no_apt(): + print(f"Installing {opt}...") + system(f"pip install {opt}") + else: + print(f"{Fore.YELLOW}- Discarded {opt}.\n") + elif strm == "i": + names = " ".join(OPT_PACKAGES.keys()) + print(f"{Fore.YELLOW}Installing all packages...") + system(f"pip install {names}") + elif strm != "s": + print("Invalid Input\n* Enter Again...") + ask_and_wait_opt() + + +def ask_make_env(): + strm = input("").strip().lower() + if strm in ["yes", "y"]: + print(f"{Fore.YELLOW}* Creating .env file..") + with open(".env", "a") as file: + for var in ["API_ID", "API_HASH", "SESSION", "REDIS_URI", "REDIS_PASSWORD"]: + inp = input(f"Enter {var}\n- ") + file.write(f"{var}={inp}\n") + print("* Created '.env' file successfully! 😃") + + else: + print("OK!") + + +# ------------------------------------------------------------------------------------------ # + +clear() + +print( + f""" +{Fore.BLACK}{Back.WHITE} _____________ + ▄▄ ▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄ +█ █ █ █ █ █ █ ▄ █ █ █ █ █ +█ █ █ █ █ █▄ ▄█ █ █ █ █ ▄ █ █ ▄ █ +█ █▄█ █ █ █ █ █ █▄▄█▄█ █ █ █ █ █ █ █ +█ █ █▄▄▄ █ █ █ ▄▄ █ █▄█ █ █ █▄█ █ +█ █ █ █ █ █ █ █ █ █ █ █ +█▄▄▄▄▄▄▄█▄▄▄▄▄▄▄█ █▄▄▄█ █▄▄▄█ █▄█▄▄▄▄▄▄▄█▄▄▄█▄▄▄▄▄▄█ +{Style.RESET_ALL} +{Fore.GREEN}- ULTROID Termux Installation - + The Main Aim of this script is to deploy Ultroid with basic requirements and save your phone resources. +{Fore.RESET} + +{COPYRIGHT} + """ +) +print("Press 'Any Key' to continue...") +input("") +clear() + +print(with_header(INFO_TEXT)) +ask_process_info_text() + +clear() + +print(with_header("Installing Mandatory requirements...")) +all_ = " ".join(MANDATORY_REQS) +system(f"pip install {all_}") + +clear() +print( + with_header( + f"\n{Fore.GREEN}# Moving toward Installing Apt-Packages{Fore.RESET}\n\n" + ) +) +print("---Enter---") +print(" - A = 'Ask Y/N for each'.") +print(" - I = 'Install all'") +print(" - S = 'Skip Apt installation.'") +print(" - E = Exit.\n") +ask_process_apt_install() + +clear() +print( + with_header( + f""" +{Fore.YELLOW}# Installing other non mandatory requirements. +(You can Install them, if you want command using them to work!){Fore.RESET} + +{'- '.join(list(OPT_PACKAGES.keys()))} + +Enter [ A = Ask for each, I = Install all, S = Skip, E = Exit]""" + ) +) +ask_and_wait_opt() + +print(f"\n{Fore.RED}#EXTRA Features...\n") +print(f"{Fore.YELLOW}* Do you want to get Ultroid Logs in Colors? [Y/N] ") +inp = input("").strip().lower() +if inp in ["yes", "y"]: + print(f"{Fore.GREEN}*Spoking the Magical Mantras*") + system("pip install coloredlogs") +else: + print("Skipped!") + +clear() +if not path.exists(".env"): + print(with_header("# Do you want to move toward creating .env file ? [y/N] ")) + ask_make_env() + +print(with_header(f"\n{Fore.GREEN}You are all Done! 🥳")) +sleep(0.2) +print(f"Use 'bash startup' to try running Ultroid.{Fore.RESET}") +sleep(0.5) +print( + "\nYou can head over to @UltroidSupportChat, if you get stuck somewhere, and need help." +) +sleep(0.5) +print("\nMade with ❤️ by @TeamUltroid...") + +system("pip3 uninstall -q colorama -y") diff --git a/resources/startup/locals.py b/resources/startup/locals.py new file mode 100644 index 0000000000000000000000000000000000000000..0d7efa9c0aa795daf6034d468aa1f303ae81794c --- /dev/null +++ b/resources/startup/locals.py @@ -0,0 +1,135 @@ +# /usr/bin/python3 +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# Please read the GNU Affero General Public License in +# . + +# Standalone file for facilitating local deploys. + +import os + +a = r""" + _ _ _ _ _ _ + | | | | | | (_) | | + | | | | | |_ _ __ ___ _ __| | + | | | | | __| '__/ _ \| |/ _ | + | |__| | | |_| | | (_) | | (_| | + \____/|_|\__|_| \___/|_|\__,_| +""" + + +def start(): + + clear_screen() + check_for_py() + + print(f"{a}\n\n") + print("Welcome to Ultroid, lets start setting up!\n\n") + print("Cloning the repository...\n\n") + os.system("rm -rf Ultroid") + os.system("git clone https://github.com/TeamUltroid/Ultroid") + print("\n\nDone") + os.chdir("Ultroid") + clear_screen() + print(a) + print("\n\nLet's start!\n") + + # generate session if needed. + sessionisneeded = input( + "Do you want to generate a new session, or have an old session string? [generate/skip]", + ) + if sessionisneeded == "generate": + gen_session() + elif sessionisneeded != "skip": + print( + 'Please choose "generate" to generate a session string, or "skip" to pass on.\n\nPlease run the script again!', + ) + exit(0) + + # start bleck megik + print("\n\nLets start entering the variables.\n\n") + varrs = [ + "API_ID", + "API_HASH", + "SESSION", + "REDIS_URI", + "REDIS_PASSWORD", + ] + all_done = "# Ultroid Environment Variables.\n# Do not delete this file.\n\n" + for i in varrs: + all_done += do_input(i) + clear_screen() + print(a) + print("\n\nHere are the things you've entered.\nKindly check.") + print(all_done) + isitdone = input("\n\nIs it all correct? [y/n]") + if isitdone == "y" or isitdone != "n": + # https://github.com/TeamUltroid/Ultroid/blob/31b9eb1f4f8059e0ae66adb74cb6e8174df12eac/resources/startup/locals.py#L35 + f = open(".env", "w") + f.write(all_done) + else: + print("Oh, let's redo these then.") + start() + clear_screen() + print("\nCongrats. All done!\nTime to start the bot!") + print("\nInstalling requirements... This might take a while...") + os.system("pip3 install --no-cache-dir -r requirements.txt") + os.system("pip3 install -r requirements.txt --break-system-packages") + ask = input( + "Enter 'yes/y' to Install other requirements, required for local deployment." + ) + if ask.lower().startswith("y"): + print("Started Installing...") + os.system( + "pip3 install --no-cache-dir -r resources/startup/optional-requirements.txt" + ) + else: + print("Skipped!") + clear_screen() + print(a) + print("\nStarting Ultroid...") + os.system("sh startup") + + +def do_input(var): + val = input(f"Enter your {var}: ") + return f"{var}={val}\n" + + +def clear_screen(): + # clear screen + _ = os.system("clear") if os.name == "posix" else os.system("cls") + + +def check_for_py(): + print( + "Please make sure you have python installed. \nGet it from http://python.org/\n\n", + ) + try: + ch = int( + input( + "Enter Choice:\n1. Continue, python is installed.\n2. Exit and install python.\n", + ), + ) + except BaseException: + print("Please run the script again, and enter the choice as a number!!") + exit(0) + if ch == 1: + pass + elif ch == 2: + print("Please install python and continue!") + exit(0) + else: + print("Weren't you taught how to read? Enter a choice!!") + return + + +def gen_session(): + print("\nProcessing...") + # https://github.com/TeamUltroid/Ultroid/main/resources/startup/locals.py#L35 + os.system("python3 resources/session/ssgen.py") + + +start() diff --git a/resources/startup/termux.sh b/resources/startup/termux.sh new file mode 100644 index 0000000000000000000000000000000000000000..4d608e46bd04bff8935875fae5b23664f182c718 --- /dev/null +++ b/resources/startup/termux.sh @@ -0,0 +1,19 @@ +printf "Updating System..\n\n" +pkg update -y +apt update +apt upgrade -y + +python_not_installed="$(python -c 'exit()')" + +# Install Python if n0t installed.. +if [ python_not_installed ] +then + printf "Installing Python..\nThis may take some long...\n" + pkg install python3 -y +fi + +printf "*Putting some magical effect...*" +pip install -q colorama + +printf "Running up Installation tool.\n" +python resources/startup/_termux.py diff --git a/sessiongen b/sessiongen new file mode 100644 index 0000000000000000000000000000000000000000..36e5ac4a39291d91ec1fee7732229bfe7fbd466a --- /dev/null +++ b/sessiongen @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in . + +bash resources/session/session.sh diff --git a/startup b/startup new file mode 100644 index 0000000000000000000000000000000000000000..43e76ab6702d4f9aaa87137abfffa846afe79ab5 --- /dev/null +++ b/startup @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Ultroid - UserBot +# Copyright (C) 2021-2026 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in . + + +echo " + ┏┳┓╋┏┓╋╋╋╋┏┓┏┓ + ┃┃┣┓┃┗┳┳┳━╋╋┛┃ + ┃┃┃┗┫┏┫┏┫╋┃┃╋┃ + ┗━┻━┻━┻┛┗━┻┻━┛ + + Visit @TheUltroid for updates!! + +" +if [ -f .env ] ; then set -o allexport; source .env; set +o allexport ; fi +if [ $SESSION1 ] ; then python3 multi_client.py ; else python3 -m pyUltroid ; fi diff --git a/strings/__init__.py b/strings/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..30e79706b4719699a8169b6c7f4d49c27249a5c4 --- /dev/null +++ b/strings/__init__.py @@ -0,0 +1,82 @@ +import os +import sys +from glob import glob +from typing import Any, Dict, List, Union + +from pyUltroid import * +from pyUltroid.fns.tools import translate + +try: + from yaml import safe_load +except ModuleNotFoundError: + from pyUltroid.fns.tools import safe_load + +ULTConfig.lang = udB.get_key("language") or os.getenv("LANGUAGE", "en") + +languages = {} +PATH = "strings/strings/{}.yml" + + +def load(file): + if not file.endswith(".yml"): + return + elif not os.path.exists(file): + file = PATH.format("en") + code = file.split("/")[-1].split("\\")[-1][:-4] + try: + languages[code] = safe_load( + open(file, encoding="UTF-8"), + ) + except Exception as er: + LOGS.info(f"Error in {file[:-4]} language file") + LOGS.exception(er) + + +load(PATH.format(ULTConfig.lang)) + + +def get_string(key: str, _res: bool = True) -> Any: + lang = ULTConfig.lang or "en" + try: + return languages[lang][key] + except KeyError: + try: + en_ = languages["en"][key] + tr = translate(en_, lang_tgt=lang).replace("\ N", "\n") + if en_.count("{}") != tr.count("{}"): + tr = en_ + if languages.get(lang): + languages[lang][key] = tr + else: + languages.update({lang: {key: tr}}) + return tr + except KeyError: + if not _res: + return + return f"Warning: could not load any string with the key `{key}`" + except TypeError: + pass + except Exception as er: + LOGS.exception(er) + if not _res: + return None + return languages["en"].get(key) or f"Failed to load language string '{key}'" + + +def get_help(key): + doc = get_string(f"help_{key}", _res=False) + if doc: + return get_string("cmda") + doc + + +def get_languages() -> Dict[str, Union[str, List[str]]]: + for file in glob("strings/strings/*yml"): + load(file) + return { + code: { + "name": languages[code]["name"], + "natively": languages[code]["natively"], + "authors": languages[code]["authors"], + } + for code in languages + } diff --git a/strings/strings/ar.yml b/strings/strings/ar.yml new file mode 100644 index 0000000000000000000000000000000000000000..15cb5d98697769a3c7ff75d13bbe58d663176503 --- /dev/null +++ b/strings/strings/ar.yml @@ -0,0 +1,599 @@ +name: Arabic +natively: العربية + +authors: + - klanrali + - memtma + +# commons +com_1: "`جـاري المعالجه...`" +com_2: "`جـاري البحث...`" +com_3: "`ادخال غير صحيح`" +com_4: "`ملف وسائط غير مدعوم..`" +com_5: "جاري التنزيل..." +com_6: "جاري الرفع..." +com_7: "`حدث خطأ.`" + +# user logs +userlogs_1: "رقم تعريف المجموعة الذي قمت بضبطه في سجل المنشن غير صحيح ، من فضلك قم بتصحيحه" +userlogs_2: "قم بإضافتي لمجوعة سجل المنشن لتحصل علي السجلات" +userlogs_3: "مغادرة المجموعة" +userlogs_4: "البوت المساعد لا يمكنه ارسال الرسائل في المجموعة.\nقم باضافته لتحصل علي السجلات.." +userlogs_5: "غادر `{}`" + +# chat actions +can_1: "#مستخدم_محظور انضم.\n\n**المستخدم** : {}\n**سبب الحظر**: {}\n\n`تم حظره.`" +can_2: "∆ #تغير_اسم_المستخدم\n\n@{} تم تغير اسم المستخدم الي @{}" +can_3: "∆ #تغير_اسم_المستخدم\n\n{} قام بحذف اسم المستخدم الخاص به. (@{})" +can_4: "∆ #تغير_اسم_المستخدم\n\n{}'s اسم المستخدم الجديد --> @{}" + +# help +help_1: "`{}` عـذرا ليس صالحآ يـوجد هنـاك خطأ!" +help_2: "عذرا لم يستجيب البوت .\nارسل امر اعاده تشغيل للأفضليه `{}restart`" +help_3: "`يرجى تشغيل وضع الاونلاين المباشر من خلال بوت فاذر` @Botfather." +help_4: "• اضافات" +help_5: "اضافات اخري •" +help_6: "••المحادثات الصوتية" +help_7: "اضافات انلاين••" +help_8: "⚙️ ادوات المالك" +help_9: "الاعدادات ⚙️" +help_10: "••اغلاق••" +help_11: "اسم الإضافة-{}\n\n✘ الاوامر المتاحة -\n\n" +help_12: "المكالمة الصوتية غير فعالة!" +help_13: "قم بارسال '{}setdb ADDONS True' لتحميل الاضافات الرسمية" + +# admintools +adm_1: "`لم استطع ان اتعرف عليه!`" +adm_2: "`حسنا.. اعتقد اني ليس لدي اي حقوق هنا!`" +adm_3: "`قم بإرسال الوقت ايضا..`" +pro_1: "`قم بالرد علي مستخدم لترقيته!`" +pro_2: "{} `الان مسؤول في مجموعة {} ولقبه {}.`" +de_1: "`قم بالرد علي مستخدم لازالة ترقيته!`" +de_2: "{} `لم يعد مسؤول في {}`" +ban_1: "`قم بالرد علي مستخدم او قم بوضع اسم المستخدم الخاص به لحظره!`" +ban_2: " `هاها، لا استطيع حظر مطوري 😂`" +ban_3: "`ليس لدي حقوق حظر المستخدمين.`" +ban_4: "{} **تم حظره بواسطة** {} **في** `{}`" +ban_5: "\n**السبب**: `{}`" +tban_1: "`قم بالرد علي احد او قم بارسال رقمه التعريفي...`" +tban_2: "`تم حظر` {} `بنجاح في {} لمدة {}`" +unban_1: "`قم بالرد على مستخدم او ارسل اسم مستخدم لإلغاء حظره!`" +unban_2: "`ليس لدي حقوق لإلغاء حظر المستخدم.`" +unban_3: "{} **تم الغاء حظره بواسطة** {} **في** `{}`" +kick_1: "`ليس لدي حقوق لطرد مستخدم.`" +kick_2: " `هاها, لا يمكنني طرد مطوري`😂" +kick_3: "`لا يمكنني طرده ابدا...`" +kick_4: "{} **تم طرده بواسطة** {} **في** `{}`" +pin_1: "`قم بالرد علي رسالة لتثبيتها!`" +pinned_1: "لم يتم العثور على رسالة مثبتة!" +pinned_2: "الرسالة المثبتة في هذه المجموعة هي [here]({})." +unpin_1: "اما ان تقوم بالرد على رسالة, او استخدم `{}unpin all`" +listpin_1: "لا يوجد رساله مثبتة في هذه المجموعة!" +purge_1: "`قم بالرد علي رسالة لبدأ الحذف من عندها.`" +purgeall_1: "`قم بالرد علي رسالة احدهم للحذف.`" +purgeall_2: "تم حذف جميع رسائل {} بنجاح" + +# autopic +autopic_1: "حسنا هيا اعطني بعض النصوص .." +autopic_2: "عذرا لايوجد نتائج للبحث لـ `{}`" +autopic_3: " حصلت على بعض من النتائج `{}` التي تتعلق في بحثك !\nجاري بدء تشغيل التلقائي !" + +# inline +inline_1: "[ألترويد للمساعدة](t.me/UltroidSupportChat)\n\n\n**قائمه المساعدة {}.\n\n +الأضافات ~ {}**" +inline_2: "[ألترويد للمساعدة](t.me/UltroidSupportChat)\n\n**قائمه المساعدة {}.\n\n +الاضافات ~ {}**" +inline_3: "[ألترويد للمساعدة](t.me/UltroidSupportChat)\n\n\n**قائمه المساعدة {}.\n\n +الاضافات ~ {}\n\n +اذهب واضف `الأضافات` متنوع مع القيمة.**" +inline_4: "** البـوت {}\n\n القـائمـه الرئـيسيـة \n\n الأضـافات ~ {}\n الأضـافات ~ {}\n جـميع التعلـيقات ~ {}**" +inline_5: "**┇ تـــم غــلـق الــقــائـــمــة ✅**" + +# tagnotif +tagnot_1: "{} ┇قــام بــعــمــل تــــاك لــك ⤴️ فــي {}\n\n```{}```\n\n[📨Message📨]({})" +tagnot_2: "{} ┇قــام بــعــمــل تــــاك لــك ⤴️ فــي {}\n\n [📨Message📨]({})" + +# whisper +wspr_1: "@{} ┇رســالــة ســريــة لــك 📩.\n┇قــم بــحــذف رســالــتــك بــعــد إن تــم قــرائــتــهــا ❗️.\n┇وإلا لــن يــتــم تــحــديــث الــرســالــة التــالــيــة ⚠️." +wspr_2: "┇تــم حــذف الــرســالــة ✅" +wspr_3: "اضف رقم تعريفي او اسم مستخدم ايضا" +wspr_4: "المستخدم {} غير موجود\n ابحث مجددا" +wspr_5: "لا يمكنك فعل هذا" + +# afk +afk_1: "`┇ لــم يــعــد نــشــط ! 📵\n\n +Was away for ~ {}`" +afk_2: "#AFK\nSet AFK mode to False.\nWas AFK for {}`" +afk_3: "┇لــســتُ نــشــطــاً حــالــيــاً 💤.\nLast seen {} ago.`\n\n**Reason:** `{}`" +afk_4: "`┇لــســتُ نــشــطــاً حــالــيــاً 💤.\n\nLast seen {} ago.`" +afk_5: "`┇جــاري الــغــاء النــشــاط 💤.`\n\n**Reason:** `{}`" +afk_6: "┇جــاري الــغــاء النــشــاط 💤." + +# bot +alive_1: "**🤖┇بــوت مــســتــخــدم ULTROID 🔱...**\n\n +**{}**\n\n +┏━━━━━━━━━━━━━━━━━━━━━\n +┣ **┇المــالــك ⬺** - `{}`\n +┣ **┇الإصــدار ⬺** - `{}`\n +┣ **┇مــدة الــتــشــغــيــل ⬺** - `{}`\n +┣ **┇بــايــثــون ⬺** - `{}`\n +┣ **┇تــلــيــثــون ⬺** - `{}`\n +┣ **┇الــفــرع ⬺** - `{}`\n +┗━━━━━━━━━━━━━━━━━━━━━" +log: "**┇هــيــروكــو** ســجــلات ULTROID 🔱 .\nتــلــصــق [هــنــا ]({}) أيــضــاً!" +ping: "**Pong !!** `{}ms`\n**┇وقـــت الــتــشــغــيــل ** - `{}`" +usage: "**⚙️ Dyno إســتــعــمــال الــ ⚙️**:\n\n + -> `Dyno إســتــعــمــال الـ for` **{}**:\n + • `{}`**h** `{}`**m** +**|** [`{}`**%**]\n\n + -> `┇حــصــة الــ Dyno المـتـبـقـيـة هـذا الـشـهـر`:\n + • `{}`**h** `{}`**m** +**|** [`{}`**%**]\n\n +**┇المــســاحــة الــكــلــيــة لـلـقــرص - {}\n\n** +┇الــمــســتــخــدم- {} الــخــالــيــة - {}\n\n +**📊┇الــبــيــانــات الــمــســتــخــدمــة📊\n\nUpload- {}\nDown- {}\n\n** +**CPU- {}%\nRAM- {}%\nDISK- {}%**" +shutdown: "┇الـى الـلـقــاء 👋 {}.\n`┇يــتــم الإطــفــاء ...🚫`" + +# broadcast +bd_1: "`┇يــتــم الاضــافــة الــى قــاعــدة الــبــيــانــات ♻️...`" +bd_2: "`┇يــتــم الــمــحــاولــة لإضــافــة كــل قــنــوات الــمــشــرف الــى قــاعــدة الــبــيــانــات ♻️...`" +bd_3: "**┇تــم ✅.**\n┇الــمــحــادثــات المــوجــودة فــي قــاعــدة الــبــيــانــات بــالــفــعــل 📍: {}\n┇الــمُــضــافــة حــديــثــاً 📅 : {}" +bd_4: "┇تــم إضــافــة الــقــنــوات ! ✅" +bd_5: "`┇تــم الإضــافــة الــى قــاعــدة الــبــيــانــات ! ✅`" +bd_6: "`┇الــقــنــاة مــوجــودة فــي قــاعــدة الــبــيــانــات بــالــفــعــل ! 📍`" + +# carbon +carbon_1: "┇يــتــم الــمــعــالــجــة ♻️" +carbon_2: "┇تــم عــمــل الــكــاربــون بــواســطــة [{}](tg://user?id={})" + +# chats +chats_1: "`┇يــتــم الــمــعــالــجــة ... ♻️`" +chats_2: "`┇لا يــمــكــن حــذف هــذه الــمــحــادثــة ⛔️`" +chats_3: "`┇انــا لــســت مــشــرف ❗️`" +chats_4: "┇ #تــم_الحــذف\n┇تــم حــذف {}" +chats_5: " ┇تــم صــنــع مــجــمــوعــتــك ✅[{}]({}) " +chats_6: "┇إنــضــم ⚜️ @TeamUltroid" + +# converter +cvt_1: "┇قــم بــالــرد عــلــى أي مــيــديــا / مــلــف نــصــي ❕." +cvt_2: "┇اكــتــب إســم وإمــتــداد الــمــلــف 📝" +cvt_3: "`┇يــتــم الــمــعــالــجــة ♻️...`" +cvt_4: "┇قــم بــالــرد عــلــى أي مــيــديــا❕" + +# core +core_1: "┇تــم الــعــثــور عــلــى وِحــدة ✅" +core_2: "{}.py ┇إســتــخــدم .Paste لــعــمــلــيــة الــلــصــق ❕" +core_3: "┇إبــحــث مــرة أخــرى ؟ ⚠️" +core_4: "┇تــم الــعــثــور عــلــى وِحــدة {} " +core_5: "{}.py ┇إســتــخــدم .Paste لــعــمــلــيــة الــلــصــق ❕" +core_6: "┇لــم يــتــم الــعــثــور عــلــى الــوحــدة {}.py " +core_7: "┇لا تــوجــد هــكــذا وحــدة ❌" +core_8: "┇لا تــوجــد وحــدة بــهــذا الإســم {}.py" +core_9: "`┇اكــتــب اســم الإضــافــة الــمُــراد إزالــتــهــا 🔰`" +core_10: "**┇تــمــت ازالــة** `{}` ** بــنــجــاح ✅**" +core_11: "**┇لا يــمــكــنــك إزالــة الإضــافــات الــرســمــيــة 🚫**" +core_12: "**┇لا تــوجــد إضــافــة بــإســم ** `{}`" +core_13: "`┇اكــتــب اســم الإضــافــة الــمُــراد إلــغــاء تــثــبــيــتــهــا 💢`" +core_14: "**┇تــم إلــغــاء تــثــبــيــت** `{}` **بــنــجــاح ✅**" +core_15: "**┇لا يــمــكــنــك إلــغــاء تــثــبــيــت الإضــافــات الــرســمــيــة 🚫**" +core_16: "`┇اكــتــب اســم الإضــافــة الــمُــراد إضــافــتــهــا 🔰`" +core_17: "**┇تــم إضافة** `{}` بــنــجــاح ✅" +core_18: "**┇تــعــذر إضــافــة ** `{}` **┇بــســبــب الــخــطــأ ⚠️**\n`{}`" + +# fedutils +sf_1: "بــدء الــ a Mass-FedBan..." +sf_2: "`عـذرآ لـم يتم تحـديد أي مسـتخدم 🚹!`" +sf_3: "عذرا لايمگـنك حـظر مطـوري أنـت جـديـد ✖️" +sf_4: "`- يـبـدو أن Rose لا تـسـتجـيـب ، أو أن المـكون الإضـافي يعـمل بشـكل سـيء ⚠️`" +sf_5: "يـمكنـك فقـط اسـتخـدام أوامـر التعليـقات مـرة واحـدة كـل 5 دقـائـق 🔄" +sf_6: "- حـاول مـرة أخـرى بـعد 5 دقائـق 5️⃣." +sf_7: "- تـعـذر جـمـع FedAdminList. 🚫 يرجـى أعاده الـمحاولة 🔄 ({} / 3) ..." +sf_8: "يـوجد هنـاك خطأ ⛔" +sf_9: "- تـعـذر جـمـع FedAdminList 🚫." +sf_10: "- FBaning in {} ألفـدراليـه 🚸." +sf_11: "- عذرا معـرف المـجموعـه المحدد غيـر صحيح ❌." +sf_12: "{} مسـتبعد ‼️." +sf_13: "خـطـأ في إزالـة مـلف FedAdmin ✖️.\n{}" +sf_14: "SuperFBan. أكتـمل .\nأجمـالي الاخطاء - {}.\nالـمسـتبـعد - {}.\n المتـأثر {} feds.\n#TB" +sf_15: "بـدء الــ a Mass-UnFedBan..." +sf_16: "SuperFBan. أكتـمل .\nأجمـالي الاخطاء - {}.\nالـمسـتبـعد - {}.\n المتـأثر {} feds.\n#TB" +sf_17: "`اعطنـي معـرف شخـص , او الـرد على الشخـص للـتحقق مـن أخطائـه.`" +sf_18: "قـائمه المحـظورين ⛔ {} تـم حظـره فـي .\n\nبأستـخدام Ultroid ⚠️." +sf_19: "خـطأ 🚫\n فـك الحـظر مـن @MissRose_Bot `ثـم حاول مجـددآ !" +sf_20: "`جـاري اسـتـخراج المعـلومـات 🚻...`" +sf_21: "\n\nFedInfo المستخرج بواسطة Ultroid" + +# gdrive +gdrive_1: "`┇انــت مــصــرح لــك بــالــفــعــل مــع Google Drive ✅`" +gdrive_2: "┇أذهــب [هــنــا](https://console.developers.google.com/flows/enableapi?apiid=drive) واحــصــل عــلــى GDRIVE_CLIENT_ID و GDRIVE_CLIENT_SECRET الــخــاص بــك\n\n" +gdrive_3: "┇ايــدي الــعــمــيــل خــاطــئ ⚠️" +gdrive_4: "`┇حــدث خــطــأ مــا ❗️ارســل `/auth` مــجــدداً . \n┇اذا حــدث نــفــس الــخــطــأ مــجدداً ؟ راســل `@TeamUltroid" +gdrive_5: "`┇نجحت العملية ! ✅\n┇انــت جــاهــز لاســتــخــدام Google Drive مــع Ultroid Userbot 🔱.`" +gdrive_6: "`┇اذهب الى `{}` وارسل ``/auth`." +gdrive_7: "**┇تــم رفــع الــمــلــف عــلــى G-Drive بــنــجــاح ✅ :**\n\n[{}]({})" + +# pmpermit +pmperm_1: "🤖┇مــن فــضــلــك , انــتــظــر الإجــابــة , او ســيــتــم حــظــرك والابــلاغ عــنــك كــ ازعــاج 💙" +pmperm_2: "🤖┇انــت تــســبــب ازعــاج لــ مــحــادثــة ســيــدي الــخــاصــة , وهــذا شــيء لا يــعــجــبــنــي ⚠️" +pmperm_3: "┇قــم بــالــرد عــلــى رســالــة شــخــص مــا لــتــجــربــة هــذا الامــر فــي الــمــحــادثــة الــخــاصــة ♥️" + +# updater +upd_1: "`┇جــاري الــتــحــقــق مــن وجــود تــحــديــثــات , انــتــظر رجــاءً ... ♥️`" +upd_2: "`┇أوبــس .. الــمــحــدث غــيــر قــادر عــلــى الاســتــكــمــال بــســبــب بــعــض الــمــشــاكــل.`\n\n**┇تــتــبــع الــســجــل:**\n" +upd_3: "**┇يــتــوفــر تــحــديــث جــديــد لــ [[{}]]({}/tree/{}):\n\n**┇ســجــل الــتــغــيــيــرات \n\n{}" +upd_4: "`┇حــجــم ســجــل الــتــغــيــيــرات كــبــيــر جــداً , اعــرض الــمــلــف لــرؤيــتــه `" +upd_6: "\n`┇بــوتــك` **بــأخــر تــحــديــث** `بـــ` **[[{}]]({}/tree/{})**\n" +upd_5: "اضغط علي الزر بالاسفل للتحديث." +upd_7: "`جار التحديث السريع...`" + +# upload download +udl_1: "`┇قــم بــالــرد عــلــى الــمــلــف / الــمــيــديــا الــمــراد تــحــمــيــلــهــا ♻️`" +udl_2: "┇تــم الــتــنــزيــل بــنــجــاح ✅\nلـــ\n`{}`\nفــي `{}`" +udl_3: "`┇ضــع مــســار مــحــدد لــلــمــلــف ⚠️`" +udl_4: "`جار محاولة التنزيل...`" +udl_5: "`لقد نسيت ان تعطيني رابط :(`" +udl_6: "`هذا المسار فارغ.`" +udl_7: "`لا يمكنك فعل هذا!`" + +# words +wrd_1: "**┇الــكــلــمــة ** - `{}`\n\n**┇مــعــنــاهــا** - \n" +wrd_2: "**┇الــكــلــمــة ** - `{}`\n\n**┇مــرادفــهــا** - \n" +wrd_3: "**┇الــكــلــمــة ** - `{}`\n\n**┇مــعــاكــســهــا ** - \n" +wrd_4: "`قم بإعطاء كلمة لمعرفة معناها..`" +wrd_5: "مرادفات" +wrd_6: "مضادات" +wrd_7: "لم يتم ايجاد مرادفات!!\n\n`{}`" +wrd_8: "لم يتم إيجاد مضادات!!\n\n`{}`" + +# ---------------------------------------------------# + +# assisant +ast_1: "┇اخــتــر أي API تــريــد اعــداده ♻️" +ast_2: "**remove.bg API**\n┇ادخــل مــفــتــاح الــ API الــخــاص بــك مــن remove.bg.\n\n┇اســتــخــدم /cancel لأنــهــاء الــعــمــلــيــة ⚠️" +ast_3: "┇مــرحــبــاً {}.↴┇مــن فــضــلــك تــصــفــح مــن خــلال الــخــيــارات " + +# Akinator +aki_1: "تم إنهاء اللعبة...." +aki_2: "بدأ اللعبة" +aki_3: "انتهي الوقت !" + +ascii_1: "`قم بالرد علي صورة.`" +ascii_2: "`جار التحويل ل html...`" +audiotools_1: "قم بالرد علي ملف صوتي او فيديو.." +audiotools_2: "تم التنزيل بنجاح، جار التحويل الي رسالة صوتية" +audiotools_3: "`ارسل الوقت بالصيغة المحددة للقص`" +audiotools_4: "`بيانات غير صحيحة`" +audiotools_5: "`جار محاولة التنزيل...`" +audiotools_6: "`مدة القص غير صحيحة`" +audiotools_7: "تم قص الملف الصوتي من `{}` الي `{}`" +audiotools_8: "قم بالرد علي فيديو..`" +audiotools_9: "`لم يتم ايجاد صوت...`" +audiotools_10: "`تم استخراج الصوت من الفيديو...`" + +asstcmd_1: "`استخدم هذا الامر مع رسالة وكلمة لاستخدم امر.`" +asstcmd_2: "`ارسل الامر الذي تريد حذفه.`" +asstcmd_3: "تم حذف الامر: `/{}`." +asstcmd_4: "تم حفظ الامر : `/{}`." +asstcmd_5: "لم يتم إيجاد اوامر" +asstcmd_6: "**كل أوامر مساعد ألترويد**\n\n" +act_1: "تم تفعيل خاصية التصحيح التلقائي" +act_2: "تم تعطيل خاصية التصحيح التلقائي" +autopic_4: "انت لم تكن تستخدم خاصية الصورة التلقائية !!" +autopic_5: "تم ايقاف الصورة التلقائية!" +inline_6: "**قائمة مساعدة المحادثة الصوتية ل {}**\n**الاوامر المتاحة:** `{}`\n\n@TeamUltroid" +inline_7: "قم بارسال .help {} لتحصل علي قائمة الأوامر." +inline_8: "اضغط علي الزر بالاسفل للتحديث!" +inline_9: "انت بالفعل علي اخر اصدار" + +bot_1: "مرحبا، انا علي قيد الحياة." +bot_2: "احصائيات" +bot_3: "Repo" +bot_4: "مجموعة المساعدة" +bot_5: "• `جار إعادة التشغيل...`" +usage_simple: "**مساحة الذاكرة**: `{}`\n**المستخدم**: `{}`\n**المتاح**: `{}`\n\n**📊 البيانات المستخدمة 📊**\n**رفع**: `{}`\n**تنزيل**: `{}`\n\n**CPU**: `{}%`\n**RAM**: `{}%`\n**الذاكرة**: `{}%`" +blk_1: "`قم بإعطاء كلمة لحظرها..`" +blk_2: "تم حظر كلمة : `{}` هنا." +blk_3: "`قم بإعطاء كلمة لالغاء حظرها..`" +blk_4: "تم الغاء حظر كلمة : `{}` هنا." +blk_5: "الكلمات المحظورة في هذه المجموعة هي :\n\n" +blk_6: "لا يوجد كلمات محظورة هنا" +bd_7: "تم الحذف من قاعدة البيانات" +bd_8: "`جار الحذف...`" +bd_9: "تم بالفعل حذف القناة من قاعدة البيانات. " +calc_1: "• الآلة الحاسبة الخصة بألترويد •" +calc_2: "فتح الآلة الحاسبة مجددا" +cha_1: "قناة غير صحيحة" +cha_2: "تم اضافة المصدر بنجاح" +cha_3: "تم اضافة قناة المصدر بالفعل" +cha_4: "تم حذف قاعدة بيانات المصدر." +cha_5: "تم حذف المصد من قاعدة البيانات" +cha_6: "تم بالفعل حذف القناة من قاعدة البيانات." +cha_7: "القنوات المقصودة في قاعدة البيانات:" +cha_8: "قنوات المصدر في قاعدة البيانات:" +carbon_3: "`قم بإعطاء لون معين لعمل الصورة...`" +chab_1: "قم بالرد علي مستخدم او قم بإعطاء اسم مستخدم لتفعيل بوت الذكاء الاصطناعي له!" +chab_2: "`لم يتم تفعيل الذكاء الاصطناعي لاي مستخدم.`" +chab_3: "**جميع المستخدمين الذين تم تفعيل الذكاء الاصطناعي لهم فيي هذه المحادثة :**\n\n" +clan_1: "تم تفعيل حذف رسائل الانضمام والمغادرة في هذه المجموعة" +clan_2: "تم تعطيل حذف رسائل الانضمام والمغادرة في هذه المجموعة" +clan_3: "`رقم تعريف غير صحيح`" +clan_4: "`لم يتم إضافة اي محادثات`" +clan_5: "حذف رسائل الانضمام والمغادرة مفعلة في هذه المجموعة." +cvt_5: "هذه الملف ليس به صورة مصغرة.`" +cvt_6: "تم اضافة [هذه]({}) كصورة مصغرة" +cvt_7: "`قم بالرد علي ملف يمكن قراءته`" +cvt_8: "`لا يمكن قراءة هذا الملف.`" +eod_1: "🎊 **حدث اليوم**\n\n" +eod_2: "• **حدث يوم {}/2022**\n\n" +devs_1: "`يم يتم إعطاء امر`" +devs_2: "`قم بإعطاء امر بايثون`" +devs_3: "`قم بإعطاء كود بلغة C++..`" +dm_1: "`قم باعطاء اسم مسخدم او رقم تعريفي للارسال له.`" +dm_2: "`قم بإعطاء نص او قم بالرد علي نص لارساله`" +dm_3: "⚜️ تم ارسال الرسالة! ⚜️" +dm_4: "خطأ : {}\nاقرأ الاستخدام : `{}مساعدة اخري`" +dm_5: "`قم بالتحقق من الرسائل الشخصية.`" +dm_6: "`لم يتم العثور علي رسالة لإرسالها :(`" +echo_1: "**تم تفعيل صدي الصوت لهذا المستخدم:**\n\n" +fka_1: "جار بدأ الفعل المزيف لمدة {} ثانية." +sf_22: "لم يتم تحديد المستخدم." +sf_23: "**خطأ**\n `قم بإلغاء حظر` @MissRose_Bot `وحاول مجددا!" + +antiflood_1: "`تم تعطيل مضاد الرسائل الكثيرة`" +antiflood_2: "`لا يوجد حد لعدد الرسائل في هذه المجموعة.`" +antiflood_3: "`تم كتمك.`" +antiflood_4: "`تم تحديد عدد رسائل الكتم ل {} في هذه المجموعة.`" +antiflood_5: "`حد عدد الرسائل في هذه المجموعة هو {}.`" + +flr_1: "`استخدم هذا الامر مع كلمة لاستخدامها كفلتر للرد...`" +flr_2: "الفلترات الموجودة في هذه المجموعة\n\n" +flr_3: "`حدد الفلتر المراد حذفه..`" +flr_4: "تم حفظ الفلتر `{}`." +flr_5: "تم حذف الفلتر `{}`." +flr_6: "لا يوجد فلترات هنا" +fgn_1: "**الخطوط المتاحة**\n\n" + +fsub_1: "حدد القناة التي تريد ان يشترك فيها المستخدم !" +fsub_2: "ارسل اسم مستخدم القناة او رقمها التعريفي" +fsub_3: "لم يتم تفعيل الاشتراك الاجباري في هذه المجموعة !" +fsub_4: "اشترك في القناة" +fsub_5: "الغاء كتمي" +fsub_6: "من فضلك اشترك بتلك القناة !\nثم اضغط علي الزر !" +fsub_7: "هذه الرسالة ليست لك" +fsub_8: "شكرا لك علي انضمامكم ! " + +ex_1: "`قم بالرد علي رسالة..`" +gas_1: "قم بإرسال رابط مباشر!" +grt_1: "`تم حفظ رسالة الترحيب`" +grt_2: "من فضلك استخدم هذا الامر في المجموعات وليس في الرسائل الخاصة!" +grt_3: "`قم بالرد علي الرسالة التي تريد تعيينها كرسالة ترحيب`" +grt_4: "`لم يتم تعيين رسالة ترحيب!`" +grt_5: "`تم حذف رسالة الترحيب`" +grt_6: "`لم يتم تعيين رسالة وداع!`" +grt_7: "`قم بالرد علي الرسالة التي تريد تعيينها كرسالة وداع`" +glitch_1: "`من فضلك انتظر...`" +mdi_1: "معلومات مشروحة اكثر" + +nightm_1: "قم بكتابة الوقت بالصيغة الصحيحة" +nightm_2: "تم تعيين الوقت بنجاح" +nightm_3: "تم تفعيل الوضع الليلي في هذه المجموعة" +nightm_4: "تم تعطيل الوضع الليلي في هذه المجموعة" +nightm_5: "حدث خطأ" + +schdl_1: "`تم جدولة الرسالة بنجاح`" +schdl_2: "`صيغة غير صحيحة`" + +srch_1: "`ابحث عن من؟ قم بتحديد اسم مستخدم!!`" +srch_2: "`لم يتم العثور علي مستخدم بهذا الاسم...`" +srch_3: "`لم يتم العثور علي الاغنية...`" +snip_1: "قم بتحديد كلمة لتعيين رد لها وقم بالرد علي رسالة." +snip_2: "حدد الكلمة التي تريد حذفها..." +snip_3: "لا يوجد كلمات بردود معينة هنا." +spcltool_1: "قم بالرد علي ملف صوتي او فيديو" +spcltool_2: "`تم.. الان قم بالرد علي فيديو لاضافة هذا الصوت له`" +spcltool_3: "قم بالرد علي فيديو" +spcltool_4: "`اولا قم بالرد علي ملف صوتي ب .raw`" +spcltool_5: "تم التنزيل بنجاح, جار اضافة الصوت للفيديو" +spcltool_6: "`اكتب التاريخ بهذه الصيغة يوم/شهر/سنة`" +spcltool_7: "`عيد ميلاد سعيد 🎉🎊`" +spcltool_8: "عن ماذا تريد ان تبحث؟.." +spcltool_9: "لم يتم العثور علي اي شئ" +sudo_1: "`قم بالرد علي رسالة او اضف رقمها التعريفي/اسم المستخدم.`" +sudo_2: "لا يمكن إضافة نفسك كمستخدم نظير..." +sudo_3: "`لم يتم تعيين اي مستخدم نظير ...`" +sudo_4: "`لا يمكن تعيين بوت كمستخدم نظير.`" +unspl_1: "لا يوجد نتائج !" + +# VCTools +vct_1: "`تم بدأ المحادثة الصوتية...`" +vct_2: "**تم تغيير عنوان المحادثه الصوتية ل** `{}` بنجاح" +vct_3: "`جار دعوة الاعضاء للمشاركة في المحادثة الصوتية...`" +vct_4: "`تم ايقاف المحادثه الصوتية...`" +vct_5: "`تم دعوة {} مستخدمين`" +vct_6: "لم يتم العثور علي مدخلات!" + +prof_1: "`تم تشغيل خاصية حظر الكلمات الخارجة لهذه المجموعة!`" +prof_2: "`تم ايقاف خاصية حظر الكلمات الخارجة لهذه المجموعة!`" + +notes_1: "`استخدم هذا الامر بالرد علي رسالة مع تحديد اسم.`" +notes_2: "تم حفظ الملاحظة : `#{}`." +notes_3: "`حدد اسم الملاحظة التي تريد حذفها.`" +notes_4: "الملاحظات الموجودة في هذه المجموعة هي\n\n" +notes_5: "لا يوجد ملاحظات هنا" + +sts_1: "`لم تكتب اي شئ, ستذهب وايفو بعيدا.`" +sts_2: "`قم بالرد علي ملصق متحرك...`" +sts_3: "**من فضلك اختر من {} **" +sts_4: "`قم بالرد علي ملصق غير متحرك.`" +sts_5: "**تم سرقة مجموعة الملصقات بنجاح**.\n**المجموعة التي تم سرقتها:** [link]({})" +sts_6: "`قم بالرد علي رسالة/ملف وسائط...`" +sts_7: "`تم إضافة الملصق لمجموعة مختلفة !\nمجموعة الملصقات هذه تم انشاءها مؤخرا!\nيمكنك إيجاد مجموعة الملصقات` [هنا](t.me/addstickers/{})" +sts_8: "`فشل اضافة الملصق, استخدم بوت` @Stickers `لاضافة الملصق يدويا.`" +sts_9: "`اوه ملصق متحرك 👀...`" +sts_10: "`قم بالرد علي ملف وسائط...`" +sts_11: "جار سرقة الملصق..." +sts_12: "`تم سرقة الملصق!`\n`Emoji` : {}\n`مجموعة الملصقات` [هنا](t.me/addstickers/{})" +sts_13: "`جار تغيير مجموعة الملصقات {} بسبب المساحة الغير كافية`" + +whs_1: "<• قم بالرد علي ملف صوتي •>" +whs_2: "`جار محاولة التعرف على الاغنيه....`" +whs_3: "فشل التعرف علي الاغنية :(" +whs_4: "**تم التعرف علي الاغنية!**\nاسمها: __{}__" +wbl_1: "`حدد اسم الملف من فضلك..`" +writer_1: "`قم باعطاء نص ايضا`" + +wbs_1: "`قم بإرسال رابط من فضلك!`" +wbs_2: "رابط غير صحيح!" +wbs_3: "**تم انشاء صورة الرابط**\n**الرابط**: {}" + +# YouTube +youtube_1: "قم باعطاء رابط (يوتيوب) مباشر ليتم التنزيل منه!" +youtube_2: "`قم باعطاء رابط ملف صوتي مباشر ليتم التنزيل منه`" +youtube_3: "قم باعطاء رابط (يوتيوب) مباشر ليتم التنزيل منه!" +youtube_4: "`قم باعطاء رابط فيديو مباشر ليتم التنزيل منه`" +youtube_5: "قم بإعطاء امر بحث (يوتيوب) ليتم تنزيل ملف الصوت منه!" +youtube_6: "`جار تنزيل الاغنية...`" +youtube_7: "قم بإعطاء امر بحث (يوتيوب) ليتم تنزيل الفيديو منه!" +youtube_8: "`جار تنزيل فيديو الاغنية...`" + +zip_1: "قم بالرد علي ملف." +zip_2: "اولا اضف كل الملفات عن طريق `{}addzip` ثم قم بعمل zip لضغط كل الملفات في وقت واحد." +zip_3: "`قم بالرد فقط علي ملف zip`" + +fsh_1: "**قائمة الملفات المخزنة.:**" +fsh_2: "**تم تخزين الملف!**\n\n**رابط يمكن مشاركته:** {}" +fsh_3: "`قم بالرد علي رسالة لعمل رابط يمكن مشاركته!`" +fsh_4: "**لا يوجد ملفات مخزنة!**" + +instu_1: "بحث عن تطبيق. ادخل اسم التطبيق!" +instu_2: "ما تم البحث عنه مؤخراً.." +instu_3: "ادخل ما تريد البحث عنه" +instu_4: "تم بنجاح" + +# games +games_1: "اختر اللعبة 🎮" +games_2: "اختر مجال الاسئلة!" +games_3: "اختر مستوي صعوبة الاسئلة" +games_4: "اختر عدد الاسئله.." +games_5: "اختر الوقت بين الأسئلة..." +games_6: "**• سيبدأ الاختبار في ٥ ثواني.** \n**• المستوي :** {}\n**• السؤال :** {}" + +# callback stuff +clst_1: "`جار بناء البوت، من فضل انتظر حتي يكتمل البناء.`" +clst_2: "`تم التحديث بنجاح!\nجار اعادة التشغيل, من فضلك انتظر...`" +clst_3: "■ جار الحصول علي سجل التغييرات..." +clst_4: "عفوا حدث خطأ..." +clst_5: "تم!" + +# pmbot +pmbot_1: "**يجب ان تنضم للمجموعة/المجموعات بالاسفل لتقوم بمحادثة سيدي!\n\n**" +pmbot_2: "من فضلك قم بالرد علي احدهم لتقوم بحظره." +pmbot_3: "هذا المستخدم محظور بالفعل!" +pmbot_4: "`وداعا! لقد تم حظرك.`\n**كل الرسائل التي سترسلها الان لن تصل.**" +pmbot_5: "من فضلك قم بالرد علي احدهم لالغاء حظره." +pmbot_6: "هذ المستخدم لم يتم حظره ابدا!" +pmbot_7: "`مبروك! تم الغاء حظرك.`" + +# vcbot +vcbot_1: "`تم مغادرة المحادثة الصوتية.`" +vcbot_2: "**حدث خطأ:**\n{}" +vcbot_3: "• تم تغيير مستوي الصوت ل `{}%` •" +vcbot_4: "`قم بتحديد مستوي الصوت بين 1 و 200!`" +vcbot_5: "`جار اعادة الانضمام للمحادثة الصوتية.`" +vcbot_6: "انت لم تتصل بمحادثة صوتية!" +vcbot_7: "`من فضلك انتظر... سيستغرق هذا بعض الوقت.`" +vcbot_8: "ارسل قائمة تشغيل يوتيوب فقط" +vcbot_9: "أتمازحني?\nماذا تريدني أن اشغل؟" +vcbot_10: "تم حذف المجموعة من قائمة المصرح لهم!" +vcbot_11: "`جار الحصول على قائمة مستخدمي بوت المحادثة الصوتية...`" +vcbot_12: "`تم كتم الصوت في هذه المجموعة.`" +vcbot_13: "`تم متابعة التشغيل في هذه المجموعة.`" +vcbot_14: "`تم ايقاف التشغيل مؤقتا في هذه المجموعة.`" +vcbot_15: "من فضلك قم بتحديد اسم اغنية او قم بالرد علي ملف فيديو !" +vcbot_16: "هذه المجموعة ليست في قائمة المصرح لهم..." +vcbot_17: "`قم بالرد علي رسالة مستخدم او قم بارسال رقمها التعريفي/اسم المستخدم...`" +vcbot_18: "• قائمة المصرح لهم فارغة.." +vcbot_19: "تم بالفعل التصريح لهذه المجموعة!" +vcbot_20: "`جاري التنزيل والتحويل...`" +vcbot_21: "• لا يوجد شيء في قائمة الانتظار!" +vcbot_22: "`تم حذف كل قائمة الانتظار`" + +help_echo: "• `{i} addecho <الرد على أي شخص>`\n ابدأ رسالة صدى تلقائي للمستخدم المردل.\n\n• `{i} remecho <الرد على أي شخص>`\n أطفئه\n\n• `{i} listecho <الرد على أي شخص>`\n للحصول على قائمة." +help_mediatools: "-\n\n• `{i} mediaInfo <الرد على الوسائط>`\n للحصول على معلومات حول هذا الموضوع.\n\n• `{i} تدوير <درجة/زاوية> <الرد على الوسائط>` `\n تدوير أي فيديو/صور/وسائط ..\n ملاحظة: بالنسبة للفيديو ، يجب أن تكون زاوية التسعينيات" +help_mute: "-\n\n• `{i} كتم <الرد على msg/ user id>`\n كتم المستخدم في الدردشة الحالية.\n\n• `{i} unmute <الرد على msg/ user id>`\n Unmuction User في الدردشة الحالية.\n\n• `{i} dmute <الرد على msg/ user id>`\n كتم المستخدم في الدردشة الحالية عن طريق حذف msgs.\n\n• `{i} unding ` `\n Unmute dmated المستخدم في الدردشة الحالية.\n\n• `{i} tmute