Full Code of mxrch/GHunt for AI

master e8b0669cabb4 cached
91 files
599.5 KB
145.6k tokens
474 symbols
1 requests
Download .txt
Showing preview only (629K chars total). Download the full file or copy to clipboard to get everything.
Repository: mxrch/GHunt
Branch: master
Commit: e8b0669cabb4
Files: 91
Total size: 599.5 KB

Directory structure:
gitextract_eegatfp6/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   └── workflows/
│       └── sponsors.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── examples/
│   ├── email_registered.py
│   └── get_people_name.py
├── ghunt/
│   ├── __init__.py
│   ├── apis/
│   │   ├── __init__.py
│   │   ├── accounts.py
│   │   ├── calendar.py
│   │   ├── clientauthconfig.py
│   │   ├── digitalassetslinks.py
│   │   ├── drive.py
│   │   ├── fireconsolepa.py
│   │   ├── geolocation.py
│   │   ├── identitytoolkit.py
│   │   ├── mobilesdk.py
│   │   ├── peoplepa.py
│   │   ├── playgames.py
│   │   ├── playgateway.py
│   │   └── vision.py
│   ├── cli.py
│   ├── config.py
│   ├── errors.py
│   ├── ghunt.py
│   ├── globals.py
│   ├── helpers/
│   │   ├── __init__.py
│   │   ├── auth.py
│   │   ├── banner.py
│   │   ├── calendar.py
│   │   ├── drive.py
│   │   ├── gcp.py
│   │   ├── gmail.py
│   │   ├── gmaps.py
│   │   ├── ia.py
│   │   ├── iam.py
│   │   ├── knowledge.py
│   │   ├── listener.py
│   │   ├── playgames.py
│   │   ├── playstore.py
│   │   └── utils.py
│   ├── knowledge/
│   │   ├── __init__.py
│   │   ├── drive.py
│   │   ├── iam.py
│   │   ├── keys.py
│   │   ├── maps.py
│   │   ├── people.py
│   │   ├── services.py
│   │   └── sig.py
│   ├── lib/
│   │   ├── __init__.py
│   │   └── httpx.py
│   ├── modules/
│   │   ├── __init__.py
│   │   ├── drive.py
│   │   ├── email.py
│   │   ├── gaia.py
│   │   ├── geolocate.py
│   │   ├── login.py
│   │   └── spiderdal.py
│   ├── objects/
│   │   ├── __init__.py
│   │   ├── apis.py
│   │   ├── base.py
│   │   ├── encoders.py
│   │   ├── session.py
│   │   └── utils.py
│   ├── parsers/
│   │   ├── __init__.py
│   │   ├── calendar.py
│   │   ├── clientauthconfig.py
│   │   ├── digitalassetslinks.py
│   │   ├── drive.py
│   │   ├── geolocate.py
│   │   ├── identitytoolkit.py
│   │   ├── mobilesdk.py
│   │   ├── people.py
│   │   ├── playgames.py
│   │   ├── playgateway.py
│   │   └── vision.py
│   ├── protos/
│   │   ├── __init__.py
│   │   └── playgatewaypa/
│   │       ├── __init__.py
│   │       ├── definitions/
│   │       │   ├── get_player.proto
│   │       │   ├── get_player_response.proto
│   │       │   ├── search_player.proto
│   │       │   └── search_player_results.proto
│   │       ├── get_player_pb2.py
│   │       ├── get_player_response_pb2.py
│   │       ├── search_player_pb2.py
│   │       └── search_player_results_pb2.py
│   └── version.py
├── main.py
└── pyproject.toml

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
github: mxrch


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**System (please complete the following information):**
 - OS
 - Python version

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/workflows/sponsors.yml
================================================
name: Generate Sponsors README
on:
  workflow_dispatch:
  schedule:
    - cron: 30 15 * * 0-6
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout 🛎️
        uses: actions/checkout@v2

      - name: Generate Sponsors 💖
        uses: JamesIves/github-sponsors-readme-action@v1
        with:
          token: ${{ secrets.PAT }}
          file: 'README.md'
          template: '<a href="https://github.com/{{{ login }}}"><img src="https://github.com/{{{ login }}}.png" width="50px" alt="{{{ login }}}" /></a>&nbsp;&nbsp;'

      - name: Deploy to GitHub Pages 🚀
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          branch: master
          folder: '.'

================================================
FILE: .gitignore
================================================
.DS_Store
build/
dist/
__pycache__/
ghunt.egg-info/

================================================
FILE: LICENSE.md
================================================
### For easier reading : https://choosealicense.com/licenses/agpl-3.0/

\
GNU Affero General Public License
=================================

_Version 3, 19 November 2007_
_Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_

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.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    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 <http://www.gnu.org/licenses/>.

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
&lt;<http://www.gnu.org/licenses/>&gt;.


================================================
FILE: README.md
================================================
![](assets/long_banner.png)

<br>

#### 🌐 GHunt Online version : https://osint.industries
#### 🐍 Now Python 3.13 compatible !

<br>

![Python minimum version](https://img.shields.io/badge/Python-3.10%2B-brightgreen)

# 😊 Description

GHunt (v2) is an offensive Google framework, designed to evolve efficiently.\
It's currently focused on OSINT, but any use related with Google is possible.

Features :
- CLI usage and modules
- Python library usage
- Fully async
- JSON export
- Browser extension to ease login

# ✔️ Requirements
- Python >= 3.10

# ⚙️ Installation

```bash
$ pip3 install pipx
$ pipx ensurepath
$ pipx install ghunt
```
It will automatically use venvs to avoid dependency conflicts with other projects.

# 💃 Usage

## Login

First, launch the listener by doing `ghunt login` and choose between 1 of the 2 first methods :
```bash
$ ghunt login

[1] (Companion) Put GHunt on listening mode (currently not compatible with docker)
[2] (Companion) Paste base64-encoded cookies
[3] Enter manually all cookies

Choice =>
```

Then, use GHunt Companion to complete the login.

The extension is available on the following stores :\
\
[![Firefox](https://files.catbox.moe/5g2ld5.png)](https://addons.mozilla.org/en-US/firefox/addon/ghunt-companion/)&nbsp;&nbsp;&nbsp;[![Chrome](https://developer.chrome.com/static/docs/webstore/branding/image/206x58-chrome-web-bcb82d15b2486.png)](https://chrome.google.com/webstore/detail/ghunt-companion/dpdcofblfbmmnikcbmmiakkclocadjab)

## Modules

Then, profit :
```bash
Usage: ghunt [-h] {login,email,gaia,drive,geolocate} ...

Positional Arguments:
  {login,email,gaia,drive,geolocate}
    login               Authenticate GHunt to Google.
    email               Get information on an email address.
    gaia                Get information on a Gaia ID.
    drive               Get information on a Drive file or folder.
    geolocate           Geolocate a BSSID.
    spiderdal           Find assets using Digital Assets Links.

Options:
  -h, --help            show this help message and exit
```

📄 You can also use --json with email, gaia, drive and geolocate modules to export in JSON ! Example :

```bash
$ ghunt email <email_address> --json user_data.json
```

**Have fun 🥰💞**

# 🧑‍💻 Developers

📕 I started writing some docs [here](https://github.com/mxrch/GHunt/wiki) and examples [here](https://github.com/mxrch/GHunt/tree/master/examples), feel free to contribute !

To use GHunt as a lib, you can't use pipx because it uses a venv.\
So you should install GHunt with pip :
```bash
$ pip3 install ghunt
```

And now, you should be able to `import ghunt` in your projects !\
You can right now play with the [examples](https://github.com/mxrch/GHunt/tree/master/examples).

# 📮 Details

## Obvious disclaimer

This tool is for educational purposes only, I am not responsible for its use.

## Less obvious disclaimer

This project is under [AGPL Licence](https://choosealicense.com/licenses/agpl-3.0/), and you have to respect it.\
**Use it only in personal, criminal investigations, pentesting, or open-source projects.**

## Thanks

- [novitae](https://github.com/novitae) for being my Python colleague
- All the people on [Malfrats Industries](https://discord.gg/sg2YcrC6x9) and elsewhere for the beta test !
- The HideAndSec team 💗 (blog : https://hideandsec.sh)
- [Med Amine Jouini](https://dribbble.com/jouiniamine) for his beautiful rework of the Google logo, which I was inspired by *a lot*.

## Sponsors

Thanks to these awesome people for supporting me !

<!-- sponsors --><a href="https://github.com/BlWasp"><img src="https://github.com/BlWasp.png" width="50px" alt="BlWasp" /></a>&nbsp;&nbsp;<a href="https://github.com/gingeleski"><img src="https://github.com/gingeleski.png" width="50px" alt="gingeleski" /></a>&nbsp;&nbsp;<!-- sponsors -->

\
You like my work ?\
[Sponsor me](https://github.com/sponsors/mxrch) on GitHub ! 🤗


================================================
FILE: examples/email_registered.py
================================================
import os

import httpx
import asyncio

import sys

from ghunt.helpers.gmail import is_email_registered


async def main():
    if not sys.argv[1:]:
        print("Please give an email address.")
        exit()

    as_client = httpx.AsyncClient() # Async Client

    email = sys.argv[1]
    is_registered = await is_email_registered(as_client, email)

    print("Registered on Google :", is_registered)

asyncio.run(main()) # running our async code in a non-async code

================================================
FILE: examples/get_people_name.py
================================================
import httpx

import asyncio
import sys

from ghunt.apis.peoplepa import PeoplePaHttp
from ghunt.objects.base import GHuntCreds


async def main():
    if not sys.argv[1:]:
        exit("Please give an email address.")
    email = sys.argv[1]

    ghunt_creds = GHuntCreds()
    ghunt_creds.load_creds() # Check creds (but it doesn't crash if they are invalid)

    as_client = httpx.AsyncClient() # Async client

    people_api = PeoplePaHttp(ghunt_creds)
    found, person = await people_api.people_lookup(as_client, email, params_template="just_name")
                                                                    # You can have multiple "params_template" for the GHunt APIs,
                                                                    # for example, on this endpoint, you have "just_gaia_id" by default,
                                                                    # "just_name" or "max_details" which is used in the email CLI module.

    print("Found :", found)
    if found:
        if "PROFILE" in person.names: # A specification of People API, there are different containers
                                          # A target may not exists globally, but only in your contacts,
                                          # so it will show you only the CONTACT container,
                                          # with the informations you submitted.
                                          # What we want here is the PROFILE container, with public infos.

            print("Name :", person.names["PROFILE"].fullname)
        else:
            print("Not existing globally.")

asyncio.run(main()) # running our async code in a non-async code

================================================
FILE: ghunt/__init__.py
================================================
from ghunt import globals as gb; gb.init_globals()

================================================
FILE: ghunt/apis/__init__.py
================================================


================================================
FILE: ghunt/apis/accounts.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig

import httpx

from typing import *
import inspect


class Accounts(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        # Android OAuth fields
        self.api_name = "chrome"
        self.package_name = "com.android.chrome"
        self.scopes = [
            "https://www.google.com/accounts/OAuthLogin"
        ]
        
        self.hostname = "accounts.google.com"
        self.scheme = "https"

        self.authentication_mode = "oauth" # sapisidhash, cookies_only, oauth or None
        self.require_key = None # key name, or None

        self._load_api(creds, headers)

    async def OAuthLogin(self, as_client: httpx.AsyncClient) -> str:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
            key_origin = None
        )
        self._load_endpoint(endpoint)

        base_url = f"/OAuthLogin"
        params = {
            "source": "ChromiumBrowser",
            "issueuberauth": 1
        }

        req = await self._query(endpoint.name, as_client, base_url, params)

        # Parsing
        uber_auth = req.text

        return True, uber_auth

================================================
FILE: ghunt/apis/calendar.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.calendar import Calendar, CalendarEvents

import httpx

from typing import *
import inspect
import json
from datetime import datetime, timezone


class CalendarHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        self.hostname = "clients6.google.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def get_calendar(self, as_client: httpx.AsyncClient, calendar_id: str) -> Tuple[bool, Calendar]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "sapisidhash", # sapisidhash, cookies_only, oauth or None
            require_key = "calendar", # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/calendar/v3/calendars/{calendar_id}"

        req = await self._query(endpoint.name, as_client, base_url)

        # Parsing
        data = json.loads(req.text)

        calendar = Calendar()
        if "error" in data:
            return False, calendar
        
        calendar._scrape(data)

        return True, calendar

    async def get_events(self, as_client: httpx.AsyncClient, calendar_id: str, params_template="next_events",
                        time_min=datetime.today().replace(tzinfo=timezone.utc).isoformat(), max_results=250, page_token="") -> Tuple[bool, CalendarEvents]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "sapisidhash", # sapisidhash, cookies_only, oauth or None
            require_key = "calendar", # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/calendar/v3/calendars/{calendar_id}/events"

        params_templates = {
            "next_events": {
                "calendarId": calendar_id,
                "singleEvents": True,
                "maxAttendees": 1,
                "maxResults": max_results,
                "timeMin": time_min # ISO Format
            },
            "from_beginning": {
                "calendarId": calendar_id,
                "singleEvents": True,
                "maxAttendees": 1,
                "maxResults": max_results
            },
            "max_from_beginning": {
                "calendarId": calendar_id,
                "singleEvents": True,
                "maxAttendees": 1,
                "maxResults": 2500 # Max
            }
        }

        if not params_templates.get(params_template):
            raise GHuntParamsTemplateError(f"The asked template {params_template} for the endpoint {endpoint.name} wasn't recognized by GHunt.")

        params = params_templates[params_template]
        if page_token:
            params["pageToken"] = page_token

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)

        events = CalendarEvents()
        if not data:
            return False, events
        
        events._scrape(data)

        return True, events

================================================
FILE: ghunt/apis/clientauthconfig.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.clientauthconfig import CacBrand

import httpx

from typing import *
import inspect
import json


class ClientAuthConfigHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        self.hostname = "clientauthconfig.googleapis.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def get_brand(self, as_client: httpx.AsyncClient, project_number: int) -> Tuple[bool, CacBrand]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = None, # sapisidhash, cookies_only, oauth or None
            require_key = "pantheon", # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/v1/brands/lookupkey/brand/{project_number}"

        params = {
            "readMask": "*",
            "$outputDefaults": True
        }

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)

        brand = CacBrand()
        if "error" in data:
            return False, brand
        
        brand._scrape(data)

        return True, brand

================================================
FILE: ghunt/apis/digitalassetslinks.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.digitalassetslinks import DalStatements

import httpx

from typing import *
import inspect
import json


class DigitalAssetsLinksHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        self.hostname = "digitalassetlinks.googleapis.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def list_statements(self, as_client: httpx.AsyncClient, website: str="",
                        android_package_name: str="", android_cert_fingerprint: str="") -> Tuple[bool, DalStatements]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = None, # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = "/v1/statements:list"

        # Inputs checks
        if website and (android_package_name or android_cert_fingerprint):
            raise GHuntParamsInputError(f"[DigitalAssetsLinks API list statements] website and {android_package_name if android_package_name else android_cert_fingerprint} can't be both put at the same time.")
        elif not website and not (android_package_name and android_cert_fingerprint):
            raise GHuntParamsInputError("[DigitalAssetsLinks API list statements] Please , android_package_name and android_cert_ingerprint.")
        elif not (website or android_package_name or android_cert_fingerprint):
            raise GHuntParamsInputError("[DigitalAssetsLinks API list statements] Please choose at least one parameter between website, android_package_name and android_cert_ingerprint.")

        params = {}
        if website:
            params["source.web.site"] = website
        if android_package_name:
            params["source.androidApp.packageName"] = android_package_name
        if android_cert_fingerprint:
            params["source.androidApp.certificate.sha256Fingerprint"] = android_cert_fingerprint

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)

        statements = DalStatements()
        if "error" in data:
            return False, statements
        
        statements._scrape(data)

        found = bool(statements.statements)
        return found, statements

================================================
FILE: ghunt/apis/drive.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.drive import DriveCommentList, DriveFile, DriveChildList
from ghunt.knowledge import drive as drive_knowledge

import httpx

from typing import *
import inspect
import json


class DriveHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        # Android OAuth fields
        self.api_name = "drive"
        self.package_name = "com.google.android.apps.docs"
        self.scopes = [
            "https://www.googleapis.com/auth/drive",
            "https://www.googleapis.com/auth/drive.file"
        ]

        self.hostname = "www.googleapis.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def get_file(self, as_client: httpx.AsyncClient, file_id: str) -> Tuple[bool, DriveFile]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/drive/v2internal/files/{file_id}"

        params = {
            "fields": ','.join(drive_knowledge.request_fields),
            "supportsAllDrives": True
        }

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)
        drive_file = DriveFile()
        if "error" in data:
            return False, drive_file

        drive_file._scrape(data)

        return True, drive_file

    async def get_comments(self, as_client: httpx.AsyncClient, file_id: str, page_token: str="") -> Tuple[bool, str, DriveCommentList]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/drive/v2internal/files/{file_id}/comments"

        params = {
            "supportsAllDrives": True,
            "maxResults": 100
        }

        if page_token:
            params["pageToken"] = page_token

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)
        drive_comments = DriveCommentList()
        if "error" in data:
            return False, "", drive_comments

        next_page_token = data.get("nextPageToken", "")

        drive_comments._scrape(data)

        return True, next_page_token, drive_comments

    async def get_childs(self, as_client: httpx.AsyncClient, file_id: str, page_token: str="") -> Tuple[bool, str, DriveChildList]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/drive/v2internal/files/{file_id}/children"

        params = {
            "supportsAllDrives": True,
            "maxResults": 1000
        }

        if page_token:
            params["pageToken"] = page_token

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)
        drive_childs = DriveChildList()
        if "error" in data:
            return False, "", drive_childs

        next_page_token = data.get("nextPageToken", "")

        drive_childs._scrape(data)

        return True, next_page_token, drive_childs

================================================
FILE: ghunt/apis/fireconsolepa.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.clientauthconfig import CacBrand

import httpx

from typing import *
import inspect
import json


class FireconsolePaHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        self.hostname = "fireconsole-pa.clients6.google.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def is_project_valid(self, as_client: httpx.AsyncClient, project_identifier: str) -> Tuple[bool, CacBrand]:
        """
            Returns if the given project identifier is valid.
            The project identifier can be a project ID or a project number.
        """
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "POST",
            data_type = "json", # json, data or None
            authentication_mode = "sapisidhash", # sapisidhash, cookies_only, oauth or None
            require_key = "firebase_console", # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = "/v1/analytics:checkAccess"

        params = {
            "alt": "json"
        }

        post_data = {
            "entityKey": {},
            "firebaseProjectId": project_identifier
        }

        req = await self._query(endpoint.name, as_client, base_url, params=params, data=post_data)

        return req.status_code != 404

================================================
FILE: ghunt/apis/geolocation.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.geolocate import GeolocationResponse

import httpx

from typing import *
import inspect
import json


class GeolocationHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        self.hostname = "www.googleapis.com"
        self.scheme = "https"

        self.authentication_mode = None # sapisidhash, cookies_only, oauth or None
        self.require_key = "geolocation" # key name, or None

        self._load_api(creds, headers)

    async def geolocate(self, as_client: httpx.AsyncClient, bssid: str, body: dict) -> Tuple[bool, GeolocationResponse]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "POST",
            data_type = "json", # json, data or None
            authentication_mode = None, # sapisidhash, cookies_only, oauth or None
            require_key = "geolocation", # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/geolocation/v1/geolocate"

        if bssid:
            payload = {
                "considerIp": False,
                "wifiAccessPoints": [
                    {
                        "macAddress": "00:25:9c:cf:1c:ad"
                    },
                    {
                        "macAddress": bssid
                    },
                ]
            }
        else:
            payload = body

        req = await self._query(endpoint.name, as_client, base_url, data=payload)

        # Parsing
        data = json.loads(req.text)

        resp = GeolocationResponse()
        if "error" in data:
            return False, resp
        
        resp._scrape(data)

        return True, resp

================================================
FILE: ghunt/apis/identitytoolkit.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.identitytoolkit import ITKProjectConfig

import httpx

from typing import *
import inspect
import json


class IdentityToolkitHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        self.hostname = "www.googleapis.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def get_project_config(self, as_client: httpx.AsyncClient, api_key: str) -> Tuple[bool, ITKProjectConfig]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = None, # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = "/identitytoolkit/v3/relyingparty/getProjectConfig"

        params = {
            "key": api_key
        }

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)

        project_config = ITKProjectConfig()
        if "error" in data:
            return False, project_config
        
        project_config._scrape(data)

        return True, project_config

================================================
FILE: ghunt/apis/mobilesdk.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.mobilesdk import MobileSDKDynamicConfig

import httpx

from typing import *
import inspect
import json


class MobileSDKPaHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        self.hostname = "mobilesdk-pa.clients6.google.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def test_iam_permissions(self, as_client: httpx.AsyncClient, project_identifier: str, permissions: List[str]) -> Tuple[bool, List[str]]:
        """
            Returns the permissions you have against a project.
            The project identifier can be a project ID or a project number.
        """

        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "POST",
            data_type = "json", # json, data or None
            authentication_mode = "sapisidhash", # sapisidhash, cookies_only, oauth or None
            require_key = "firebase_console", # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/v1/projects/{project_identifier}:testIamPermissions"

        post_data = {
            "permissions": permissions
        }

        req = await self._query(endpoint.name, as_client, base_url, data=post_data)

        # Parsing
        data = json.loads(req.text)

        if "error" in data:
            return False, []

        return True, data.get("permissions", [])

    async def get_webapp_dynamic_config(self, as_client: httpx.AsyncClient, app_id: str) -> Tuple[bool, MobileSDKDynamicConfig]:
        """
            Returns the dynamic config of a web app.
            
            :param app_id: The app id
        """
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "sapisidhash", # sapisidhash, cookies_only, oauth or None,
            key_origin="firebase_console", # key name, or None
            # require_key = "firebase_console", # key name, or None
        )
        self._load_endpoint(endpoint)

        # Android OAuth fields
        self.api_name = "mobilesdk"
        self.package_name = "com.android.chrome"
        self.scopes = [
                        "https://www.googleapis.com/auth/cloud-platform",
                        "https://www.googleapis.com/auth/cloud-platform.read-only",
                        "https://www.googleapis.com/auth/firebase",
                        "https://www.googleapis.com/auth/firebase.readonly"
                    ]

        base_url = f"/v1/config/webApps/{app_id}/dynamicConfig"

        req = await self._query(endpoint.name, as_client, base_url)

        # Parsing
        data = json.loads(req.text)

        dynamic_config = MobileSDKDynamicConfig()
        if "error" in data:
            return False, dynamic_config
        
        dynamic_config._scrape(data)

        return True, dynamic_config

================================================
FILE: ghunt/apis/peoplepa.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.people import Person

import httpx

from typing import *
import inspect
import json


class PeoplePaHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {
            "Host": "people-pa.clients6.google.com",
        }

        headers = {**headers, **base_headers}

        self.hostname = "googleapis.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def people_lookup(self, as_client: httpx.AsyncClient, email: str, params_template="just_gaia_id") -> Tuple[bool, Person]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "sapisidhash", # sapisidhash, cookies_only, oauth or None
            require_key = "photos", # key name, or None
            # key_origin="photos"
        )

        # Android OAuth fields
        self.api_name = "people"
        self.package_name = "com.google.android.gms"
        self.scopes = [
            "https://www.googleapis.com/auth/profile.agerange.read",
            "https://www.googleapis.com/auth/profile.language.read",
            "https://www.googleapis.com/auth/contacts",
            "https://www.googleapis.com/auth/peopleapi.legacy.readwrite"

        ]

        self._load_endpoint(endpoint)

        base_url = "/v2/people/lookup"

        params_templates = {
            "just_gaia_id": {
                "id": email,
                "type": "EMAIL",
                "matchType": "EXACT",
                "requestMask.includeField.paths": "person.metadata"
            },
            "just_name": {
                "id": email,
                "type": "EMAIL",
                "matchType": "EXACT",
                "requestMask.includeField.paths": "person.name",
                "core_id_params.enable_private_names": True
            },
            "max_details": {
                "id": email,
                "type": "EMAIL",
                "match_type": "EXACT",
                "extension_set.extension_names": [
                    "DYNAMITE_ADDITIONAL_DATA",
                    "DYNAMITE_ORGANIZATION_INFO"
                ],
                "request_mask.include_field.paths": [
                    "person.metadata.best_display_name",
                    "person.photo",
                    "person.cover_photo",
                    "person.interaction_settings",
                    "person.legacy_fields",
                    "person.metadata",
                    "person.in_app_reachability",
                    "person.name",
                    "person.read_only_profile_info",
                    "person.sort_keys",
                    "person.email"
                ],
                "request_mask.include_container": [
                    "AFFINITY",
                    "PROFILE",
                    "DOMAIN_PROFILE",
                    "ACCOUNT",
                    "EXTERNAL_ACCOUNT",
                    "CIRCLE",
                    "DOMAIN_CONTACT",
                    "DEVICE_CONTACT",
                    "GOOGLE_GROUP",
                    "CONTACT"
                ],
                "core_id_params.enable_private_names": True
            }
        }

        if not params_templates.get(params_template):
            raise GHuntParamsTemplateError(f"The asked template {params_template} for the endpoint {endpoint.name} wasn't recognized by GHunt.")
        params = params_templates[params_template]

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)
        person = Person()
        if not data:
            return False, person
        
        person_data = list(data["people"].values())[0]
        await person._scrape(as_client, person_data)

        return True, person

    async def people(self, as_client: httpx.AsyncClient, gaia_id: str, params_template="just_name") -> Tuple[bool, Person]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "sapisidhash", # sapisidhash, cookies_only, oauth or None
            require_key = "photos", # key name, or None
            # key_origin="photos"
        )
        self._load_endpoint(endpoint)

        # Android OAuth fields
        self.api_name = "people"
        self.package_name = "com.google.android.gms"
        self.scopes = [
            "https://www.googleapis.com/auth/profile.agerange.read",
            "https://www.googleapis.com/auth/profile.language.read",
            "https://www.googleapis.com/auth/contacts",
            "https://www.googleapis.com/auth/peopleapi.legacy.readwrite"

        ]

        base_url = "/v2/people"

        params_templates = {
            "just_name": {
                "person_id": gaia_id,
                "requestMask.includeField.paths": "person.name",
                "core_id_params.enable_private_names": True
            },
            "max_details": {
                "person_id": gaia_id,
                "extension_set.extension_names": [
                    "DYNAMITE_ADDITIONAL_DATA",
                    "DYNAMITE_ORGANIZATION_INFO"
                ],
                "request_mask.include_field.paths": [
                    "person.metadata.best_display_name",
                    "person.photo",
                    "person.cover_photo",
                    "person.interaction_settings",
                    "person.legacy_fields",
                    "person.metadata",
                    "person.in_app_reachability",
                    "person.name",
                    "person.read_only_profile_info",
                    "person.sort_keys",
                    "person.email"
                ],
                "request_mask.include_container": [
                    "AFFINITY",
                    "PROFILE",
                    "DOMAIN_PROFILE",
                    "ACCOUNT",
                    "EXTERNAL_ACCOUNT",
                    "CIRCLE",
                    "DOMAIN_CONTACT",
                    "DEVICE_CONTACT",
                    "GOOGLE_GROUP",
                    "CONTACT"
                ],
                "core_id_params.enable_private_names": True
            }
        }

        if not params_templates.get(params_template):
            raise GHuntParamsTemplateError(f"The asked template {params_template} for the endpoint {endpoint.name} wasn't recognized by GHunt.")
        params = params_templates[params_template]

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)
        person = Person()
        if data["personResponse"][0]["status"] == "NOT_FOUND":
            return False, person
        
        person_data = data["personResponse"][0]["person"]
        await person._scrape(as_client, person_data)

        return True, person

================================================
FILE: ghunt/apis/playgames.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.playgames import PlayedGames, PlayerAchievements, PlayerProfile

import httpx

from typing import *
import inspect
import json


class PlayGames(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers

        base_headers = {}

        headers = {**headers, **base_headers}

        # Android OAuth fields
        self.api_name = "playgames"
        self.package_name = "com.google.android.play.games"
        self.scopes = [
            "https://www.googleapis.com/auth/games.firstparty",
            "https://www.googleapis.com/auth/googleplay"
        ]
        
        self.hostname = "www.googleapis.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def get_profile(self, as_client: httpx.AsyncClient, player_id: str) -> Tuple[bool, PlayerProfile]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/games/v1whitelisted/players/{player_id}"

        req = await self._query(endpoint.name, as_client, base_url)

        # Parsing
        data = json.loads(req.text)
        player_profile = PlayerProfile()
        if not "displayPlayer" in data:
            return False, player_profile

        player_profile._scrape(data["displayPlayer"])
        player_profile.id = player_id

        return True, player_profile

    async def get_played_games(self, as_client: httpx.AsyncClient, player_id: str, page_token: str="") -> Tuple[bool, str, PlayedGames]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "GET",
            data_type = None, # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/games/v1whitelisted/players/{player_id}/applications/played"

        params = {}
        if page_token:
            params = {"pageToken": page_token}

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)
        played_games = PlayedGames()
        if not "items" in data:
            return False, "", played_games

        next_page_token = data.get("nextPageToken", "")

        played_games._scrape(data["items"])

        return True, next_page_token, played_games

    async def get_achievements(self, as_client: httpx.AsyncClient, player_id: str, page_token: str="") -> Tuple[bool, str, PlayerAchievements]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "POST",
            data_type = "json", # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
        )
        self._load_endpoint(endpoint)

        base_url = f"/games/v1whitelisted/players/{player_id}/achievements"

        params = {
            "state": "UNLOCKED",
            "returnDefinitions": True,
            "sortOrder": "RECENT_FIRST"
        }

        if page_token:
            params["pageToken"] = page_token

        req = await self._query(endpoint.name, as_client, base_url, params=params)

        # Parsing
        data = json.loads(req.text)
        achievements = PlayerAchievements()
        if not "items" in data:
            return False, "", achievements
        
        next_page_token = ""
        if "nextPageToken" in data:
            next_page_token = data["nextPageToken"]

        achievements._scrape(data)

        return True, next_page_token, achievements

================================================
FILE: ghunt/apis/playgateway.py
================================================
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.objects.base import GHuntCreds
from ghunt import globals as gb
from ghunt.protos.playgatewaypa.search_player_pb2 import PlayerSearchProto
from ghunt.protos.playgatewaypa.search_player_results_pb2 import PlayerSearchResultsProto
from ghunt.protos.playgatewaypa.get_player_pb2 import GetPlayerProto
from ghunt.protos.playgatewaypa.get_player_response_pb2 import GetPlayerResponseProto
from ghunt.parsers.playgateway import PlayerSearchResults
from ghunt.parsers.playgateway import PlayerProfile

import httpx

from typing import *
from struct import pack
import inspect


class PlayGatewayPaGrpc(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()

        # Android OAuth fields
        self.api_name = "playgames"
        self.package_name = "com.google.android.play.games"
        self.scopes = [
            "https://www.googleapis.com/auth/games.firstparty",
            "https://www.googleapis.com/auth/googleplay"
        ]

        if not headers:
            headers = gb.config.android_headers

        headers = {**headers, **{
            "Content-Type": "application/grpc",
            "Te": "trailers"
        }}

        # Normal fields

        self.hostname = "playgateway-pa.googleapis.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def search_player(self, as_client: httpx.AsyncClient, query: str) -> PlayerSearchResults:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "POST",
            data_type = "data", # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
            ext_metadata = {
                                "bin": {
                                    "158709649": "CggaBgj22K2aARo4EgoI+aKnlZf996E/GhcQHhoPUkQyQS4yMTEwMDEuMDAyIgIxMToICgZJZ0pHVWdCB1BpeGVsIDU",
                                    "173715354": "CgEx"
                                }
                            }
        )
        self._load_endpoint(endpoint)

        base_url = "/play.gateway.adapter.interplay.v1.PlayGatewayInterplayService/GetPage"

        player_search = PlayerSearchProto()
        player_search.search_form.query.text = query
        payload = player_search.SerializeToString()

        prefix = bytes(1) + pack(">i", len(payload))
        data = prefix + payload

        req = await self._query(endpoint.name, as_client, base_url, data=data)

        # Parsing
        player_search_results = PlayerSearchResultsProto()
        player_search_results.ParseFromString(req.content[5:])

        parser = PlayerSearchResults()
        parser._scrape(player_search_results)

        return parser

    async def get_player_stats(self, as_client: httpx.AsyncClient, player_id: str) -> PlayerProfile:
        """
            This endpoint client isn't finished, it is only used to get total played applications & achievements count.
            To get all the details about a player, please use get_player method of PlayGames (HTTP API).
        """

        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "POST",
            data_type = "data", # json, data or None
            authentication_mode = "oauth", # sapisidhash, cookies_only, oauth or None
            require_key = None, # key name, or None
            ext_metadata = {
                                "bin": {
                                    "158709649": "CggaBgj22K2aARo4EgoI+aKnlZf996E/GhcQHhoPUkQyQS4yMTEwMDEuMDAyIgIxMToICgZJZ0pHVWdCB1BpeGVsIDU",
                                    "173715354": "CgEx"
                                }
                            }
        )
        self._load_endpoint(endpoint)

        base_url = "/play.gateway.adapter.interplay.v1.PlayGatewayInterplayService/GetPage"

        player_profile = GetPlayerProto()
        player_profile.form.query.id = player_id
        payload = player_profile.SerializeToString()

        prefix = bytes(1) + pack(">i", len(payload))
        data = prefix + payload

        req = await self._query(endpoint.name, as_client, base_url, data=data)

        # Parsing
        player_profile = GetPlayerResponseProto()
        player_profile.ParseFromString(req.content[5:])

        parser = PlayerProfile()
        parser._scrape(player_profile)

        return parser

================================================
FILE: ghunt/apis/vision.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
import ghunt.globals as gb
from ghunt.objects.apis import GAPI, EndpointConfig
from ghunt.parsers.vision import VisionFaceDetection

import httpx

from typing import *
import inspect
import json

        
class VisionHttp(GAPI):
    def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
        super().__init__()
        
        if not headers:
            headers = gb.config.headers
        
        base_headers = {
            "X-Origin": "https://explorer.apis.google.com"
        }

        headers = {**headers, **base_headers}

        self.hostname = "content-vision.googleapis.com"
        self.scheme = "https"

        self._load_api(creds, headers)

    async def detect_faces(self, as_client: httpx.AsyncClient, image_url: str = "", image_content: str = "",
                            data_template="default") -> Tuple[bool, bool, VisionFaceDetection]:
        endpoint = EndpointConfig(
            name = inspect.currentframe().f_code.co_name,
            verb = "POST",
            data_type = "json", # json, data or None
            authentication_mode = None, # sapisidhash, cookies_only, oauth or None
            require_key = "apis_explorer", # key name, or None
            key_origin = "https://content-vision.googleapis.com"
        )
        self._load_endpoint(endpoint)

        base_url = "/v1/images:annotate"

        # image_url can cause errors with vision_api, so we prefer using image_content
        # See => https://cloud.google.com/vision/docs/detecting-faces?#detect_faces_in_a_remote_image

        data_templates = {
            "default": {
                "requests":[
                    {
                        "features": [
                            {
                                "maxResults":100,
                                "type":"FACE_DETECTION"
                            }
                        ],
                        "image": {}
                    }
                ]
            }
        }

        if not data_templates.get(data_template):
            raise GHuntParamsTemplateError(f"The asked template {data_template} for the endpoint {endpoint.name} wasn't recognized by GHunt.")

        # Inputs checks
        if image_url and image_content:
            raise GHuntParamsInputError("[Vision API faces detection] image_url and image_content can't be both put at the same time.")
        elif not image_url and not image_content:
            raise GHuntParamsInputError("[Vision API faces detection] Please choose at least one parameter between image_url and image_content.")

        if data_template == "default":
            if image_url:
                data_templates["default"]["requests"][0]["image"] = {
                    "source": {
                        "imageUri": image_url
                    }
                }
            elif image_content:
                data_templates["default"]["requests"][0]["image"] = {
                    "content": image_content
                }

        data = data_templates[data_template]
        req = await self._query(endpoint.name, as_client, base_url, data=data)

        rate_limited = req.status_code == 429 # API Explorer sometimes rate-limit because they set their DefaultRequestsPerMinutePerProject to 1800

        vision_face_detection = VisionFaceDetection()
        if rate_limited:
            return rate_limited, False, vision_face_detection

        # Parsing
        data = json.loads(req.text)
        if not data["responses"][0]:
            return rate_limited, False, vision_face_detection
        
        vision_data = data["responses"][0]
        vision_face_detection._scrape(vision_data)

        return rate_limited, True, vision_face_detection

================================================
FILE: ghunt/cli.py
================================================
from rich_argparse import RichHelpFormatter

import argparse
from typing import *
import sys
from pathlib import Path


def parse_and_run():
    RichHelpFormatter.styles["argparse.groups"] = "misty_rose1"
    RichHelpFormatter.styles["argparse.metavar"] = "light_cyan1"
    RichHelpFormatter.styles["argparse.args"] = "light_steel_blue1"
    RichHelpFormatter.styles["argparse.prog"] = "light_pink1 bold italic"


    parser = argparse.ArgumentParser(formatter_class=RichHelpFormatter)
    subparsers = parser.add_subparsers(dest="module")

    ### Login module
    parser_login = subparsers.add_parser('login', help="Authenticate GHunt to Google.", formatter_class=RichHelpFormatter)
    parser_login.add_argument('--clean', action='store_true', help="Clear credentials local file.")

    ### Email module
    parser_email = subparsers.add_parser('email', help="Get information on an email address.", formatter_class=RichHelpFormatter)
    parser_email.add_argument("email_address")
    parser_email.add_argument('--json', type=Path, help="File to write the JSON output to.")

    ### Gaia module
    parser_gaia = subparsers.add_parser('gaia', help="Get information on a Gaia ID.", formatter_class=RichHelpFormatter)
    parser_gaia.add_argument("gaia_id")
    parser_gaia.add_argument('--json', type=Path, help="File to write the JSON output to.")

    ### Drive module
    parser_drive = subparsers.add_parser('drive', help="Get information on a Drive file or folder.", formatter_class=RichHelpFormatter)
    parser_drive.add_argument("file_id", help="Example: 1N__vVu4c9fCt4EHxfthUNzVOs_tp8l6tHcMBnpOZv_M")
    parser_drive.add_argument('--json', type=Path, help="File to write the JSON output to.")

    ### Geolocate module
    parser_geolocate = subparsers.add_parser('geolocate', help="Geolocate a BSSID.", formatter_class=RichHelpFormatter)
    geolocate_group = parser_geolocate.add_mutually_exclusive_group(required=True)
    geolocate_group.add_argument("-b", "--bssid", help="Example: 30:86:2d:c4:29:d0")
    geolocate_group.add_argument("-f", "--file", type=Path,  help="File containing a raw request body, useful to put many BSSIDs. ([italic light_steel_blue1][link=https://developers.google.com/maps/documentation/geolocation/requests-geolocation?#sample-requests]Reference format[/link][/italic light_steel_blue1])")
    parser_geolocate.add_argument('--json', type=Path, help="File to write the JSON output to.")

    ### Spiderdal module
    parser_spiderdal = subparsers.add_parser('spiderdal', help="Find assets using Digital Assets Links.", formatter_class=RichHelpFormatter)
    parser_spiderdal.add_argument("-p", "--package", help="Example: com.squareup.cash")
    parser_spiderdal.add_argument("-f", "--fingerprint", help="Example: 21:A7:46:75:96:C1:68:65:0F:D7:B6:31:B6:54:22:EB:56:3E:1D:21:AF:F2:2D:DE:73:89:BA:0D:5D:73:87:48")
    parser_spiderdal.add_argument("-u", "--url", help="Example: https://cash.app. If a domain is given, it will convert it to a URL, and also try the \"www\" subdomain.")
    parser_spiderdal.add_argument("-s", "--strict", action='store_true', help="Don't attempt to convert the domain to a URL, and don't try the \"www\" subdomain.")
    parser_spiderdal.add_argument('--json', type=Path, help="File to write the JSON output to.")

    ### Parsing
    args = None
    if not sys.argv[1:]:
        parser.parse_args(["--help"])
    else:
        for mod in ["email", "gaia", "drive", "geolocate", "spiderdal"]:
            if sys.argv[1] == mod and not sys.argv[2:]:
                parser.parse_args([mod, "--help"])

    args = parser.parse_args(args)
    process_args(args)

def process_args(args: argparse.Namespace):
    import asyncio
    match args.module:
        case "login":
            from ghunt.modules import login
            asyncio.run(login.check_and_login(None, args.clean))
        case "email":
            from ghunt.modules import email
            asyncio.run(email.hunt(None, args.email_address, args.json))
        case "gaia":
            from ghunt.modules import gaia
            asyncio.run(gaia.hunt(None, args.gaia_id, args.json))
        case "drive":
            from ghunt.modules import drive
            asyncio.run(drive.hunt(None, args.file_id, args.json))
        case "geolocate":
            from ghunt.modules import geolocate
            asyncio.run(geolocate.main(None, args.bssid, args.file, args.json))
        case "spiderdal":
            if any([args.package, args.fingerprint]) and not all([args.package, args.fingerprint]):
                exit("[!] You must provide both a package name and a certificate fingerprint.")
            from ghunt.modules import spiderdal
            asyncio.run(spiderdal.main(args.url, args.package, args.fingerprint, args.strict, args.json))

================================================
FILE: ghunt/config.py
================================================
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0',
    'Connection': 'Keep-Alive'
}

android_headers = {
    'User-Agent': '{}/323710070 (Linux; U; Android 11; fr_FR; Pixel 5; Build/RD2A.211001.002; Cronet/97.0.4692.70) grpc-java-cronet/1.44.0-SNAPSHOT', # android package name
    'Connection': 'Keep-Alive'
}

templates = {
    "gmaps_pb":{
        "stats": "!1s{}!2m3!1sYE3rYc2rEsqOlwSHx534DA!7e81!15i14416!6m2!4b1!7b1!9m0!16m4!1i100!4b1!5b1!6BQ0FFU0JrVm5TVWxEenc9PQ!17m28!1m6!1m2!1i0!2i0!2m2!1i458!2i736!1m6!1m2!1i1868!2i0!2m2!1i1918!2i736!1m6!1m2!1i0!2i0!2m2!1i1918!2i20!1m6!1m2!1i0!2i716!2m2!1i1918!2i736!18m12!1m3!1d806313.5865720833!2d150.19484835!3d-34.53825215!2m3!1f0!2f0!3f0!3m2!1i1918!2i736!4f13.1",
        "reviews": {
            "first": "!1s{}!2m3!1s_2zAZ5CJDouBi-gPpJ7biAg!7e81!15i14416!6m2!4b1!7b1!9m0!17m28!1m6!1m2!1i0!2i0!2m2!1i530!2i1279!1m6!1m2!1i3390!2i0!2m2!1i3440!2i1279!1m6!1m2!1i0!2i0!2m2!1i3440!2i20!1m6!1m2!1i0!2i1259!2m2!1i3440!2i1279!18m15!1m3!1d2834470.167608874!2d150.05636185!3d-33.57307085!2m3!1f0!2f0!3f0!3m2!1i3440!2i1279!4f13.1!6m2!1f0!2f0!41m15!1i20!2m9!2b1!3b1!5b1!7b1!12m4!1b1!2b1!4m1!1e1!3sCAESA0VnQQ%3D%3D!7m2!1m1!1e1",
            "page": "!1s{}!2m3!1s_2zAZ5CJDouBi-gPpJ7biAg!7e81!15i14416!6m2!4b1!7b1!9m0!17m28!1m6!1m2!1i0!2i0!2m2!1i530!2i1279!1m6!1m2!1i3390!2i0!2m2!1i3440!2i1279!1m6!1m2!1i0!2i0!2m2!1i3440!2i20!1m6!1m2!1i0!2i1259!2m2!1i3440!2i1279!18m15!1m3!1d2834470.167608874!2d150.05636185!3d-33.57307085!2m3!1f0!2f0!3f0!3m2!1i3440!2i1279!4f13.1!6m2!1f0!2f0!41m15!1i20!2m9!2b1!3b1!5b1!7b1!12m4!1b1!2b1!4m1!1e1!3s{}!7m2!1m1!1e1"
        },
        "photos": {
            "first": "!1s{}!2m3!1spQUAYoPQLcOTlwT9u6-gDA!7e81!15i18404!9m0!14m69!1m57!1m4!1m3!1e3!1e2!1e4!3m5!2m4!3m3!1m2!1i260!2i365!4m1!3i10!10b1!11m42!1m3!1e1!2b0!3e3!1m3!1e2!2b1!3e2!1m3!1e2!2b0!3e3!1m3!1e8!2b0!3e3!1m3!1e10!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e9!2b1!3e2!1m3!1e10!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e10!2b0!3e4!2b1!4b1!2m5!1e1!1e4!1e3!1e5!1e2!3b1!4b1!5m1!1e1!7b1",
            "page": "!1s{}!2m3!1spQUAYoPQLcOTlwT9u6-gDA!7e81!15i14415!9m0!14m68!1m58!1m4!1m3!1e3!1e2!1e4!3m5!2m4!3m3!1m2!1i260!2i365!4m2!2s{}!3i100!10b1!11m42!1m3!1e1!2b0!3e3!1m3!1e2!2b1!3e2!1m3!1e2!2b0!3e3!1m3!1e8!2b0!3e3!1m3!1e10!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e9!2b1!3e2!1m3!1e10!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e10!2b0!3e4!2b1!4b1!2m5!1e1!1e4!1e3!1e5!1e2!5m1!1e1!7b1!17m28!1m6!1m2!1i0!2i0!2m2!1i458!2i595!1m6!1m2!1i950!2i0!2m2!1i1000!2i595!1m6!1m2!1i0!2i0!2m2!1i1000!2i20!1m6!1m2!1i0!2i575!2m2!1i1000!2i595!18m12!1m3!1d1304345.2752527467!2d149.32871599857805!3d-34.496155324132545!2m3!1f0!2f0!3f0!3m2!1i1000!2i595!4f13.1"
        }
    }
}

gmaps_radius     = 30 # in km. The radius distance to create groups of gmaps reviews.

# Cookies
default_consent_cookie = "YES+cb.20220118-08-p0.fr+FX+510"
default_pref_cookie = "tz=Europe.Paris&f6=40000000&hl=en" # To set the lang settings to english

================================================
FILE: ghunt/errors.py
================================================
class GHuntKnowledgeError(Exception):
    pass

class GHuntCorruptedHeadersError(Exception):
    pass

class GHuntUnknownVerbError(Exception):
    pass

class GHuntUnknownRequestDataTypeError(Exception):
    pass

class GHuntInsufficientCreds(Exception):
    pass

class GHuntParamsTemplateError(Exception):
    pass

class GHuntParamsInputError(Exception):
    pass

class GHuntAPIResponseParsingError(Exception):
    pass

class GHuntObjectsMergingError(Exception):
    pass

class GHuntAndroidMasterAuthError(Exception):
    pass

class GHuntAndroidAppOAuth2Error(Exception):
    pass

class GHuntOSIDAuthError(Exception):
    pass

class GHuntCredsNotLoaded(Exception):
    pass

class GHuntInvalidSession(Exception):
    pass

class GHuntNotAuthenticated(Exception):
    pass

class GHuntInvalidTarget(Exception):
    pass

class GHuntLoginError(Exception):
    pass

================================================
FILE: ghunt/ghunt.py
================================================
import os
import sys


def main():
    version = sys.version_info
    if (version < (3, 10)):
        print('[-] GHunt only works with Python 3.10+.')
        print(f'Your current Python version : {version.major}.{version.minor}.{version.micro}')
        sys.exit(os.EX_SOFTWARE)

    from ghunt.cli import parse_and_run
    from ghunt.helpers.banner import show_banner
    from ghunt.helpers.utils import show_version

    show_banner()
    show_version()
    print()
    parse_and_run()

================================================
FILE: ghunt/globals.py
================================================
# This file is only intended to serve global variables at a project-wide level.


def init_globals():
    from ghunt.objects.utils import TMPrinter
    from rich.console import Console

    global config, tmprinter, rc
    
    from ghunt import config
    tmprinter = TMPrinter()
    rc = Console(highlight=False) # Rich Console

================================================
FILE: ghunt/helpers/__init__.py
================================================


================================================
FILE: ghunt/helpers/auth.py
================================================
import asyncio
import json
import base64
import os
from typing import *

import httpx
from bs4 import BeautifulSoup as bs

from ghunt import globals as gb
from ghunt.objects.base import GHuntCreds
from ghunt.errors import *
from ghunt.helpers.utils import *
from ghunt.helpers import listener
from ghunt.helpers.knowledge import get_domain_of_service, get_package_sig
from ghunt.knowledge.services import services_baseurls
from ghunt.helpers.auth import *


async def android_master_auth(as_client: httpx.AsyncClient, oauth_token: str) -> Tuple[str, List[str], str, str]:
    """
        Takes an oauth_token to perform an android authentication
        to get the master token and other informations.

        Returns the master token, connected services, account email and account full name.
    """
    data = {
        "Token": oauth_token,
        "service": "ac2dm",
        "get_accountid": 1,
        "ACCESS_TOKEN": 1,
        "add_account": 1,
        "callerSig": "38918a453d07199354f8b19af05ec6562ced5788",
        "droidguard_results": "dummy123", # https://github.com/simon-weber/gpsoauth/blob/429b7f99fa268315cef7a981408a612fb424a79b/gpsoauth/__init__.py#L153
    }

    req = await as_client.post("https://android.googleapis.com/auth", data=data)
    resp = parse_oauth_flow_response(req.text)
    for keyword in ["Token", "Email", "services", "firstName", "lastName"]:
        if keyword not in resp:
            raise GHuntAndroidMasterAuthError(f'Expected "{keyword}" in the response of the Android Master Authentication.\nThe oauth_token may be expired.')
    return resp["Token"], resp["services"].split(","), resp["Email"], f'{resp["firstName"]} {resp["lastName"]}'

async def android_oauth_app(as_client: httpx.AsyncClient, master_token: str,
                package_name: str, scopes: List[str]) -> Tuple[str, List[str], int]:
    """
        Uses the master token to ask for an authorization token,
        with specific scopes and app package name.

        Returns the authorization token, granted scopes and expiry UTC timestamp.
    """
    client_sig = get_package_sig(package_name)

    data = {
        "app": package_name,
        "service": f"oauth2:{' '.join(scopes)}",
        "client_sig": client_sig,
        "Token": master_token
    }

    req = await as_client.post("https://android.googleapis.com/auth", data=data)
    resp = parse_oauth_flow_response(req.text)
    for keyword in ["Expiry", "grantedScopes", "Auth"]:
        if keyword not in resp:
            raise GHuntAndroidAppOAuth2Error(f'Expected "{keyword}" in the response of the Android App OAuth2 Authentication.\nThe master token may be revoked.')
    return resp["Auth"], resp["grantedScopes"].split(" "), int(resp["Expiry"])

async def gen_osid(as_client: httpx.AsyncClient, cookies: Dict[str, str], generated_osids: dict[str, str], service: str) -> None:
    domain = get_domain_of_service(service)

    params = {
        "service": service,
        "osid": 1,
        "continue": f"https://{domain}/",
        "followup": f"https://{domain}/",
        "authuser": 0
    }

    req = await as_client.get(f"https://accounts.google.com/ServiceLogin", params=params, cookies=cookies, headers=gb.config.headers)

    body = bs(req.text, 'html.parser')
    
    params = {x.attrs["name"]:x.attrs["value"] for x in body.find_all("input", {"type":"hidden"})}

    headers = {**gb.config.headers, **{"Content-Type": "application/x-www-form-urlencoded"}}
    req = await as_client.post(f"https://{domain}/accounts/SetOSID", cookies=cookies, data=params, headers=headers)

    if not "OSID" in req.cookies:
        raise GHuntOSIDAuthError("[-] No OSID header detected, exiting...")

    generated_osids[service] = req.cookies["OSID"]

async def gen_osids(as_client: httpx.AsyncClient, cookies: Dict[str, str], osids: List[str]) -> Dict[str, str]:
    """
        Generate OSIDs of given services names,
        contained in the "osids" dict argument.
    """
    generated_osids = {}
    tasks = [gen_osid(as_client, cookies, generated_osids, service) for service in osids]
    await asyncio.gather(*tasks)

    return generated_osids

async def check_cookies(as_client: httpx.AsyncClient, cookies: Dict[str, str]) -> bool:
    """Checks the validity of given cookies."""
    continue_url = "https://www.google.com/robots.txt"
    params = {"continue": continue_url}
    req = await as_client.get("https://accounts.google.com/CheckCookie", params=params, cookies=cookies)
    return req.status_code == 302 and not req.headers.get("Location", "").startswith(("https://support.google.com", "https://accounts.google.com/CookieMismatch"))

async def check_osid(as_client: httpx.AsyncClient, cookies: Dict[str, str], service: str) -> bool:
    """Checks the validity of given OSID."""
    domain = get_domain_of_service(service)
    wanted = ["authuser", "continue", "osidt", "ifkv"]
    req = await as_client.get(f"https://accounts.google.com/ServiceLogin?service={service}&osid=1&continue=https://{domain}/&followup=https://{domain}/&authuser=0",
                    cookies=cookies, headers=gb.config.headers)

    body = bs(req.text, 'html.parser')
    params = [x.attrs["name"] for x in body.find_all("input", {"type":"hidden"})]
    if not all([param in wanted for param in params]):
        return False

    return True

async def check_osids(as_client: httpx.AsyncClient, cookies: Dict[str, str], osids: Dict[str, str]) -> bool:
    """Checks the validity of given OSIDs."""
    tasks = [check_osid(as_client, cookies, service) for service in osids]
    results = await asyncio.gather(*tasks)
    return all(results)

async def check_master_token(as_client: httpx.AsyncClient, master_token: str) -> str:
    """Checks the validity of the android master token."""
    try:
        await android_oauth_app(as_client, master_token, "com.google.android.play.games", ["https://www.googleapis.com/auth/games.firstparty"])
    except GHuntAndroidAppOAuth2Error:
        return False
    return True

async def gen_cookies_and_osids(as_client: httpx.AsyncClient, ghunt_creds: GHuntCreds, osids: list[str]=[*services_baseurls.keys()]):
    from ghunt.apis.accounts import Accounts
    accounts_api = Accounts(ghunt_creds)
    is_logged_in, uber_auth = await accounts_api.OAuthLogin(as_client)
    if not is_logged_in:
        raise GHuntLoginError("[-] Not logged in.")

    params = {
        "uberauth": uber_auth,
        "continue": "https://www.google.com",
        "source": "ChromiumAccountReconcilor",
        "externalCcResult": "doubleclick:null,youtube:null"
    }

    req = await as_client.get("https://accounts.google.com/MergeSession", params=params)
    cookies = dict(req.cookies)
    ghunt_creds.cookies = cookies
    osids = await gen_osids(as_client, cookies, osids)
    ghunt_creds.osids = osids

async def check_and_gen(as_client: httpx.AsyncClient, ghunt_creds: GHuntCreds):
    """Checks the validity of the cookies and generate new ones if needed."""
    if not await check_cookies(as_client, ghunt_creds.cookies):
        await gen_cookies_and_osids(as_client, ghunt_creds)
        if not await check_cookies(as_client, ghunt_creds.cookies):
            raise GHuntLoginError("[-] Can't generate cookies after multiple retries. Exiting...")

    ghunt_creds.save_creds(silent=True)
    gb.rc.print("[+] Authenticated !\n", style="sea_green3")

def auth_dialog() -> Tuple[Dict[str, str], str] :
    """
        Launch the dialog that asks the user
        how he want to generate its credentials.
    """
    choices = ("You can facilitate configuring GHunt by using the GHunt Companion extension on Firefox, Chrome, Edge and Opera here :\n"
                "=> https://github.com/mxrch/ghunt_companion\n\n"
                "[1] (Companion) Put GHunt on listening mode (currently not compatible with docker)\n"
                "[2] (Companion) Paste base64-encoded authentication\n"
                "[3] Enter the oauth_token (starts with \"oauth2_4/\")\n"
                "[4] Enter the master token (starts with \"aas_et/\")\n"
                "Choice => ")

    oauth_token = ""
    master_token = ""
    choice = input(choices)
    if choice in ["1", "2"]:
        if choice == "1":
            received_data = listener.run()
        elif choice == "2":
            received_data = input("Paste the encoded credentials here => ")
        data = json.loads(base64.b64decode(received_data))
        oauth_token = data["oauth_token"]

    elif choice == "3":
        oauth_token = input(f"OAuth token => ").strip('" ')

    elif choice == "4":
        master_token = input(f"Master token => ").strip('" ')

    else:
        print("Please choose a valid choice. Exiting...")
        exit()

    return oauth_token, master_token

async def load_and_auth(as_client: httpx.AsyncClient, help=True) -> GHuntCreds:
    """Returns an authenticated GHuntCreds object."""
    creds = GHuntCreds()
    try:
        creds.load_creds()
    except GHuntInvalidSession as e:
        if help:
            raise GHuntInvalidSession(f"Please generate a new session by doing => ghunt login") from e
        else:
            raise e

    await check_and_gen(as_client, creds)

    return creds


================================================
FILE: ghunt/helpers/banner.py
================================================
from ghunt import globals as gb

def show_banner():

    banner = """
    [red] .d8888b.  [/][blue]888    888[/][red]                   888    
    [/][red]d88P  Y88b [/][blue]888    888[/][red]                   888    
    [/][yellow]888    [/][red]888 [/][blue]888    888[/][red]                   888    
    [/][yellow]888        [/][blue]8888888888[/][green] 888  888[/][yellow] 88888b. [/][red] 888888 
    [/][yellow]888  [/][blue]88888 [/][blue]888    888[/][green] 888  888[/][yellow] 888 "88b[/][red] 888    
    [/][yellow]888    [/][blue]888 [/][blue]888    888[/][green] 888  888[/][yellow] 888  888[/][red] 888    
    [/][green]Y88b  d88P [/][blue]888    888[/][green] Y88b 888[/][yellow] 888  888[/][red] Y88b.  
    [/][green] "Y8888P88 [/][blue]888    888[/][green]  "Y88888[/][yellow] 888  888[/][red]  "Y888[/red] v2

             [bold]By: mxrch (🐦 [deep_sky_blue1][link=https://x.com/mxrchreborn]@mxrchreborn[/link][/deep_sky_blue1])
       [indian_red1]Support my work on GitHub Sponsors ! 💖[/indian_red1][/bold]
    """
                                                
    gb.rc.print(banner)

================================================
FILE: ghunt/helpers/calendar.py
================================================
from xmlrpc.client import Boolean
from dateutil.relativedelta import relativedelta
from beautifultable import BeautifulTable
import httpx

from typing import *
from copy import deepcopy

from ghunt.parsers.calendar import Calendar, CalendarEvents
from ghunt.objects.base import GHuntCreds
from ghunt.objects.utils import TMPrinter
from ghunt.apis.calendar import CalendarHttp


async def fetch_all(ghunt_creds: GHuntCreds, as_client: httpx.AsyncClient, email_address: str) -> Tuple[Boolean, Calendar, CalendarEvents]:
    calendar_api = CalendarHttp(ghunt_creds)
    found, calendar = await calendar_api.get_calendar(as_client, email_address)
    if not found:
        return False, None, None
    tmprinter = TMPrinter()
    _, events = await calendar_api.get_events(as_client, email_address, params_template="max_from_beginning")
    next_page_token = deepcopy(events.next_page_token)
    while next_page_token:
        tmprinter.out(f"[~] Dumped {len(events.items)} events...")
        _, new_events = await calendar_api.get_events(as_client, email_address, params_template="max_from_beginning", page_token=next_page_token)
        events.items += new_events.items
        next_page_token = deepcopy(new_events.next_page_token)
    tmprinter.clear()
    return True, calendar, events

def out(calendar: Calendar, events: CalendarEvents, email_address: str, display_name="", limit=5):
    """
        Output fetched calendar events.
        if limit = 0, = all events are shown
    """

    ### Calendar

    print(f"Calendar ID : {calendar.id}")
    if calendar.summary != calendar.id:
        print(f"[+] Calendar Summary : {calendar.summary}")
    print(f"Calendar Timezone : {calendar.time_zone}\n")

    ### Events
    target_events = events.items[-limit:]
    if target_events:
        print(f"[+] {len(events.items)} event{'s' if len(events.items) > 1 else ''} dumped ! Showing the last {len(target_events)} one{'s' if len(target_events) > 1 else ''}...\n")

        table = BeautifulTable()
        table.set_style(BeautifulTable.STYLE_GRID)
        table.columns.header = ["Name", "Datetime (UTC)", "Duration"]

        for event in target_events:
            title = "/"
            if event.summary:
                title = event.summary
            duration = "?"
            if event.end.date_time and event.start.date_time:
                duration = relativedelta(event.end.date_time, event.start.date_time)
                if duration.days or duration.hours or duration.minutes:
                    duration = (f"{(str(duration.days) + ' day' + ('s' if duration.days > 1 else '')) if duration.days else ''} "
                        f"{(str(duration.hours) + ' hour' + ('s' if duration.hours > 1 else '')) if duration.hours else ''} "
                        f"{(str(duration.minutes) + ' minute' + ('s' if duration.minutes > 1 else '')) if duration.minutes else ''}").strip()         

            date = "?"
            if event.start.date_time:
                date = event.start.date_time.strftime("%Y/%m/%d %H:%M:%S")
            table.rows.append([title, date, duration])

        print(table)

        print(f"\n🗃️ Download link :\n=> https://calendar.google.com/calendar/ical/{email_address}/public/basic.ics")
    else:
        print("[-] No events dumped.")

    ### Names

    names = set()
    for event in events.items:
        if event.creator.email == email_address and (name := event.creator.display_name) and name != display_name:
            names.add(name)
    if names:
        print("\n[+] Found other names used by the target :")
        for name in names:
            print(f"- {name}")

================================================
FILE: ghunt/helpers/drive.py
================================================
from typing import *

from ghunt.parsers.drive import DriveComment, DriveCommentList, DriveCommentReply, DriveFile
from ghunt.objects.base import DriveExtractedUser
from ghunt.helpers.utils import oprint # TEMP


def get_users_from_file(file: DriveFile) -> List[DriveExtractedUser]:
    """
        Extracts the users from the permissions of a Drive file,
        and the last modifying user.
    """
    
    users: Dict[str, DriveExtractedUser] = {}
    for perms in [file.permissions, file.permissions_summary.select_permissions]:
        for perm in perms:
            if not perm.email_address:
                continue
            #oprint(perm)
            user = DriveExtractedUser()
            user.email_address = perm.email_address
            user.gaia_id = perm.user_id
            user.name = perm.name
            user.role = perm.role
            users[perm.email_address] = user

    # Last modifying user
    target_user = file.last_modifying_user
    if target_user.id:
        email = target_user.email_address
        if not email:
            email = target_user.email_address_from_account
        if not email:
            return users

        if email in users:
            users[email].is_last_modifying_user = True

    return list(users.values())

def get_comments_from_file(comments: DriveCommentList) -> List[Tuple[str, Dict[str, any]]]:
    """
        Extracts the comments and replies of a Drive file.
    """

    def update_stats(authors: List[Dict[str, Dict[str, any]]], comment: DriveComment|DriveCommentReply):
        name = comment.author.display_name
        pic_url = comment.author.picture.url
        key = f"{name}${pic_url}" # Two users can have the same name, not the same picture URL (I hope so)
                                  # So we do this to make users "unique"
        if key not in authors:
            authors[key] = {
                "name": name,
                "pic_url": pic_url,
                "count": 0
            }
        authors[key]["count"] += 1

    authors: Dict[str, Dict[str, any]] = {}
    for comment in comments.items:
        update_stats(authors, comment)
        for reply in comment.replies:
            update_stats(authors, reply)

    return sorted(authors.items(), key=lambda k_v: k_v[1]['count'], reverse=True)

================================================
FILE: ghunt/helpers/gcp.py
================================================
import dns.message
import dns.asyncquery
import httpx

from ghunt.objects.base import GHuntCreds
from ghunt.apis.identitytoolkit import IdentityToolkitHttp


async def is_cloud_functions_panel_existing(project_id: str):
    q = dns.message.make_query(f"endpoints.{project_id}.cloud.goog", "A")
    r = await dns.asyncquery.tcp(q, "8.8.8.8")
    return bool(r.answer)

async def project_nb_from_key(as_client: httpx.AsyncClient, ghunt_creds: GHuntCreds, api_key: str, fallback=True) -> str|None:
    identitytoolkit_api = IdentityToolkitHttp(ghunt_creds)
    found, project_config = await identitytoolkit_api.get_project_config(as_client, api_key)
    if found:
        return project_config.project_id
    if fallback:
        # Fallback on fetching the project number by producing an error
        import json
        import re
        req = await as_client.get("https://blobcomments-pa.clients6.google.com/$discovery/rest", params={"key": api_key})
        try:
            data = json.loads(req.text)
            return re.findall(r'\d{12}', data["error"]["message"])[0]
        except Exception:
            pass
    return None

================================================
FILE: ghunt/helpers/gmail.py
================================================
import httpx


async def is_email_registered(as_client: httpx.AsyncClient, email: str) -> bool:
    """
        Abuse the gxlu endpoint to check if any email address
        is registered on Google. (not only gmail accounts)
    """
    req = await as_client.get(f"https://mail.google.com/mail/gxlu", params={"email": email})
    return "Set-Cookie" in req.headers

================================================
FILE: ghunt/helpers/gmaps.py
================================================
from dateutil.relativedelta import relativedelta
from datetime import datetime
import json
from geopy import distance
from geopy.geocoders import Nominatim
from typing import *

import httpx
from alive_progress import alive_bar

from ghunt import globals as gb
from ghunt.objects.base import *
from ghunt.helpers.utils import *
from ghunt.objects.utils import *
from ghunt.helpers.knowledge import get_gmaps_type_translation


def get_datetime(datepublished: str):
    """
        Get an approximative date from the maps review date
        Examples : 'last 2 days', 'an hour ago', '3 years ago'
    """
    if datepublished.split()[0] in ["a", "an"]:
        nb = 1
    else:
        if datepublished.startswith("last"):
            nb = int(datepublished.split()[1])
        else:
            nb = int(datepublished.split()[0])

    if "minute" in datepublished:
        delta = relativedelta(minutes=nb)
    elif "hour" in datepublished:
        delta = relativedelta(hours=nb)
    elif "day" in datepublished:
        delta = relativedelta(days=nb)
    elif "week" in datepublished:
        delta = relativedelta(weeks=nb)
    elif "month" in datepublished:
        delta = relativedelta(months=nb)
    elif "year" in datepublished:
        delta = relativedelta(years=nb)
    else:
        delta = relativedelta()

    return (datetime.today() - delta).replace(microsecond=0, second=0)

async def get_reviews(as_client: httpx.AsyncClient, gaia_id: str) -> Tuple[str, Dict[str, int]]:
    """Extracts the target's statistics, reviews and photos."""
    stats = {}

    print("Getting statistics")
    req = await as_client.get(f"https://www.google.com/locationhistory/preview/mas?authuser=0&hl=en&gl=us&pb={gb.config.templates['gmaps_pb']['stats'].format(gaia_id)}")
    if req.status_code == 302 and req.headers["Location"].startswith("https://www.google.com/sorry/index"):
        return "failed", stats

    data = json.loads(req.text[5:])
    if not data[16][8]:
        return "empty", stats
    stats = {sec[6]:sec[7] for sec in data[16][8][0]}
    total_reviews = stats["Reviews"] + stats["Ratings"] + stats["Photos"]
    if not total_reviews:
        return "empty", stats

    # # with alive_bar(total_reviews, receipt=False) as bar:
    # for category in ["reviews", "photos"]:
    #     first = True
    #     while True:
    #         if first:
    #             print(f"Getting {category} (first)")
    #             req = await as_client.get(f"https://www.google.com/locationhistory/preview/mas?authuser=0&hl=en&gl=us&pb={gb.config.templates['gmaps_pb'][category]['first'].format(gaia_id)}")
    #             first = False
    #         else:
    #             print(f"Getting {category} (next)")
    #             req = await as_client.get(f"https://www.google.com/locationhistory/preview/mas?authuser=0&hl=en&gl=us&pb={gb.config.templates['gmaps_pb'][category]['page'].format(gaia_id, next_page_token)}")
    #         data = json.loads(req.text[5:])

    #         new_reviews = []
    #         new_photos = []
    #         next_page_token = ""

    #         # Reviews
    #         if category == "reviews":
    #             if not data[45]:
    #                 return "private", stats, [], []
    #             reviews_data = data[45][0]
    #             if not reviews_data:
    #                 break
    #             for review_data in reviews_data:
    #                 review = MapsReview()
    #                 # from pprint import pprint; import pdb; pdb.set_trace()
    #                 review.id = review_data[2][0]
    #                 review.date = datetime.utcfromtimestamp(review_data[2][1][3] / 1000000)
    #                 if len(review_data[2][2]) > 15 and review_data[2][2][15]:
    #                     review.comment = review_data[2][2][15][0][0]
    #                 review.rating = review_data[2][2][0][0]

    #                 review.location.id = review_data[4][14][0]
    #                 review.location.name = review_data[4][2]
    #                 review.location.address = review_data[4][3]
    #                 review.location.tags = review_data[4][4] if review_data[4][4] else []
    #                 review.location.types = [x for x in review_data[4][8] if x]
    #                 if review_data[4][0]:
    #                     review.location.position.latitude = review_data[4][0][2]
    #                     review.location.position.longitude = review_data[4][0][3]
    #                 # if len(review_data[1]) > 31 and review_data[1][31]:
    #                     # print(f"Cost level : {review_data[1][31]}")
    #                     # review.location.cost_level = len(review_data[1][31])
    #                 new_reviews.append(review)
    #                 # bar()

    #             agg_reviews += new_reviews

    #             if not new_reviews or len(data[45]) < 2 or not data[45][1]:
    #                 # from pprint import pprint; import pdb; pdb.set_trace()
    #                 break
    #             next_page_token = data[45][1].strip("=")

    #         # Photos
    #         elif category == "photos" :
    #             if not data[22]:
    #                 return "private", stats, [], []
    #             photos_data = data[22][1]
    #             if not photos_data:
    #                 break
    #             for photo_data in photos_data:
    #                 photos = MapsPhoto()
    #                 photos.id = photo_data[0][10]
    #                 photos.url = photo_data[0][6][0].split("=")[0]
    #                 date = photo_data[0][21][6][8]
    #                 photos.date = datetime(date[0], date[1], date[2], date[3]) # UTC
    #                 # photos.approximative_date = get_datetime(date[8][0]) # UTC

    #                 if len(photo_data) > 1:
    #                     photos.location.id = photo_data[1][14][0]
    #                     photos.location.name = photo_data[1][2]
    #                     photos.location.address = photo_data[1][3]
    #                     photos.location.tags = photo_data[1][4] if photo_data[1][4] else []
    #                     photos.location.types = [x for x in photo_data[1][8] if x] if photo_data[1][8] else []
    #                     if photo_data[1][0]:
    #                         photos.location.position.latitude = photo_data[1][0][2]
    #                         photos.location.position.longitude = photo_data[1][0][3]
    #                     if len(photo_data[1]) > 31 and photo_data[1][31]:
    #                         photos.location.cost_level = len(photo_data[1][31])
    #                 new_photos.append(photos)
    #                 # bar()

    #             agg_photos += new_photos

    #             if not new_photos or len(data[22]) < 4 or not data[22][3]:
    #                 break
    #             next_page_token = data[22][3].strip("=")

    return "", stats

def avg_location(locs: Tuple[float, float]):
    """
        Calculates the average location
        from a list of (latitude, longitude) tuples.
    """
    latitude = []
    longitude = []
    for loc in locs:
        latitude.append(loc[0])
        longitude.append(loc[1])

    latitude = sum(latitude) / len(latitude)
    longitude = sum(longitude) / len(longitude)
    return latitude, longitude

def translate_confidence(percents: int):
    """Translates the percents number to a more human-friendly text"""
    if percents >= 100:
        return "Extremely high"
    elif percents >= 80:
        return "Very high"
    elif percents >= 60:
        return "Little high"
    elif percents >= 40:
        return "Okay"
    elif percents >= 20:
        return "Low"
    elif percents >= 10:
        return "Very low"
    else:
        return "Extremely low"

def sanitize_location(location: Dict[str, str]):
    """Returns the nearest place from a Nomatim location response."""
    not_country = False
    not_town = False
    town = "?"
    country = "?"
    if "city" in location:
        town = location["city"]
    elif "village" in location:
        town = location["village"]
    elif "town" in location:
        town = location["town"]
    elif "municipality" in location:
        town = location["municipality"]
    else:
        not_town = True
    if not "country" in location:
        not_country = True
        location["country"] = country
    if not_country and not_town:
        return False
    location["town"] = town
    return location

def calculate_probable_location(geolocator: Nominatim, reviews_and_photos: List[MapsReview|MapsPhoto], gmaps_radius: int):
    """Calculates the probable location from a list of reviews and the max radius."""
    tmprinter = TMPrinter()
    radius = gmaps_radius

    locations = {}
    tmprinter.out(f"Calculation of the distance of each review...")
    for nb, review in enumerate(reviews_and_photos):
        if not review.location.position.latitude or not review.location.position.longitude:
            continue
        if review.location.id not in locations:
            locations[review.location.id] = {"dates": [], "locations": [], "range": None, "score": 0}
        location = (review.location.position.latitude, review.location.position.longitude)
        for review2 in reviews_and_photos:
            location2 = (review2.location.position.latitude, review2.location.position.longitude)
            dis = distance.distance(location, location2).km

            if dis <= radius:
                locations[review.location.id]["dates"].append(review2.date)
                locations[review.location.id]["locations"].append(location2)

        maxdate = max(locations[review.location.id]["dates"])
        mindate = min(locations[review.location.id]["dates"])
        locations[review.location.id]["range"] = maxdate - mindate
        tmprinter.out(f"Calculation of the distance of each review ({nb}/{len(reviews_and_photos)})...")

    tmprinter.clear()

    locations = {k: v for k, v in
                 sorted(locations.items(), key=lambda k: len(k[1]["locations"]), reverse=True)}  # We sort it

    tmprinter.out("Identification of redundant areas...")
    to_del = []
    for id in locations:
        if id in to_del:
            continue
        for id2 in locations:
            if id2 in to_del or id == id2:
                continue
            if all([loc in locations[id]["locations"] for loc in locations[id2]["locations"]]):
                to_del.append(id2)
    for hash in to_del:
        del locations[hash]

    tmprinter.out("Calculating confidence...")

    maxrange = max([locations[hash]["range"] for hash in locations])
    maxlen = max([len(locations[hash]["locations"]) for hash in locations])
    minreq = 3
    mingroups = 3

    score_steps = 4
    for hash, loc in locations.items():
        if len(loc["locations"]) == maxlen:
            locations[hash]["score"] += score_steps * 4
        if loc["range"] == maxrange:
            locations[hash]["score"] += score_steps * 3
        if len(locations) >= mingroups:
            others = sum([len(locations[h]["locations"]) for h in locations if h != hash])
            if len(loc["locations"]) > others:
                locations[hash]["score"] += score_steps * 2
        if len(loc["locations"]) >= minreq:
            locations[hash]["score"] += score_steps

    panels = sorted(set([loc["score"] for loc in locations.values()]), reverse=True)

    maxscore = sum([p * score_steps for p in range(1, score_steps + 1)])
    for panel in panels:
        locs = [loc for loc in locations.values() if loc["score"] == panel]
        if len(locs[0]["locations"]) == 1:
            panel /= 2
        if len(reviews_and_photos) < 4:
            panel /= 2
        confidence = translate_confidence(panel / maxscore * 100)
        for nb, loc in enumerate(locs):
            avg = avg_location(loc["locations"])
            while True:
                try:
                    location = geolocator.reverse(f"{avg[0]}, {avg[1]}", timeout=10).raw["address"]
                    break
                except:
                    pass
            location = sanitize_location(location)
            locs[nb]["avg"] = location
            del locs[nb]["locations"]
            del locs[nb]["score"]
            del locs[nb]["range"]
            del locs[nb]["dates"]

        tmprinter.clear()

        return confidence, locs

def output(err: str, stats: Dict[str, int], gaia_id: str):
    """Pretty print the Maps results, and do some guesses."""

    print(f"\nProfile page : https://www.google.com/maps/contrib/{gaia_id}/reviews")

    if err == "failed":
        print("\n[-] Your IP has been blocked by Google. Try again later.")
        return
    
    elif err == "empty":
        print("\n[-] No reviews, ratings or photos found.")
        return

    print("\n[Statistics]")
    for section, number in stats.items():
        if number:
            print(f"{section} : {number}")

    if err == "private":
        print("\n[-] Reviews are private.")
        return

    # I removed the costs calculation because of a Google update : https://github.com/mxrch/GHunt/issues/529

    # costs_table = {
    #     1: "Inexpensive",
    #     2: "Moderately expensive",
    #     3: "Expensive",
    #     4: "Very expensive"
    # }

    # total_costs = 0
    # costs_stats = {x:0 for x in range(1,5)}
    # for review in reviews_and_photos:
    #     if review.location.cost_level:
    #         costs_stats[review.location.cost_level] += 1
    #         total_costs += 1
    # costs_stats = dict(sorted(costs_stats.items(), key=lambda item: item[1], reverse=True)) # We sort the dict by cost popularity

    # if total_costs:
    #     print("[Costs]")
    #     for cost, desc in costs_table.items():
    #         line = f"> {ppnb(round(costs_stats[cost]/total_costs*100, 1))}% {desc} ({costs_stats[cost]})"
    #         style = ""
    #         if not costs_stats[cost]:
    #             style = "bright_black"
    #         elif costs_stats[cost] == list(costs_stats.values())[0]:
    #             style = "spring_green1"
    #         gb.rc.print(line, style=style)
            
    #     avg_costs = round(sum([x*y for x,y in costs_stats.items()]) / total_costs)
    #     print(f"\n[+] Average costs : {costs_table[avg_costs]}")
    # else:
    #     print("[-] No costs data.")

    # types = {}
    # for review in reviews_and_photos:
    #     for type in review.location.types:
    #         if type not in types:
    #             types[type] = 0
    #         types[type] += 1
    # types = dict(sorted(types.items(), key=lambda item: item[1], reverse=True))

    # types_and_tags = {}
    # for review in reviews_and_photos:
    #     for type in review.location.types:
    #         if type not in types_and_tags:
    #             types_and_tags[type] = {}
    #         for tag in review.location.tags:
    #             if tag not in types_and_tags[type]:
    #                 types_and_tags[type][tag] = 0
    #             types_and_tags[type][tag] += 1
    #         types_and_tags[type] = dict(sorted(types_and_tags[type].items(), key=lambda item: item[1], reverse=True))
    # types_and_tags = dict(sorted(types_and_tags.items()))

    # if types_and_tags:
    #     print("\nTarget's locations preferences :")

    #     unknown_trads = []
    #     for type, type_count in types.items():
    #         tags_counts = types_and_tags[type]
    #         translation = get_gmaps_type_translation(type)
    #         if not translation:
    #             unknown_trads.append(type)
    #         gb.rc.print(f"\n🏨 [underline]{translation if translation else type.title()} [{type_count}]", style="bold")
    #         nb = 0
    #         for tag, tag_count in list(tags_counts.items()):
    #             if nb >= 7:
    #                 break
    #             elif tag.lower() == type:
    #                 continue
    #             print(f"- {tag} ({tag_count})")
    #             nb += 1

    #     if unknown_trads:
    #         print(f"\n⚠️ The following gmaps types haven't been found in GHunt\'s knowledge.")
    #         for type in unknown_trads:
    #             print(f"- {type}")
    #         print("Please open an issue on the GHunt Github or submit a PR to add it !")

    # geolocator = Nominatim(user_agent="nominatim")

    # confidence, locations = calculate_probable_location(geolocator, reviews_and_photos, gb.config.gmaps_radius)
    # print(f"\n[+] Probable location (confidence => {confidence}) :")

    # loc_names = []
    # for loc in locations:
    #     loc_names.append(
    #         f"- {loc['avg']['town']}, {loc['avg']['country']}"
    #     )

    # loc_names = set(loc_names)  # delete duplicates
    # for loc in loc_names:
    #     print(loc)

================================================
FILE: ghunt/helpers/ia.py
================================================
import os

from ghunt import globals as gb
from ghunt.apis.vision import VisionHttp

import httpx

from base64 import b64encode
import asyncio


async def detect_face(vision_api: VisionHttp, as_client: httpx.AsyncClient, image_url: str) -> None:
    req = await as_client.get(image_url)
    encoded_image = b64encode(req.content).decode()
    
    are_faces_found = False
    faces_results = None

    for retry in range(5):
        rate_limited, are_faces_found, faces_results = await vision_api.detect_faces(as_client, image_content=encoded_image)
        if not rate_limited:
            break
        await asyncio.sleep(0.5)
    else:
        print("\n[-] Vision API keeps rate-limiting.")
        exit(os.EX_UNAVAILABLE)

    if are_faces_found:
        if len(faces_results.face_annotations) > 1:
            gb.rc.print(f"🎭 {len(faces_results.face_annotations)} faces detected !", style="italic")
        else:
            gb.rc.print(f"🎭 [+] Face detected !", style="italic bold")
    else:
        gb.rc.print(f"🎭 No face detected.", style="italic bright_black")

    return faces_results

================================================
FILE: ghunt/helpers/iam.py
================================================
import httpx
import asyncio

from ghunt.objects.base import GHuntCreds
from ghunt.apis.mobilesdk import MobileSDKPaHttp
from ghunt.knowledge import iam
from ghunt.helpers.utils import chunkify

from typing import *


async def test_all_permissions(as_client: httpx.AsyncClient, ghunt_creds: GHuntCreds, project_identifier: str):

    async def test_permission(as_client: httpx.AsyncClient, mobilesdk_api: MobileSDKPaHttp, limiter: asyncio.Semaphore,
                                project_identifier: str, permissions: List[str], results: List[str]):
        async with limiter:
            _, perms = await mobilesdk_api.test_iam_permissions(as_client, project_identifier, permissions)
            results.extend(perms)

    mobilesdk_api = MobileSDKPaHttp(ghunt_creds)
    results: List[str] = []
    limiter = asyncio.Semaphore(20)
    tasks = []
    for perms_chunk in chunkify(iam.permissions, 100): # Max 100 permissions per request
        tasks.append(test_permission(as_client, mobilesdk_api, limiter, project_identifier, perms_chunk, results))
    await asyncio.gather(*tasks)

    results = list(set(results))
    print(results)

================================================
FILE: ghunt/helpers/knowledge.py
================================================
from ghunt.knowledge.services import services_baseurls
from ghunt.knowledge.keys import keys
from ghunt.knowledge.maps import types_translations
from ghunt.knowledge.people import user_types
from ghunt.knowledge.sig import sigs
from ghunt.errors import GHuntKnowledgeError

from typing import *


def get_domain_of_service(service: str) -> str:
    if service not in services_baseurls:
        raise GHuntKnowledgeError(f'The service "{service}" has not been found in GHunt\'s services knowledge.')
    return services_baseurls.get(service)

def get_origin_of_key(key_name: str) -> str:
    if key_name not in keys:
        raise GHuntKnowledgeError(f'The key "{key_name}" has not been found in GHunt\'s API keys knowledge.')
    return keys.get(key_name, {}).get("origin")

def get_api_key(key_name: str) -> str:
    if key_name not in keys:
        raise GHuntKnowledgeError(f'The key "{key_name}" has not been found in GHunt\'s API keys knowledge.')
    return keys.get(key_name, {}).get("key")

def get_gmaps_type_translation(type_name: str) -> str:
    if type_name not in types_translations:
        raise GHuntKnowledgeError(f'The gmaps type "{type_name}" has not been found in GHunt\'s knowledge.\nPlease open an issue on the GHunt Github or submit a PR to add it !')
    return types_translations.get(type_name)

def get_user_type_definition(type_name: str) -> str:
    if type_name not in user_types:
        raise GHuntKnowledgeError(f'The user type "{type_name}" has not been found in GHunt\'s knowledge.\nPlease open an issue on the GHunt Github or submit a PR to add it !')
    return user_types.get(type_name)

def get_package_sig(package_name: str) -> str:
    if package_name not in sigs:
        raise GHuntKnowledgeError(f'The package name "{package_name}" has not been found in GHunt\'s SIGs knowledge.')
    return sigs.get(package_name)

================================================
FILE: ghunt/helpers/listener.py
================================================
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import *

from ghunt.objects.base import SmartObj


class DataBridge(SmartObj):
    def __init__(self):
        self.data = None

class Server(BaseHTTPRequestHandler):    
    def _set_response(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.send_header('Access-Control-Allow-Origin','*')
        self.end_headers()

    def do_GET(self):
        if self.path == "/ghunt_ping":
            self._set_response()
            self.wfile.write(b"ghunt_pong")

    def do_POST(self):
        if self.path == "/ghunt_feed":
            content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
            post_data = self.rfile.read(content_length) # <--- Gets the data itself
            self.data_bridge.data = post_data.decode('utf-8')

            self._set_response()
            self.wfile.write(b"ghunt_received_ok")

    def log_message(self, format, *args):
        return

def run(server_class=HTTPServer, handler_class=Server, port=60067):
    server_address = ('127.0.0.1', port)
    handler_class.data_bridge = DataBridge()
    server = server_class(server_address, handler_class)
    try:
        print(f"GHunt is listening on port {port}...")

        while True:
            server.handle_request()
            if handler_class.data_bridge.data:
                break

    except KeyboardInterrupt:
        print("[-] Exiting...")
        exit(os.CLD_KILLED)
    else:
        if handler_class.data_bridge.data:
            print("[+] Received cookies !")
            return handler_class.data_bridge.data

================================================
FILE: ghunt/helpers/playgames.py
================================================
from ghunt.objects.base import GHuntCreds
from ghunt.apis.playgames import PlayGames
from ghunt.apis.playgateway import PlayGatewayPaGrpc
from ghunt.parsers.playgames import Player, PlayerProfile
from ghunt.parsers.playgateway import PlayerSearchResult
from ghunt.objects.utils import TMPrinter

import httpx
from alive_progress import alive_bar

from typing import *


async def get_player(ghunt_creds: GHuntCreds, as_client: httpx.AsyncClient, player_id: str):
    playgames = PlayGames(ghunt_creds)

    tmprinter = TMPrinter()
    tmprinter.out("[~] Getting player profile...")
    is_found, player_profile = await playgames.get_profile(as_client, player_id)
    tmprinter.clear()
    if not is_found or not player_profile.profile_settings.profile_visible:
        return is_found, Player()
    
    playgateway_pa = PlayGatewayPaGrpc(ghunt_creds)
    player_stats = await playgateway_pa.get_player_stats(as_client, player_id)

    with alive_bar(player_stats.played_games_count, title="🚎 Fetching played games...", receipt=False) as bar:
        _, next_page_token, played_games = await playgames.get_played_games(as_client, player_id)
        bar(len(played_games.games))
        while next_page_token:
            _, next_page_token, new_played_games = await playgames.get_played_games(as_client, player_id, next_page_token)
            played_games.games += new_played_games.games
            bar(len(new_played_games.games))

    with alive_bar(player_stats.achievements_count, title="🚎 Fetching achievements...", receipt=False) as bar:
        _, next_page_token, achievements = await playgames.get_achievements(as_client, player_id)
        bar(len(achievements.achievements))
        while next_page_token:
            _, next_page_token, new_achievements = await playgames.get_achievements(as_client, player_id, next_page_token)
            achievements.achievements += new_achievements.achievements
            bar(len(new_achievements.achievements))

    player = Player(player_profile, played_games.games, achievements.achievements)
    return is_found, player

async def search_player(ghunt_creds: GHuntCreds, as_client: httpx.AsyncClient, query: str) -> List[PlayerSearchResult]:
    playgateway_pa = PlayGatewayPaGrpc(ghunt_creds)
    player_search_results = await playgateway_pa.search_player(as_client, query)
    return player_search_results.results

def output(player: Player):
    if not player.profile.profile_settings.profile_visible:
        print("\n[-] Profile is private.")
        return

    print("\n[+] Profile is public !")
    print(f"\n[+] Played to {len(player.played_games)} games")
    print(f"[+] Got {len(player.achievements)} achievements")

    if player.played_games:
        print(f"\n[+] Last played game : {player.profile.last_played_app.app_name} ({player.profile.last_played_app.timestamp_millis} UTC)")

        if player.achievements:
            app_ids_count = {}
            for achievement in player.achievements:
                if (app_id := achievement.app_id) not in app_ids_count:
                    app_ids_count[app_id] = 0
                app_ids_count[app_id] += 1
            app_ids_count = dict(sorted(app_ids_count.items(), key=lambda item: item[1], reverse=True))
            achiv_nb = list(app_ids_count.values())[0]
            target_game = None
            for game in player.played_games:
                if game.game_data.id == list(app_ids_count.keys())[0]:
                    target_game = game
                    break

            print(f"[+] Game with the most achievements : {target_game.game_data.name} ({achiv_nb})")

================================================
FILE: ghunt/helpers/playstore.py
================================================
import httpx


async def app_exists(as_client: httpx.AsyncClient, package: str) -> bool:
    params = {
        "id": package
    }
    req = await as_client.head(f"https://play.google.com/store/apps/details", params=params)
    return req.status_code == 200


================================================
FILE: ghunt/helpers/utils.py
================================================
from pathlib import Path
from PIL import Image
import hashlib
from typing import *
from time import time
from datetime import datetime, timezone
from dateutil.parser import isoparse
from copy import deepcopy
import jsonpickle
import json
from packaging.version import parse as parse_version

import httpx
import imagehash
from io import BytesIO

from ghunt import globals as gb
from ghunt import version as current_version
from ghunt.lib.httpx import AsyncClient


def get_httpx_client() -> httpx.AsyncClient:
    """
        Returns a customized to better support the needs of GHunt CLI users.
    """
    return AsyncClient(http2=True, timeout=15)
    # return AsyncClient(http2=True, timeout=15, proxies="http://127.0.0.1:8282", verify=False)

def oprint(obj: any) -> str:
    serialized = jsonpickle.encode(obj)
    pretty_output = json.dumps(json.loads(serialized), indent=2)
    print(pretty_output)

def chunkify(lst, n):
    """
        Cut a given list to chunks of n items.
    """
    k, m = divmod(len(lst), n)
    for i in range(n):
        yield lst[i*k+min(i, m):(i+1)*k+min(i+1, m)]

def within_docker() -> bool:
    return Path('/.dockerenv').is_file()

def gen_sapisidhash(sapisid: str, origin: str, timestamp: str = str(int(time()))) -> str:
    return f"{timestamp}_{hashlib.sha1(' '.join([timestamp, sapisid, origin]).encode()).hexdigest()}"

def inject_osid(cookies: Dict[str, str], osids: Dict[str, str], service: str) -> Dict[str, str]:
    cookies_with_osid = deepcopy(cookies)
    cookies_with_osid["OSID"] = osids[service]
    return cookies_with_osid
    
def is_headers_syntax_good(headers: Dict[str, str]) -> bool:
    try:
        httpx.Headers(headers)
        return True
    except:
        return False

async def get_url_image_flathash(as_client: httpx.AsyncClient, image_url: str) -> str:
    req = await as_client.get(image_url)
    img = Image.open(BytesIO(req.content))
    flathash = imagehash.average_hash(img)
    return str(flathash)

async def is_default_profile_pic(as_client: httpx.AsyncClient, image_url: str) -> Tuple[bool, str]:
    """
        Returns a boolean which indicates if the image_url
        is a default profile picture, and the flathash of
        the image.
    """
    flathash = await get_url_image_flathash(as_client, image_url)
    if imagehash.hex_to_flathash(flathash, 8) - imagehash.hex_to_flathash("000018183c3c0000", 8) < 10 :
        return True, str(flathash)
    return False, str(flathash)

def get_class_name(obj) -> str:
        return str(obj).strip("<>").split(" ")[0]

def get_datetime_utc(date_str):
    """Converts ISO to datetime object in UTC"""
    date = isoparse(date_str)
    margin = date.utcoffset()
    return date.replace(tzinfo=timezone.utc) - margin

def ppnb(nb: float|int) -> float:
    """
        Pretty print float number
        Ex: 3.9 -> 3.9
            4.0 -> 4
            4.1 -> 4.1
    """
    try:
        return int(nb) if nb % int(nb) == 0.0 else nb
    except ZeroDivisionError:
        if nb == 0.0:
            return 0
        else:
            return nb

def parse_oauth_flow_response(body: str):
    """
        Correctly format the response sent by android.googleapis.com
        during the Android OAuth2 Login Flow.
    """
    return {sp[0]:'='.join(sp[1:]) for x in body.split("\n") if (sp := x.split("="))}

def humanize_list(array: List[any]):
    """
        Transforms a list to a human sentence.
        Ex : ["reader", "writer", "owner"] -> "reader, writer and owner".
    """
    if len(array) <= 1:
        return ''.join(array)

    final = ""
    for nb, item in enumerate(array):
        if nb == 0:
            final += f"{item}"
        elif nb+1 < len(array):
            final += f", {item}"
        else:
            final += f" and {item}"
    return final

def unicode_patch(txt: str):
    bad_chars = {
        "é": "e",
        "è": "e",
        "ç": "c",
        "à": "a"
    }
    return txt.replace(''.join([*bad_chars.keys()]), ''.join([*bad_chars.values()]))

def show_version():
    new_version, new_metadata = check_new_version()
    print()
    gb.rc.print(f"> GHunt {current_version.metadata.get('version', '')} ({current_version.metadata.get('name', '')}) <".center(53), style="bold")
    print()
    if new_version:
        gb.rc.print(f"🥳 New version {new_metadata.get('version', '')} ({new_metadata.get('name', '')}) is available !", style="bold red")
        gb.rc.print(f"🤗 Run 'pipx upgrade ghunt' to update.", style="bold light_pink3")
    else:
        gb.rc.print("🎉 You are up to date !", style="light_pink3")
        

def check_new_version() -> tuple[bool, dict[str, str]]:
    """
        Checks if there is a new version of GHunt available.
    """
    req = httpx.get("https://raw.githubusercontent.com/mxrch/GHunt/master/ghunt/version.py")
    if req.status_code != 200:
        return False, {}
    
    raw = req.text.strip().removeprefix("metadata = ")
    data = json.loads(raw)
    new_version = data.get("version", "")
    new_name = data.get("name", "")

    if parse_version(new_version) > parse_version(current_version.metadata.get("version", "")):
        return True, {"version": new_version, "name": new_name}
    return False, {}

================================================
FILE: ghunt/knowledge/__init__.py
================================================


================================================
FILE: ghunt/knowledge/drive.py
================================================
default_file_capabilities = [
    'can_block_owner',
    'can_copy',
    'can_download',
    'can_print',
    'can_read',
    'can_remove_my_drive_parent'
]

default_folder_capabilities = [
    'can_block_owner',
    'can_download',
    'can_list_children',
    'can_print',
    'can_read',
    'can_remove_my_drive_parent'
]

request_fields = [
    'copyRequiresWriterPermission',
    'sourceAppId',
    'authorizedAppIds',
    'linkShareMetadata',
    'teamDriveId',
    'primaryDomainName',
    'approvalMetadata',
    'md5Checksum',
    'resourceKey',
    'quotaBytesUsed',
    'hasChildFolders',
    'fullFileExtension',
    'isAppAuthorized',
    'iconLink',
    'trashingUser',
    'title',
    'recency',
    'detectors',
    'exportLinks',
    'modifiedDate',
    'copyable',
    'description',
    'mimeType',
    'passivelySubscribed',
    'videoMediaMetadata',
    'headRevisionId',
    'customerId',
    'fileExtension',
    'originalFilename',
    'parents',
    'imageMediaMetadata',
    'recencyReason',
    'folderColorRgb',
    'createdDate',
    'labels',
    'abuseNoticeReason',
    'webViewLink',
    'driveId',
    'ownedByMe',
    'flaggedForAbuse',
    'lastModifyingUser',
    'thumbnailLink',
    'capabilities',
    'sharedWithMeDate',
    'primarySyncParentId',
    'sharingUser',
    'version',
    'permissionsSummary',
    'actionItems',
    'labelInfo',
    'explicitlyTrashed',
    'shared',
    'subscribed',
    'ancestorHasAugmentedPermissions',
    'writersCanShare',
    'permissions',
    'alternateLink',
    'hasLegacyBlobComments',
    'id',
    'userPermission',
    'hasThumbnail',
    'lastViewedByMeDate',
    'fileSize',
    'kind',
    'thumbnailVersion',
    'spaces',
    'organizationDisplayName',
    'abuseIsAppealable',
    'trashedDate',
    'folderFeatures',
    'webContentLink',
    'contentRestrictions',
    'shortcutDetails',
    'folderColor',
    'hasAugmentedPermissions'
]

mime_types = {
    "application/vnd.google-apps.audio": "Audio 🎧",
    "application/vnd.google-apps.document":	"Google Docs 📝",
    "application/vnd.google-apps.drive-sdk": "3rd party shortcut ↪️",
    "application/vnd.google-apps.drawing": "Google Drawing ✏️",
    "application/vnd.google-apps.file":	"Google Drive file 📄",
    "application/vnd.google-apps.folder": "Google Drive folder 🗂️",
    "application/vnd.google-apps.form":	"Google Forms 👨‍🏫",
    "application/vnd.google-apps.fusiontable": "Google Fusion Tables 🌶️",
    "application/vnd.google-apps.jam": "Google Jamboard 🖍️",
    "application/vnd.google-apps.map": "Google My Maps 📍",
    "application/vnd.google-apps.photo": "Photo 📷",
    "application/vnd.google-apps.presentation":	"Google Slides ❇️",
    "application/vnd.google-apps.script": "Google Apps Scripts 📜",
    "application/vnd.google-apps.shortcut":	"Shortcut ↩️",
    "application/vnd.google-apps.site":	"Google Sites 🌐",
    "application/vnd.google-apps.spreadsheet": "Google Sheets 📟",
    "application/vnd.google-apps.unknown": "Unknown ❔",
    "application/vnd.google-apps.video": "Video 📼",
    "application/pdf": "PDF Document 📕",
    "application/msword": "Microsoft Word document 📝",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "OpenXML Word document 📝",
    "application/vnd.ms-powerpoint.presentation.macroEnabled.12": "Microsoft Powerpoint with macros ❇️",
    "application/vnd.ms-excel": "Microsoft Excel spreadsheet 📟",
    "image/jpeg": "JPEG Image 🖼️",
    "audio/mpeg": "MPEG Audio 🎧",
    "video/mpeg": "MPEG Video 📼",
    "application/zip": "ZIP Archive 🗃️",
    "text/plain": "Plain Text 📃",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "OpenXML Spreadsheet document ❇️",
    "application/vnd.android.package-archive": "Android Package 📱",
    "application/vnd.google-apps.kix": "Google Apps 🈸"
}

================================================
FILE: ghunt/knowledge/iam.py
================================================
permissions = [
    "accessapproval.requests.approve",
    "accessapproval.requests.dismiss",
    "accessapproval.requests.get",
    "accessapproval.requests.invalidate",
    "accessapproval.requests.list",
    "accessapproval.serviceAccounts.get",
    "accessapproval.settings.delete",
    "accessapproval.settings.get",
    "accessapproval.settings.update",
    "actions.agent.claimContentProvider",
    "actions.agent.get",
    "actions.agent.update",
    "actions.agentVersions.create",
    "actions.agentVersions.delete",
    "actions.agentVersions.deploy",
    "actions.agentVersions.get",
    "actions.agentVersions.list",
    "aiplatform.annotationSpecs.create",
    "aiplatform.annotationSpecs.delete",
    "aiplatform.annotationSpecs.get",
    "aiplatform.annotationSpecs.list",
    "aiplatform.annotationSpecs.update",
    "aiplatform.annotations.create",
    "aiplatform.annotations.delete",
    "aiplatform.annotations.get",
    "aiplatform.annotations.list",
    "aiplatform.annotations.update",
    "aiplatform.artifacts.create",
    "aiplatform.artifacts.delete",
    "aiplatform.artifacts.get",
    "aiplatform.artifacts.list",
    "aiplatform.artifacts.update",
    "aiplatform.batchPredictionJobs.cancel",
    "aiplatform.batchPredictionJobs.create",
    "aiplatform.batchPredictionJobs.delete",
    "aiplatform.batchPredictionJobs.get",
    "aiplatform.batchPredictionJobs.list",
    "aiplatform.contexts.addContextArtifactsAndExecutions",
    "aiplatform.contexts.addContextChildren",
    "aiplatform.contexts.create",
    "aiplatform.contexts.delete",
    "aiplatform.contexts.get",
    "aiplatform.contexts.list",
    "aiplatform.contexts.queryContextLineageSubgraph",
    "aiplatform.contexts.update",
    "aiplatform.customJobs.cancel",
    "aiplatform.customJobs.create",
    "aiplatform.customJobs.delete",
    "aiplatform.customJobs.get",
    "aiplatform.customJobs.list",
    "aiplatform.dataItems.create",
    "aiplatform.dataItems.delete",
    "aiplatform.dataItems.get",
    "aiplatform.dataItems.list",
    "aiplatform.dataItems.update",
    "aiplatform.dataLabelingJobs.cancel",
    "aiplatform.dataLabelingJobs.create",
    "aiplatform.dataLabelingJobs.delete",
    "aiplatform.dataLabelingJobs.get",
    "aiplatform.dataLabelingJobs.list",
    "aiplatform.datasets.create",
    "aiplatform.datasets.delete",
    "aiplatform.datasets.export",
    "aiplatform.datasets.get",
    "aiplatform.datasets.import",
    "aiplatform.datasets.list",
    "aiplatform.datasets.update",
    "aiplatform.deploymentResourcePools.create",
    "aiplatform.deploymentResourcePools.delete",
    "aiplatform.deploymentResourcePools.get",
    "aiplatform.deploymentResourcePools.list",
    "aiplatform.deploymentResourcePools.queryDeployedModels",
    "aiplatform.deploymentResourcePools.update",
    "aiplatform.edgeDeploymentJobs.create",
    "aiplatform.edgeDeploymentJobs.delete",
    "aiplatform.edgeDeploymentJobs.get",
    "aiplatform.edgeDeploymentJobs.list",
    "aiplatform.edgeDeviceDebugInfo.get",
    "aiplatform.edgeDevices.create",
    "aiplatform.edgeDevices.delete",
    "aiplatform.edgeDevices.get",
    "aiplatform.edgeDevices.list",
    "aiplatform.edgeDevices.update",
    "aiplatform.endpoints.create",
    "aiplatform.endpoints.delete",
    "aiplatform.endpoints.deploy",
    "aiplatform.endpoints.explain",
    "aiplatform.endpoints.get",
    "aiplatform.endpoints.list",
    "aiplatform.endpoints.predict",
    "aiplatform.endpoints.undeploy",
    "aiplatform.endpoints.update",
    "aiplatform.entityTypes.create",
    "aiplatform.entityTypes.delete",
    "aiplatform.entityTypes.deleteFeatureValues",
    "aiplatform.entityTypes.exportFeatureValues",
    "aiplatform.entityTypes.get",
    "aiplatform.entityTypes.getIamPolicy",
    "aiplatform.entityTypes.importFeatureValues",
    "aiplatform.entityTypes.list",
    "aiplatform.entityTypes.readFeatureValues",
    "aiplatform.entityTypes.setIamPolicy",
    "aiplatform.entityTypes.streamingReadFeatureValues",
    "aiplatform.entityTypes.update",
    "aiplatform.entityTypes.writeFeatureValues",
    "aiplatform.executions.addExecutionEvents",
    "aiplatform.executions.create",
    "aiplatform.executions.delete",
    "aiplatform.executions.get",
    "aiplatform.executions.list",
    "aiplatform.executions.queryExecutionInputsAndOutputs",
    "aiplatform.executions.update",
    "aiplatform.features.create",
    "aiplatform.features.delete",
    "aiplatform.features.get",
    "aiplatform.features.list",
    "aiplatform.features.update",
    "aiplatform.featurestores.batchReadFeatureValues",
    "aiplatform.featurestores.create",
    "aiplatform.featurestores.delete",
    "aiplatform.featurestores.exportFeatures",
    "aiplatform.featurestores.get",
    "aiplatform.featurestores.getIamPolicy",
    "aiplatform.featurestores.importFeatures",
    "aiplatform.featurestores.list",
    "aiplatform.featurestores.readFeatures",
    "aiplatform.featurestores.setIamPolicy",
    "aiplatform.featurestores.update",
    "aiplatform.featurestores.writeFeatures",
    "aiplatform.humanInTheLoops.create",
    "aiplatform.humanInTheLoops.delete",
    "aiplatform.humanInTheLoops.get",
    "aiplatform.humanInTheLoops.list",
    "aiplatform.humanInTheLoops.queryAnnotationStats",
    "aiplatform.humanInTheLoops.send",
    "aiplatform.humanInTheLoops.update",
    "aiplatform.hyperparameterTuningJobs.cancel",
    "aiplatform.hyperparameterTuningJobs.create",
    "aiplatform.hyperparameterTuningJobs.delete",
    "aiplatform.hyperparameterTuningJobs.get",
    "aiplatform.hyperparameterTuningJobs.list",
    "aiplatform.indexEndpoints.create",
    "aiplatform.indexEndpoints.delete",
    "aiplatform.indexEndpoints.deploy",
    "aiplatform.indexEndpoints.get",
    "aiplatform.indexEndpoints.list",
    "aiplatform.indexEndpoints.undeploy",
    "aiplatform.indexEndpoints.update",
    "aiplatform.indexes.create",
    "aiplatform.indexes.delete",
    "aiplatform.indexes.get",
    "aiplatform.indexes.list",
    "aiplatform.indexes.update",
    "aiplatform.locations.get",
    "aiplatform.locations.list",
    "aiplatform.metadataSchemas.create",
    "aiplatform.metadataSchemas.delete",
    "aiplatform.metadataSchemas.get",
    "aiplatform.metadataSchemas.list",
    "aiplatform.metadataStores.create",
    "aiplatform.metadataStores.delete",
    "aiplatform.metadataStores.get",
    "aiplatform.metadataStores.list",
    "aiplatform.migratableResources.migrate",
    "aiplatform.migratableResources.search",
    "aiplatform.modelDeploymentMonitoringJobs.create",
    "aiplatform.modelDeploymentMonitoringJobs.delete",
    "aiplatform.modelDeploymentMonitoringJobs.get",
    "aiplatform.modelDeploymentMonitoringJobs.list",
    "aiplatform.modelDeploymentMonitoringJobs.pause",
    "aiplatform.modelDeploymentMonitoringJobs.resume",
    "aiplatform.modelDeploymentMonitoringJobs.searchStatsAnomalies",
    "aiplatform.modelDeploymentMonitoringJobs.update",
    "aiplatform.modelEvaluationSlices.get",
    "aiplatform.modelEvaluationSlices.list",
    "aiplatform.modelEvaluations.exportEvaluatedDataItems",
    "aiplatform.modelEvaluations.get",
    "aiplatform.modelEvaluations.list",
    "aiplatform.models.delete",
    "aiplatform.models.export",
    "aiplatform.models.get",
    "aiplatform.models.list",
    "aiplatform.models.update",
    "aiplatform.models.upload",
    "aiplatform.nasJobs.cancel",
    "aiplatform.nasJobs.create",
    "aiplatform.nasJobs.delete",
    "aiplatform.nasJobs.get",
    "aiplatform.nasJobs.list",
    "aiplatform.nasTrialDetails.get",
    "aiplatform.nasTrialDetails.list",
    "aiplatform.operations.list",
    "aiplatform.pipelineJobs.cancel",
    "aiplatform.pipelineJobs.create",
    "aiplatform.pipelineJobs.delete",
    "aiplatform.pipelineJobs.get",
    "aiplatform.pipelineJobs.list",
    "aiplatform.specialistPools.create",
    "aiplatform.specialistPools.delete",
    "aiplatform.specialistPools.get",
    "aiplatform.specialistPools.list",
    "aiplatform.specialistPools.update",
    "aiplatform.studies.create",
    "aiplatform.studies.delete",
    "aiplatform.studies.get",
    "aiplatform.studies.list",
    "aiplatform.studies.update",
    "aiplatform.tensorboardExperiments.create",
    "aiplatform.tensorboardExperiments.delete",
    "aiplatform.tensorboardExperiments.get",
    "aiplatform.tensorboardExperiments.list",
    "aiplatform.tensorboardExperiments.update",
    "aiplatform.tensorboardExperiments.write",
    "aiplatform.tensorboardRuns.batchCreate",
    "aiplatform.tensorboardRuns.create",
    "aiplatform.tensorboardRuns.delete",
    "aiplatform.tensorboardRuns.get",
    "aiplatform.tensorboardRuns.list",
    "aiplatform.tensorboardRuns.update",
    "aiplatform.tensorboardRuns.write",
    "aiplatform.tensorboardTimeSeries.batchCreate",
    "aiplatform.tensorboardTimeSeries.batchRead",
    "aiplatform.tensorboardTimeSeries.create",
    "aiplatform.tensorboardTimeSeries.delete",
    "aiplatform.tensorboardTimeSeries.get",
    "aiplatform.tensorboardTimeSeries.list",
    "aiplatform.tensorboardTimeSeries.read",
    "aiplatform.tensorboardTimeSeries.update",
    "aiplatform.tensorboards.create",
    "aiplatform.tensorboards.delete",
    "aiplatform.tensorboards.get",
    "aiplatform.tensorboards.list",
    "aiplatform.tensorboards.recordAccess",
    "aiplatform.tensorboards.update",
    "aiplatform.trainingPipelines.cancel",
    "aiplatform.trainingPipelines.create",
    "aiplatform.trainingPipelines.delete",
    "aiplatform.trainingPipelines.get",
    "aiplatform.trainingPipelines.list",
    "aiplatform.trials.create",
    "aiplatform.trials.delete",
    "aiplatform.trials.get",
    "aiplatform.trials.list",
    "aiplatform.trials.update",
    "alloydb.backups.create",
    "alloydb.backups.delete",
    "alloydb.backups.get",
    "alloydb.backups.list",
    "alloydb.backups.update",
    "alloydb.clusters.create",
    "alloydb.clusters.delete",
    "alloydb.clusters.generateClientCertificate",
    "alloydb.clusters.get",
    "alloydb.clusters.list",
    "alloydb.clusters.update",
    "alloydb.instances.connect",
    "alloydb.instances.create",
    "alloydb.instances.delete",
    "alloydb.instances.failover",
    "alloydb.instances.get",
    "alloydb.instances.list",
    "alloydb.instances.restart",
    "alloydb.instances.update",
    "alloydb.locations.get",
    "alloydb.locations.list",
    "alloydb.operations.cancel",
    "alloydb.operations.delete",
    "alloydb.operations.get",
    "alloydb.operations.list",
    "alloydb.supportedDatabaseFlags.get",
    "alloydb.supportedDatabaseFlags.list",
    "analyticshub.dataExchanges.create",
    "analyticshub.dataExchanges.delete",
    "analyticshub.dataExchanges.get",
    "analyticshub.dataExchanges.getIamPolicy",
    "analyticshub.dataExchanges.list",
    "analyticshub.dataExchanges.setIamPolicy",
    "analyticshub.dataExchanges.update",
    "analyticshub.listings.create",
    "analyticshub.listings.delete",
    "analyticshub.listings.get",
    "analyticshub.listings.getIamPolicy",
    "analyticshub.listings.list",
    "analyticshub.listings.setIamPolicy",
    "analyticshub.listings.subscribe",
    "analyticshub.listings.update",
    "androidmanagement.enterprises.manage",
    "apigateway.apiconfigs.create",
    "apigateway.apiconfigs.delete",
    "apigateway.apiconfigs.get",
    "apigateway.apiconfigs.getIamPolicy",
    "apigateway.apiconfigs.list",
    "apigateway.apiconfigs.setIamPolicy",
    "apigateway.apiconfigs.update",
    "apigateway.apis.create",
    "apigateway.apis.delete",
    "apigateway.apis.get",
    "apigateway.apis.getIamPolicy",
    "apigateway.apis.list",
    "apigateway.apis.setIamPolicy",
    "apigateway.apis.update",
    "apigateway.gateways.create",
    "apigateway.gateways.delete",
    "apigateway.gateways.get",
    "apigateway.gateways.getIamPolicy",
    "apigateway.gateways.list",
    "apigateway.gateways.setIamPolicy",
    "apigateway.gateways.update",
    "apigateway.locations.get",
    "apigateway.locations.list",
    "apigateway.operations.cancel",
    "apigateway.operations.delete",
    "apigateway.operations.get",
    "apigateway.operations.list",
    "apigee.apiproductattributes.createOrUpdateAll",
    "apigee.apiproductattributes.delete",
    "apigee.apiproductattributes.get",
    "apigee.apiproductattributes.list",
    "apigee.apiproductattributes.update",
    "apigee.apiproducts.create",
    "apigee.apiproducts.delete",
    "apigee.apiproducts.get",
    "apigee.apiproducts.list",
    "apigee.apiproducts.update",
    "apigee.appkeys.create",
    "apigee.appkeys.delete",
    "apigee.appkeys.get",
    "apigee.appkeys.manage",
    "apigee.apps.get",
    "apigee.apps.list",
    "apigee.archivedeployments.create",
    "apigee.archivedeployments.delete",
    "apigee.archivedeployments.download",
    "apigee.archivedeployments.get",
    "apigee.archivedeployments.list",
    "apigee.archivedeployments.update",
    "apigee.archivedeployments.upload",
    "apigee.caches.delete",
    "apigee.caches.list",
    "apigee.canaryevaluations.create",
    "apigee.canaryevaluations.get",
    "apigee.datacollectors.create",
    "apigee.datacollectors.delete",
    "apigee.datacollectors.get",
    "apigee.datacollectors.list",
    "apigee.datacollectors.update",
    "apigee.datalocation.get",
    "apigee.datastores.create",
    "apigee.datastores.delete",
    "apigee.datastores.get",
    "apigee.datastores.list",
    "apigee.datastores.update",
    "apigee.deployments.create",
    "apigee.deployments.delete",
    "apigee.deployments.get",
    "apigee.deployments.list",
    "apigee.deployments.update",
    "apigee.developerappattributes.createOrUpdateAll",
    "apigee.developerappattributes.delete",
    "apigee.developerappattributes.get",
    "apigee.developerappattributes.list",
    "apigee.developerappattributes.update",
    "apigee.developerapps.create",
    "apigee.developerapps.delete",
    "apigee.developerapps.get",
    "apigee.developerapps.list",
    "apigee.developerapps.manage",
    "apigee.developerattributes.createOrUpdateAll",
    "apigee.developerattributes.delete",
    "apigee.developerattributes.get",
    "apigee.developerattributes.list",
    "apigee.developerattributes.update",
    "apigee.developerbalances.adjust",
    "apigee.developerbalances.get",
    "apigee.developerbalances.update",
    "apigee.developermonetizationconfigs.get",
    "apigee.developermonetizationconfigs.update",
    "apigee.developers.create",
    "apigee.developers.delete",
    "apigee.developers.get",
    "apigee.developers.list",
    "apigee.developers.update",
    "apigee.developersubscriptions.create",
    "apigee.developersubscriptions.get",
    "apigee.developersubscriptions.list",
    "apigee.developersubscriptions.update",
    "apigee.endpointattachments.create",
    "apigee.endpointattachments.delete",
    "apigee.endpointattachments.get",
    "apigee.endpointattachments.list",
    "apigee.envgroupattachments.create",
    "apigee.envgroupattachments.delete",
    "apigee.envgroupattachments.get",
    "apigee.envgroupattachments.list",
    "apigee.envgroups.create",
    "apigee.envgroups.delete",
    "apigee.envgroups.get",
    "apigee.envgroups.list",
    "apigee.envgroups.update",
    "apigee.environments.create",
    "apigee.environments.delete",
    "apigee.environments.get",
    "apigee.environments.getDataLocation",
    "apigee.environments.getIamPolicy",
    "apigee.environments.getStats",
    "apigee.environments.list",
    "apigee.environments.manageRuntime",
    "apigee.environments.setIamPolicy",
    "apigee.environments.update",
    "apigee.exports.create",
    "apigee.exports.get",
    "apigee.exports.list",
    "apigee.flowhooks.attachSharedFlow",
    "apigee.flowhooks.detachSharedFlow",
    "apigee.flowhooks.getSharedFlow",
    "apigee.flowhooks.list",
    "apigee.hostqueries.create",
    "apigee.hostqueries.get",
    "apigee.hostqueries.list",
    "apigee.hostsecurityreports.create",
    "apigee.hostsecurityreports.get",
    "apigee.hostsecurityreports.list",
    "apigee.hoststats.get",
    "apigee.ingressconfigs.get",
    "apigee.instanceattachments.create",
    "apigee.instanceattachments.delete",
    "apigee.instanceattachments.get",
    "apigee.instanceattachments.list",
    "apigee.instances.create",
    "apigee.instances.delete",
    "apigee.instances.get",
    "apigee.instances.list",
    "apigee.instances.reportStatus",
    "apigee.instances.update",
    "apigee.keystorealiases.create",
    "apigee.keystorealiases.delete",
    "apigee.keystorealiases.exportCertificate",
    "apigee.keystorealiases.generateCSR",
    "apigee.keystorealiases.get",
    "apigee.keystorealiases.list",
    "apigee.keystorealiases.update",
    "apigee.keystores.create",
    "apigee.keystores.delete",
    "apigee.keystores.export",
    "apigee.keystores.get",
    "apigee.keystores.list",
    "apigee.keyvaluemapentries.create",
    "apigee.keyvaluemapentries.delete",
    "apigee.keyvaluemapentries.get",
    "apigee.keyvaluemapentries.list",
    "apigee.keyvaluemaps.create",
    "apigee.keyvaluemaps.delete",
    "apigee.keyvaluemaps.list",
    "apigee.maskconfigs.get",
    "apigee.maskconfigs.update",
    "apigee.operations.get",
    "apigee.operations.list",
    "apigee.organizations.create",
    "apigee.organizations.delete",
    "apigee.organizations.get",
    "apigee.organizations.list",
    "apigee.organizations.update",
    "apigee.portals.create",
    "apigee.portals.delete",
    "apigee.portals.get",
    "apigee.portals.list",
    "apigee.portals.update",
    "apigee.projects.migrate",
    "apigee.projects.previewMigration",
    "apigee.projects.update",
    "apigee.proxies.create",
    "apigee.proxies.delete",
    "apigee.proxies.get",
    "apigee.proxies.list",
    "apigee.proxies.update",
    "apigee.proxyrevisions.delete",
    "apigee.proxyrevisions.deploy",
    "apigee.proxyrevisions.get",
    "apigee.proxyrevisions.list",
    "apigee.proxyrevisions.undeploy",
    "apigee.proxyrevisions.update",
    "apigee.queries.create",
    "apigee.queries.get",
    "apigee.queries.list",
    "apigee.rateplans.create",
    "apigee.rateplans.delete",
    "apigee.rateplans.get",
    "apigee.rateplans.list",
    "apigee.rateplans.update",
    "apigee.references.create",
    "apigee.references.delete",
    "apigee.references.get",
    "apigee.references.list",
    "apigee.references.update",
    "apigee.reports.create",
    "apigee.reports.delete",
    "apigee.reports.get",
    "apigee.reports.list",
    "apigee.reports.update",
    "apigee.resourcefiles.create",
    "apigee.resourcefiles.delete",
    "apigee.resourcefiles.get",
    "apigee.resourcefiles.list",
    "apigee.resourcefiles.update",
    "apigee.runtimeconfigs.get",
    "apigee.securityProfileEnvironments.computeScore",
    "apigee.securityProfileEnvironments.create",
    "apigee.securityProfileEnvironments.delete",
    "apigee.securityProfiles.get",
    "apigee.securityProfiles.list",
    "apigee.securityStats.queryTabularStats",
    "apigee.securityStats.queryTimeSeriesStats",
    "apigee.securityreports.create",
    "apigee.securityreports.get",
    "apigee.securityreports.list",
    "apigee.sharedflowrevisions.delete",
    "apigee.sharedflowrevisions.deploy",
    "apigee.sharedflowrevisions.get",
    "apigee.sharedflowrevisions.list",
    "apigee.sharedflowrevisions.undeploy",
    "apigee.sharedflowrevisions.update",
    "apigee.sharedflows.create",
    "apigee.sharedflows.delete",
    "apigee.sharedflows.get",
    "apigee.sharedflows.list",
    "apigee.targetservers.create",
    "apigee.targetservers.delete",
    "apigee.targetservers.get",
    "apigee.targetservers.list",
    "apigee.targetservers.update",
    "apigee.traceconfig.get",
    "apigee.traceconfig.update",
    "apigee.traceconfigoverrides.create",
    "apigee.traceconfigoverrides.delete",
    "apigee.traceconfigoverrides.get",
    "apigee.traceconfigoverrides.list",
    "apigee.traceconfigoverrides.update",
    "apigee.tracesessions.create",
    "apigee.tracesessions.delete",
    "apigee.tracesessions.get",
    "apigee.tracesessions.list",
    "apigeeconnect.connections.list",
    "apigeeconnect.endpoints.connect",
    "apigeeregistry.apis.create",
    "apigeeregistry.apis.delete",
    "apigeeregistry.apis.get",
    "apigeeregistry.apis.getIamPolicy",
    "apigeeregistry.apis.list",
    "apigeeregistry.apis.setIamPolicy",
    "apigeeregistry.apis.update",
    "apigeeregistry.artifacts.create",
    "apigeeregistry.artifacts.delete",
    "apigeeregistry.artifacts.get",
    "apigeeregistry.artifacts.getIamPolicy",
    "apigeeregistry.artifacts.list",
    "apigeeregistry.artifacts.setIamPolicy",
    "apigeeregistry.artifacts.update",
    "apigeeregistry.deployments.create",
    "apigeeregistry.deployments.delete",
    "apigeeregistry.deployments.get",
    "apigeeregistry.deployments.list",
    "apigeeregistry.deployments.update",
    "apigeeregistry.instances.get",
    "apigeeregistry.instances.update",
    "apigeeregistry.locations.get",
    "apigeeregistry.locations.list",
    "apigeeregistry.operations.cancel",
    "apigeeregistry.operations.delete",
    "apigeeregistry.operations.get",
    "apigeeregistry.operations.list",
    "apigeeregistry.specs.create",
    "apigeeregistry.specs.delete",
    "apigeeregistry.specs.get",
    "apigeeregistry.specs.getIamPolicy",
    "apigeeregistry.specs.list",
    "apigeeregistry.specs.setIamPolicy",
    "apigeeregistry.specs.update",
    "apigeeregistry.versions.create",
    "apigeeregistry.versions.delete",
    "apigeeregistry.versions.get",
    "apigeeregistry.versions.getIamPolicy",
    "apigeeregistry.versions.list",
    "apigeeregistry.versions.setIamPolicy",
    "apigeeregistry.versions.update",
    "apikeys.keys.create",
    "apikeys.keys.delete",
    "apikeys.keys.get",
    "apikeys.keys.getKeyString",
    "apikeys.keys.list",
    "apikeys.keys.lookup",
    "apikeys.keys.undelete",
    "apikeys.keys.update",
    "appengine.applications.create",
    "appengine.applications.get",
    "appengine.applications.update",
    "appengine.instances.delete",
    "appengine.instances.get",
    "appengine.instances.list",
    "appengine.memcache.addKey",
    "appengine.memcache.flush",
    "appengine.memcache.get",
    "appengine.memcache.getKey",
    "appengine.memcache.list",
    "appengine.memcache.update",
    "appengine.operations.get",
    "appengine.operations.list",
    "appengine.runtimes.actAsAdmin",
    "appengine.services.delete",
    "appengine.services.get",
    "appengine.services.list",
    "appengine.services.update",
    "appengine.versions.create",
    "appengine.versions.delete",
    "appengine.versions.get",
    "appengine.versions.getFileContents",
    "appengine.versions.list",
    "appengine.versions.update",
    "artifactregistry.aptartifacts.create",
    "artifactregistry.dockerimages.get",
    "artifactregistry.dockerimages.list",
    "artifactregistry.files.get",
    "artifactregistry.files.list",
    "artifactregistry.kfpartifacts.create",
    "artifactregistry.locations.get",
    "artifactregistry.locations.list",
    "artifactregistry.mavenartifacts.get",
    "artifactregistry.mavenartifacts.list",
    "artifactregistry.npmpackages.get",
    "artifactregistry.npmpackages.list",
    "artifactregistry.packages.delete",
    "artifactregistry.packages.get",
    "artifactregistry.packages.list",
    "artifactregistry.projectsettings.get",
    "artifactregistry.projectsettings.update",
    "artifactregistry.pythonpackages.get",
    "artifactregistry.pythonpackages.list",
    "artifactregistry.repositories.create",
    "artifactregistry.repositories.createTagBinding",
    "artifactregistry.repositories.delete",
    "artifactregistry.repositories.deleteArtifacts",
    "artifactregistry.repositories.deleteTagBinding",
    "artifactregistry.repositories.downloadArtifacts",
    "artifactregistry.repositories.get",
    "artifactregistry.repositories.getIamPolicy",
    "artifactregistry.repositories.list",
    "artifactregistry.repositories.listEffectiveTags",
    "artifactregistry.repositories.listTagBindings",
    "artifactregistry.repositories.setIamPolicy",
    "artifactregistry.repositories.update",
    "artifactregistry.repositories.uploadArtifacts",
    "artifactregistry.tags.create",
    "artifactregistry.tags.delete",
    "artifactregistry.tags.get",
    "artifactregistry.tags.list",
    "artifactregistry.tags.update",
    "artifactregistry.versions.delete",
    "artifactregistry.versions.get",
    "artifactregistry.versions.list",
    "artifactregistry.yumartifacts.create",
    "automl.annotationSpecs.create",
    "automl.annotationSpecs.delete",
    "automl.annotationSpecs.get",
    "automl.annotationSpecs.list",
    "automl.annotationSpecs.update",
    "automl.annotations.approve",
    "automl.annotations.create",
    "automl.annotations.list",
    "automl.annotations.manipulate",
    "automl.annotations.reject",
    "automl.columnSpecs.get",
    "automl.columnSpecs.list",
    "automl.columnSpecs.update",
    "automl.datasets.create",
    "automl.datasets.delete",
    "automl.datasets.export",
    "automl.datasets.get",
    "automl.datasets.getIamPolicy",
    "automl.datasets.import",
    "automl.datasets.list",
    "automl.datasets.setIamPolicy",
    "automl.datasets.update",
    "automl.examples.delete",
    "automl.examples.get",
    "automl.examples.list",
    "automl.examples.update",
    "automl.files.delete",
    "automl.files.list",
    "automl.humanAnnotationTasks.create",
    "automl.humanAnnotationTasks.delete",
    "automl.humanAnnotationTasks.get",
    "automl.humanAnnotationTasks.list",
    "automl.locations.get",
    "automl.locations.getIamPolicy",
    "automl.locations.list",
    "automl.locations.setIamPolicy",
    "automl.modelEvaluations.create",
    "automl.modelEvaluations.get",
    "automl.modelEvaluations.list",
    "automl.models.create",
    "automl.models.delete",
    "automl.models.deploy",
    "automl.models.export",
    "automl.models.get",
    "automl.models.getIamPolicy",
    "automl.models.list",
    "automl.models.predict",
    "automl.models.setIamPolicy",
    "automl.models.undeploy",
    "automl.operations.cancel",
    "automl.operations.delete",
    "automl.operations.get",
    "automl.operations.list",
    "automl.tableSpecs.get",
    "automl.tableSpecs.list",
    "automl.tableSpecs.update",
    "automlrecommendations.apiKeys.create",
    "automlrecommendations.apiKeys.delete",
    "automlrecommendations.apiKeys.list",
    "automlrecommendations.catalogItems.create",
    "automlrecommendations.catalogItems.delete",
    "automlrecommendations.catalogItems.get",
    "automlrecommendations.catalogItems.list",
    "automlrecommendations.catalogItems.update",
    "automlrecommendations.catalogs.getStats",
    "automlrecommendations.catalogs.list",
    "automlrecommendations.catalogs.update",
    "automlrecommendations.eventStores.getStats",
    "automlrecommendations.events.create",
    "automlrecommendations.events.list",
    "automlrecommendations.events.purge",
    "automlrecommendations.events.rejoin",
    "automlrecommendations.placements.create",
    "automlrecommendations.placements.delete",
    "automlrecommendations.placements.getStats",
    "automlrecommendations.placements.list",
    "automlrecommendations.recommendations.create",
    "automlrecommendations.recommendations.delete",
    "automlrecommendations.recommendations.list",
    "automlrecommendations.recommendations.pause",
    "automlrecommendations.recommendations.resume",
    "automlrecommendations.recommendations.update",
    "autoscaling.sites.getIamPolicy",
    "autoscaling.sites.readRecommendations",
    "autoscaling.sites.setIamPolicy",
    "autoscaling.sites.writeMetrics",
    "autoscaling.sites.writeState",
    "axt.labels.get",
    "axt.labels.set",
    "backupdr.locations.get",
    "backupdr.locations.list",
    "backupdr.managementServers.backupAccess",
    "backupdr.managementServers.create",
    "backupdr.managementServers.delete",
    "backupdr.managementServers.get",
    "backupdr.managementServers.getIamPolicy",
    "backupdr.managementServers.list",
    "backupdr.managementServers.manageInternalACL",
    "backupdr.managementServers.setIamPolicy",
    "backupdr.operations.cancel",
    "backupdr.operations.delete",
    "backupdr.operations.get",
    "backupdr.operations.list",
    "baremetalsolution.instancequotas.list",
    "baremetalsolution.instances.attachNetwork",
    "baremetalsolution.instances.attachVolume",
    "baremetalsolution.instances.create",
    "baremetalsolution.instances.detachLun",
    "baremetalsolution.instances.detachNetwork",
    "baremetalsolution.instances.detachVolume",
    "baremetalsolution.instances.disableInteractiveSerialConsole",
    "baremetalsolution.instances.enableInteractiveSerialConsole",
    "baremetalsolution.instances.get",
    "baremetalsolution.instances.list",
    "baremetalsolution.instances.reset",
    "baremetalsolution.instances.start",
    "baremetalsolution.instances.stop",
    "baremetalsolution.instances.update",
    "baremetalsolution.luns.create",
    "baremetalsolution.luns.delete",
    "baremetalsolution.luns.get",
    "baremetalsolution.luns.list",
    "baremetalsolution.luns.update",
    "baremetalsolution.networkquotas.list",
    "baremetalsolution.networks.create",
    "baremetalsolution.networks.delete",
    "baremetalsolution.networks.get",
    "baremetalsolution.networks.list",
    "baremetalsolution.networks.update",
    "baremetalsolution.nfsshares.create",
    "baremetalsolution.nfsshares.delete",
    "baremetalsolution.nfsshares.get",
    "baremetalsolution.nfsshares.list",
    "baremetalsolution.nfsshares.update",
    "baremetalsolution.snapshotschedulepolicies.create",
    "baremetalsolution.snapshotschedulepolicies.delete",
    "baremetalsolution.snapshotschedulepolicies.get",
    "baremetalsolution.snapshotschedulepolicies.list",
    "baremetalsolution.snapshotschedulepolicies.update",
    "baremetalsolution.sshKeys.create",
    "baremetalsolution.sshKeys.delete",
    "baremetalsolution.sshKeys.list",
    "baremetalsolution.volumequotas.list",
    "baremetalsolution.volumes.create",
    "baremetalsolution.volumes.delete",
    "baremetalsolution.volumes.get",
    "baremetalsolution.volumes.list",
    "baremetalsolution.volumes.resize",
    "baremetalsolution.volumes.update",
    "baremetalsolution.volumesnapshots.create",
    "baremetalsolution.volumesnapshots.delete",
    "baremetalsolution.volumesnapshots.get",
    "baremetalsolution.volumesnapshots.list",
    "baremetalsolution.volumesnapshots.restore",
    "batch.jobs.create",
    "batch.jobs.delete",
    "batch.jobs.get",
    "batch.jobs.list",
    "batch.locations.get",
    "batch.locations.list",
    "batch.operations.get",
    "batch.operations.list",
    "batch.states.report",
    "batch.tasks.get",
    "batch.tasks.list",
    "beyondcorp.appConnections.create",
    "beyondcorp.appConnections.delete",
    "beyondcorp.appConnections.get",
    "beyondcorp.appConnections.getIamPolicy",
    "beyondcorp.appConnections.list",
    "beyondcorp.appConnections.setIamPolicy",
    "beyondcorp.appConnections.update",
    "beyondcorp.appConnectors.create",
    "beyondcorp.appConnectors.delete",
    "beyondcorp.appConnectors.get",
    "beyondcorp.appConnectors.getIamPolicy",
    "beyondcorp.appConnectors.list",
    "beyondcorp.appConnectors.reportStatus",
    "beyondcorp.appConnectors.setIamPolicy",
    "beyondcorp.appConnectors.update",
    "beyondcorp.appGateways.create",
    "beyondcorp.appGateways.delete",
    "beyondcorp.appGateways.get",
    "beyondcorp.appGateways.getIamPolicy",
    "beyondcorp.appGateways.list",
    "beyondcorp.appGateways.setIamPolicy",
    "beyondcorp.appGateways.update",
    "beyondcorp.clientConnectorServices.access",
    "beyondcorp.clientConnectorServices.create",
    "beyondcorp.clientConnectorServices.delete",
    "beyondcorp.clientConnectorServices.get",
    "beyondcorp.clientConnectorServices.getIamPolicy",
    "beyondcorp.clientConnectorServices.list",
    "beyondcorp.clientConnectorServices.setIamPolicy",
    "beyondcorp.clientConnectorServices.update",
    "beyondcorp.clientGateways.create",
    "beyondcorp.clientGateways.delete",
    "beyondcorp.clientGateways.get",
    "beyondcorp.clientGateways.getIamPolicy",
    "beyondcorp.clientGateways.list",
    "beyondcorp.clientGateways.setIamPolicy",
    "beyondcorp.locations.get",
    "beyondcorp.locations.list",
    "beyondcorp.operations.cancel",
    "beyondcorp.operations.delete",
    "beyondcorp.operations.get",
    "beyondcorp.operations.list",
    "bigquery.bireservations.get",
    "bigquery.bireservations.update",
    "bigquery.capacityCommitments.create",
    "bigquery.capacityCommitments.delete",
    "bigquery.capacityCommitments.get",
    "bigquery.capacityCommitments.list",
    "bigquery.capacityCommitments.update",
    "bigquery.config.get",
    "bigquery.config.update",
    "bigquery.connections.create",
    "bigquery.connections.delegate",
    "bigquery.connections.delete",
    "bigquery.connections.get",
    "bigquery.connections.getIamPolicy",
    "bigquery.connections.list",
    "bigquery.connections.setIamPolicy",
    "bigquery.connections.update",
    "bigquery.connections.updateTag",
    "bigquery.connections.use",
    "bigquery.dataPolicies.create",
    "bigquery.dataPolicies.delete",
    "bigquery.dataPolicies.get",
    "bigquery.dataPolicies.getIamPolicy",
    "bigquery.dataPolicies.list",
    "bigquery.dataPolicies.maskedGet",
    "bigquery.dataPolicies.setIamPolicy",
    "bigquery.dataPolicies.update",
    "bigquery.datasets.create",
    "bigquery.datasets.createTagBinding",
    "bigquery.datasets.delete",
    "bigquery.datasets.deleteTagBinding",
    "bigquery.datasets.get",
    "bigquery.datasets.getIamPolicy",
    "bigquery.datasets.link",
    "bigquery.datasets.listTagBindings",
    "bigquery.datasets.setIamPolicy",
    "bigquery.datasets.update",
    "bigquery.datasets.updateTag",
    "bigquery.jobs.create",
    "bigquery.jobs.delete",
    "bigquery.jobs.get",
    "bigquery.jobs.list",
    "bigquery.jobs.listAll",
    "bigquery.jobs.listExecutionMetadata",
    "bigquery.jobs.update",
    "bigquery.models.create",
    "bigquery.models.delete",
    "bigquery.models.export",
    "bigquery.models.getData",
    "bigquery.models.getMetadata",
    "bigquery.models.list",
    "bigquery.models.updateData",
    "bigquery.models.updateMetadata",
    "bigquery.models.updateTag",
    "bigquery.readsessions.create",
    "bigquery.readsessions.getData",
    "bigquery.readsessions.update",
    "bigquery.reservationAssignments.create",
    "bigquery.reservationAssignments.delete",
    "bigquery.reservationAssignments.list",
    "bigquery.reservationAssignments.search",
    "bigquery.reservations.create",
    "bigquery.reservations.delete",
    "bigquery.reservations.get",
    "bigquery.reservations.list",
    "bigquery.reservations.update",
    "bigquery.routines.create",
    "bigquery.routines.delete",
    "bigquery.routines.get",
    "bigquery.routines.list",
    "bigquery.routines.update",
    "bigquery.routines.updateTag",
    "bigquery.rowAccessPolicies.create",
    "bigquery.rowAccessPolicies.delete",
    "bigquery.rowAccessPolicies.getFilteredData",
    "bigquery.rowAccessPolicies.getIamPolicy",
    "bigquery.rowAccessPolicies.list",
    "bigquery.rowAccessPolicies.overrideTimeTravelRestrictions",
    "bigquery.rowAccessPolicies.setIamPolicy",
    "bigquery.rowAccessPolicies.update",
    "bigquery.savedqueries.create",
    "bigquery.savedqueries.delete",
    "bigquery.savedqueries.get",
    "bigquery.savedqueries.list",
    "bigquery.savedqueries.update",
    "bigquery.tables.create",
    "bigquery.tables.createIndex",
    "bigquery.tables.createSnapshot",
    "bigquery.tables.delete",
    "bigquery.tables.deleteIndex",
    "bigquery.tables.deleteSnapshot",
    "bigquery.tables.export",
    "bigquery.tables.get",
    "bigquery.tables.getData",
    "bigquery.tables.getIamPolicy",
    "bigquery.tables.list",
    "bigquery.tables.restoreSnapshot",
    "bigquery.tables.setCategory",
    "bigquery.tables.setIamPolicy",
    "bigquery.tables.update",
    "bigquery.tables.updateData",
    "bigquery.tables.updateTag",
    "bigquery.transfers.get",
    "bigquery.transfers.update",
    "bigquerymigration.locations.get",
    "bigquerymigration.locations.list",
    "bigquerymigration.subtaskTypes.executeTask",
    "bigquerymigration.subtasks.create",
    "bigquerymigration.subtasks.executeTask",
    "bigquerymigration.subtasks.get",
    "bigquerymigration.subtasks.list",
    "bigquerymigration.taskTypes.orchestrateTask",
    "bigquerymigration.translation.translate",
    "bigquerymigration.workflows.create",
    "bigquerymigration.workflows.delete",
    "bigquerymigration.workflows.get",
    "bigquerymigration.workflows.list",
    "bigquerymigration.workflows.orchestrateTask",
    "bigquerymigration.workflows.update",
    "bigquerymigration.workflows.writeLogs",
    "bigtable.appProfiles.create",
    "bigtable.appProfiles.delete",
    "bigtable.appProfiles.get",
    "bigtable.appProfiles.list",
    "bigtable.appProfiles.update",
    "bigtable.backups.create",
    "bigtable.backups.delete",
    "bigtable.backups.get",
    "bigtable.backups.getIamPolicy",
    "bigtable.backups.list",
    "bigtable.backups.read",
    "bigtable.backups.restore",
    "bigtable.backups.setIamPolicy",
    "bigtable.backups.update",
    "bigtable.clusters.create",
    "bigtable.clusters.delete",
    "bigtable.clusters.get",
    "bigtable.clusters.list",
    "bigtable.clusters.update",
    "bigtable.hotTablets.list",
    "bigtable.instances.create",
    "bigtable.instances.createTagBinding",
    "bigtable.instances.delete",
    "bigtable.instances.deleteTagBinding",
    "bigtable.instances.get",
    "bigtable.instances.getIamPolicy",
    "bigtable.instances.list",
    "bigtable.instances.listEffectiveTags",
    "bigtable.instances.listTagBindings",
    "bigtable.instances.ping",
    "bigtable.instances.setIamPolicy",
    "bigtable.instances.update",
    "bigtable.keyvisualizer.get",
    "bigtable.keyvisualizer.list",
    "bigtable.locations.list",
    "bigtable.tables.checkConsistency",
    "bigtable.tables.create",
    "bigtable.tables.delete",
    "bigtable.tables.generateConsistencyToken",
    "bigtable.tables.get",
    "bigtable.tables.getIamPolicy",
    "bigtable.tables.list",
    "bigtable.tables.mutateRows",
    "bigtable.tables.readRows",
    "bigtable.tables.sampleRowKeys",
    "bigtable.tables.setIamPolicy",
    "bigtable.tables.undelete",
    "bigtable.tables.update",
    "billing.resourceCosts.get",
    "binaryauthorization.attestors.create",
    "binaryauthorization.attestors.delete",
    "binaryauthorization.attestors.get",
    "binaryauthorization.attestors.getIamPolicy",
    "binaryauthorization.attestors.list",
    "binaryauthorization.attestors.setIamPolicy",
    "binaryauthorization.attestors.update",
    "binaryauthorization.attestors.verifyImageAttested",
    "binaryauthorization.continuousValidationConfig.get",
    "binaryauthorization.continuousValidationConfig.getIamPolicy",
    "binaryauthorization.continuousValidationConfig.setIamPolicy",
    "binaryauthorization.continuousValidationConfig.update",
    "binaryauthorization.platformPolicies.create",
    "binaryauthorization.platformPolicies.delete",
    "binaryauthorization.platformPolicies.evaluatePolicy",
    "binaryauthorization.platformPolicies.get",
    "binaryauthorization.platformPolicies.list",
    "binaryauthorization.platformPolicies.replace",
    "binaryauthorization.policy.evaluatePolicy",
    "binaryauthorization.policy.get",
    "binaryauthorization.policy.getIamPolicy",
    "binaryauthorization.policy.setIamPolicy",
    "binaryauthorization.policy.update",
    "carestudio.patients.get",
    "carestudio.patients.list",
    "certificatemanager.certissuanceconfigs.create",
    "certificatemanager.certissuanceconfigs.delete",
    "certificatemanager.certissuanceconfigs.get",
    "certificatemanager.certissuanceconfigs.list",
    "certificatemanager.certissuanceconfigs.update",
    "certificatemanager.certissuanceconfigs.use",
    "certificatemanager.certmapentries.create",
    "certificatemanager.certmapentries.delete",
    "certificatemanager.certmapentries.get",
    "certificatemanager.certmapentries.getIamPolicy",
    "certificatemanager.certmapentries.list",
    "certificatemanager.certmapentries.setIamPolicy",
    "certificatemanager.certmapentries.update",
    "certificatemanager.certmaps.create",
    "certificatemanager.certmaps.delete",
    "certificatemanager.certmaps.get",
    "certificatemanager.certmaps.getIamPolicy",
    "certificatemanager.certmaps.list",
    "certificatemanager.certmaps.setIamPolicy",
    "certificatemanager.certmaps.update",
    "certificatemanager.certmaps.use",
    "certificatemanager.certs.create",
    "certificatemanager.certs.delete",
    "certificatemanager.certs.get",
    "certificatemanager.certs.getIamPolicy",
    "certificatemanager.certs.list",
    "certificatemanager.certs.setIamPolicy",
    "certificatemanager.certs.update",
    "certificatemanager.certs.use",
    "certificatemanager.dnsauthorizations.create",
    "certificatemanager.dnsauthorizations.delete",
    "certificatemanager.dnsauthorizations.get",
    "certificatemanager.dnsauthorizations.getIamPolicy",
    "certificatemanager.dnsauthorizations.list",
    "certificatemanager.dnsauthorizations.setIamPolicy",
    "certificatemanager.dnsauthorizations.update",
    "certificatemanager.dnsauthorizations.use",
    "certificatemanager.locations.get",
    "certificatemanager.locations.list",
    "certificatemanager.operations.cancel",
    "certificatemanager.operations.delete",
    "certificatemanager.operations.get",
    "certificatemanager.operations.list",
    "chat.bots.get",
    "chat.bots.update",
    "chronicle.dashboards.copy",
    "chronicle.dashboards.create",
    "chronicle.dashboards.delete",
    "chronicle.dashboards.get",
    "chronicle.dashboards.list",
    "chronicle.multitenantDirectories.get",
    "clientauthconfig.brands.create",
    "clientauthconfig.brands.delete",
    "clientauthconfig.brands.get",
    "clientauthconfig.brands.list",
    "clientauthconfig.brands.update",
    "clientauthconfig.clients.create",
    "clientauthconfig.clients.createSecret",
    "clientauthconfig.clients.delete",
    "clientauthconfig.clients.get",
    "clientauthconfig.clients.getWithSecret",
    "clientauthconfig.clients.list",
    "clientauthconfig.clients.listWithSecrets",
    "clientauthconfig.clients.undelete",
    "clientauthconfig.clients.update",
    "cloudasset.assets.analyzeIamPolicy",
    "cloudasset.assets.analyzeMove",
    "cloudasset.assets.exportAccessLevel",
    "cloudasset.assets.exportAccessPolicy",
    "cloudasset.assets.exportAiplatformBatchPredictionJobs",
    "cloudasset.assets.exportAiplatformCustomJobs",
    "cloudasset.assets.exportAiplatformDataLabelingJobs",
    "cloudasset.assets.exportAiplatformDatasets",
    "cloudasset.assets.exportAiplatformEndpoints",
    "cloudasset.assets.exportAiplatformHyperparameterTuningJobs",
    "cloudasset.assets.exportAiplatformMetadataStores",
    "cloudasset.assets.exportAiplatformModelDeploymentMonitoringJobs",
    "cloudasset.assets.exportAiplatformModels",
    "cloudasset.assets.exportAiplatformPipelineJobs",
    "cloudasset.assets.exportAiplatformSpecialistPools",
    "cloudasset.assets.exportAiplatformTrainingPipelines",
    "cloudasset.assets.exportAllAccessPolicy",
    "cloudasset.assets.exportAnthosConnectedCluster",
    "cloudasset.assets.exportAnthosedgeCluster",
    "cloudasset.assets.exportApigatewayApi",
    "cloudasset.assets.exportApigatewayApiConfig",
    "cloudasset.assets.exportApigatewayGateway",
    "cloudasset.assets.exportApikeysKeys",
    "cloudasset.assets.exportAppengineApplications",
    "cloudasset.assets.exportAppengineServices",
    "cloudasset.assets.exportAppengineVersions",
    "cloudasset.assets.exportArtifactregistryDockerImages",
    "cloudasset.assets.exportArtifactregistryRepositories",
    "cloudasset.assets.exportAssuredWorkloadsWorkloads",
    "cloudasset.assets.exportBeyondCorpApiGateways",
    "cloudasset.assets.exportBeyondCorpAppConnections",
    "cloudasset.assets.exportBeyondCorpAppConnectors",
    "cloudasset.assets.exportBeyondCorpClientConnectorServices",
    "cloudasset.assets.exportBeyondCorpClientGateways",
    "cloudasset.assets.exportBigqueryDatasets",
    "cloudasset.assets.exportBigqueryModels",
    "cloudasset.assets.exportBigqueryTables",
    "cloudasset.assets.exportBigtableAppProfile",
    "cloudasset.assets.exportBigtableBackup",
    "cloudasset.assets.exportBigtableCluster",
    "cloudasset.assets.exportBigtableInstance",
    "cloudasset.assets.exportBigtableTable",
    "cloudasset.assets.exportCloudAssetFeeds",
    "cloudasset.assets.exportCloudDeployDeliveryPipelines",
    "cloudasset.assets.exportCloudDeployReleases",
    "cloudasset.assets.exportCloudDeployRollouts",
    "cloudasset.assets.exportCloudDeployTargets",
    "cloudasset.assets.exportCloudDocumentAIEvaluation",
    "cloudasset.assets.exportCloudDocumentAIHumanReviewConfig",
    "cloudasset.assets.exportCloudDocumentAILabelerPool",
    "cloudasset.assets.exportCloudDocumentAIProcessor",
    "cloudasset.assets.exportCloudDocumentAIProcessorVersion",
    "cloudasset.assets.exportCloudbillingBillingAccounts",
    "cloudasset.assets.exportCloudbillingProjectBillingInfos",
    "cloudasset.assets.exportCloudfunctionsFunctions",
    "cloudasset.assets.exportCloudfunctionsGen2Functions",
    "cloudasset.assets.exportCloudkmsCryptoKeyVersions",
    "cloudasset.assets.exportCloudkmsCryptoKeys",
    "cloudasset.assets.exportCloudkmsEkmConnections",
    "cloudasset.assets.exportCloudkmsImportJobs",
    "cloudasset.assets.exportCloudkmsKeyRings",
    "cloudasset.assets.exportCloudmemcacheInstances",
    "cloudasset.assets.exportCloudresourcemanagerFolders",
    "cloudasset.assets.exportCloudresourcemanagerOrganizations",
    "cloudasset.assets.exportCloudresourcemanagerProjects",
    "cloudasset.assets.exportCloudresourcemanagerTagBindings",
    "cloudasset.assets.exportCloudresourcemanagerTagKeys",
    "cloudasset.assets.exportCloudresourcemanagerTagValues",
    "cloudasset.assets.exportComposerEnvironments",
    "cloudasset.assets.exportComputeAddress",
    "cloudasset.assets.exportComputeAutoscalers",
    "cloudasset.assets.exportComputeBackendBuckets",
    "cloudasset.assets.exportComputeBackendServices",
    "cloudasset.assets.exportComputeCommitments",
    "cloudasset.assets.exportComputeDisks",
    "cloudasset.assets.exportComputeExternalVpnGateways",
    "cloudasset.assets.exportComputeFirewallPolicies",
    "cloudasset.assets.exportComputeFirewalls",
    "cloudasset.assets.exportComputeForwardingRules",
    "cloudasset.assets.exportComputeGlobalAddress",
    "cloudasset.assets.exportComputeGlobalForwardingRules",
    "cloudasset.assets.exportComputeHealthChecks",
    "cloudasset.assets.exportComputeHttpHealthChecks",
    "cloudasset.assets.exportComputeHttpsHealthChecks",
    "cloudasset.assets.exportComputeImages",
    "cloudasset.assets.exportComputeInstanceGroupManagers",
    "cloudasset.assets.exportComputeInstanceGroups",
    "cloudasset.assets.exportComputeInstanceTemplates",
    "cloudasset.assets.exportComputeInstances",
    
Download .txt
gitextract_eegatfp6/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   └── workflows/
│       └── sponsors.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── examples/
│   ├── email_registered.py
│   └── get_people_name.py
├── ghunt/
│   ├── __init__.py
│   ├── apis/
│   │   ├── __init__.py
│   │   ├── accounts.py
│   │   ├── calendar.py
│   │   ├── clientauthconfig.py
│   │   ├── digitalassetslinks.py
│   │   ├── drive.py
│   │   ├── fireconsolepa.py
│   │   ├── geolocation.py
│   │   ├── identitytoolkit.py
│   │   ├── mobilesdk.py
│   │   ├── peoplepa.py
│   │   ├── playgames.py
│   │   ├── playgateway.py
│   │   └── vision.py
│   ├── cli.py
│   ├── config.py
│   ├── errors.py
│   ├── ghunt.py
│   ├── globals.py
│   ├── helpers/
│   │   ├── __init__.py
│   │   ├── auth.py
│   │   ├── banner.py
│   │   ├── calendar.py
│   │   ├── drive.py
│   │   ├── gcp.py
│   │   ├── gmail.py
│   │   ├── gmaps.py
│   │   ├── ia.py
│   │   ├── iam.py
│   │   ├── knowledge.py
│   │   ├── listener.py
│   │   ├── playgames.py
│   │   ├── playstore.py
│   │   └── utils.py
│   ├── knowledge/
│   │   ├── __init__.py
│   │   ├── drive.py
│   │   ├── iam.py
│   │   ├── keys.py
│   │   ├── maps.py
│   │   ├── people.py
│   │   ├── services.py
│   │   └── sig.py
│   ├── lib/
│   │   ├── __init__.py
│   │   └── httpx.py
│   ├── modules/
│   │   ├── __init__.py
│   │   ├── drive.py
│   │   ├── email.py
│   │   ├── gaia.py
│   │   ├── geolocate.py
│   │   ├── login.py
│   │   └── spiderdal.py
│   ├── objects/
│   │   ├── __init__.py
│   │   ├── apis.py
│   │   ├── base.py
│   │   ├── encoders.py
│   │   ├── session.py
│   │   └── utils.py
│   ├── parsers/
│   │   ├── __init__.py
│   │   ├── calendar.py
│   │   ├── clientauthconfig.py
│   │   ├── digitalassetslinks.py
│   │   ├── drive.py
│   │   ├── geolocate.py
│   │   ├── identitytoolkit.py
│   │   ├── mobilesdk.py
│   │   ├── people.py
│   │   ├── playgames.py
│   │   ├── playgateway.py
│   │   └── vision.py
│   ├── protos/
│   │   ├── __init__.py
│   │   └── playgatewaypa/
│   │       ├── __init__.py
│   │       ├── definitions/
│   │       │   ├── get_player.proto
│   │       │   ├── get_player_response.proto
│   │       │   ├── search_player.proto
│   │       │   └── search_player_results.proto
│   │       ├── get_player_pb2.py
│   │       ├── get_player_response_pb2.py
│   │       ├── search_player_pb2.py
│   │       └── search_player_results_pb2.py
│   └── version.py
├── main.py
└── pyproject.toml
Download .txt
SYMBOL INDEX (474 symbols across 55 files)

FILE: examples/email_registered.py
  function main (line 11) | async def main():

FILE: examples/get_people_name.py
  function main (line 10) | async def main():

FILE: ghunt/apis/accounts.py
  class Accounts (line 12) | class Accounts(GAPI):
    method __init__ (line 13) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method OAuthLogin (line 38) | async def OAuthLogin(self, as_client: httpx.AsyncClient) -> str:

FILE: ghunt/apis/calendar.py
  class CalendarHttp (line 15) | class CalendarHttp(GAPI):
    method __init__ (line 16) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method get_calendar (line 31) | async def get_calendar(self, as_client: httpx.AsyncClient, calendar_id...
    method get_events (line 56) | async def get_events(self, as_client: httpx.AsyncClient, calendar_id: ...

FILE: ghunt/apis/clientauthconfig.py
  class ClientAuthConfigHttp (line 14) | class ClientAuthConfigHttp(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method get_brand (line 30) | async def get_brand(self, as_client: httpx.AsyncClient, project_number...

FILE: ghunt/apis/digitalassetslinks.py
  class DigitalAssetsLinksHttp (line 14) | class DigitalAssetsLinksHttp(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method list_statements (line 30) | async def list_statements(self, as_client: httpx.AsyncClient, website:...

FILE: ghunt/apis/drive.py
  class DriveHttp (line 15) | class DriveHttp(GAPI):
    method __init__ (line 16) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method get_file (line 39) | async def get_file(self, as_client: httpx.AsyncClient, file_id: str) -...
    method get_comments (line 68) | async def get_comments(self, as_client: httpx.AsyncClient, file_id: st...
    method get_childs (line 102) | async def get_childs(self, as_client: httpx.AsyncClient, file_id: str,...

FILE: ghunt/apis/fireconsolepa.py
  class FireconsolePaHttp (line 14) | class FireconsolePaHttp(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method is_project_valid (line 30) | async def is_project_valid(self, as_client: httpx.AsyncClient, project...

FILE: ghunt/apis/geolocation.py
  class GeolocationHttp (line 14) | class GeolocationHttp(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method geolocate (line 33) | async def geolocate(self, as_client: httpx.AsyncClient, bssid: str, bo...

FILE: ghunt/apis/identitytoolkit.py
  class IdentityToolkitHttp (line 14) | class IdentityToolkitHttp(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method get_project_config (line 30) | async def get_project_config(self, as_client: httpx.AsyncClient, api_k...

FILE: ghunt/apis/mobilesdk.py
  class MobileSDKPaHttp (line 14) | class MobileSDKPaHttp(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method test_iam_permissions (line 30) | async def test_iam_permissions(self, as_client: httpx.AsyncClient, pro...
    method get_webapp_dynamic_config (line 61) | async def get_webapp_dynamic_config(self, as_client: httpx.AsyncClient...

FILE: ghunt/apis/peoplepa.py
  class PeoplePaHttp (line 14) | class PeoplePaHttp(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method people_lookup (line 32) | async def people_lookup(self, as_client: httpx.AsyncClient, email: str...
    method people (line 125) | async def people(self, as_client: httpx.AsyncClient, gaia_id: str, par...

FILE: ghunt/apis/playgames.py
  class PlayGames (line 14) | class PlayGames(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method get_profile (line 38) | async def get_profile(self, as_client: httpx.AsyncClient, player_id: s...
    method get_played_games (line 63) | async def get_played_games(self, as_client: httpx.AsyncClient, player_...
    method get_achievements (line 93) | async def get_achievements(self, as_client: httpx.AsyncClient, player_...

FILE: ghunt/apis/playgateway.py
  class PlayGatewayPaGrpc (line 18) | class PlayGatewayPaGrpc(GAPI):
    method __init__ (line 19) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method search_player (line 45) | async def search_player(self, as_client: httpx.AsyncClient, query: str...
    method get_player_stats (line 81) | async def get_player_stats(self, as_client: httpx.AsyncClient, player_...

FILE: ghunt/apis/vision.py
  class VisionHttp (line 14) | class VisionHttp(GAPI):
    method __init__ (line 15) | def __init__(self, creds: GHuntCreds, headers: Dict[str, str] = {}):
    method detect_faces (line 32) | async def detect_faces(self, as_client: httpx.AsyncClient, image_url: ...

FILE: ghunt/cli.py
  function parse_and_run (line 9) | def parse_and_run():
  function process_args (line 65) | def process_args(args: argparse.Namespace):

FILE: ghunt/errors.py
  class GHuntKnowledgeError (line 1) | class GHuntKnowledgeError(Exception):
  class GHuntCorruptedHeadersError (line 4) | class GHuntCorruptedHeadersError(Exception):
  class GHuntUnknownVerbError (line 7) | class GHuntUnknownVerbError(Exception):
  class GHuntUnknownRequestDataTypeError (line 10) | class GHuntUnknownRequestDataTypeError(Exception):
  class GHuntInsufficientCreds (line 13) | class GHuntInsufficientCreds(Exception):
  class GHuntParamsTemplateError (line 16) | class GHuntParamsTemplateError(Exception):
  class GHuntParamsInputError (line 19) | class GHuntParamsInputError(Exception):
  class GHuntAPIResponseParsingError (line 22) | class GHuntAPIResponseParsingError(Exception):
  class GHuntObjectsMergingError (line 25) | class GHuntObjectsMergingError(Exception):
  class GHuntAndroidMasterAuthError (line 28) | class GHuntAndroidMasterAuthError(Exception):
  class GHuntAndroidAppOAuth2Error (line 31) | class GHuntAndroidAppOAuth2Error(Exception):
  class GHuntOSIDAuthError (line 34) | class GHuntOSIDAuthError(Exception):
  class GHuntCredsNotLoaded (line 37) | class GHuntCredsNotLoaded(Exception):
  class GHuntInvalidSession (line 40) | class GHuntInvalidSession(Exception):
  class GHuntNotAuthenticated (line 43) | class GHuntNotAuthenticated(Exception):
  class GHuntInvalidTarget (line 46) | class GHuntInvalidTarget(Exception):
  class GHuntLoginError (line 49) | class GHuntLoginError(Exception):

FILE: ghunt/ghunt.py
  function main (line 5) | def main():

FILE: ghunt/globals.py
  function init_globals (line 4) | def init_globals():

FILE: ghunt/helpers/auth.py
  function android_master_auth (line 20) | async def android_master_auth(as_client: httpx.AsyncClient, oauth_token:...
  function android_oauth_app (line 44) | async def android_oauth_app(as_client: httpx.AsyncClient, master_token: ...
  function gen_osid (line 68) | async def gen_osid(as_client: httpx.AsyncClient, cookies: Dict[str, str]...
  function gen_osids (line 93) | async def gen_osids(as_client: httpx.AsyncClient, cookies: Dict[str, str...
  function check_cookies (line 104) | async def check_cookies(as_client: httpx.AsyncClient, cookies: Dict[str,...
  function check_osid (line 111) | async def check_osid(as_client: httpx.AsyncClient, cookies: Dict[str, st...
  function check_osids (line 125) | async def check_osids(as_client: httpx.AsyncClient, cookies: Dict[str, s...
  function check_master_token (line 131) | async def check_master_token(as_client: httpx.AsyncClient, master_token:...
  function gen_cookies_and_osids (line 139) | async def gen_cookies_and_osids(as_client: httpx.AsyncClient, ghunt_cred...
  function check_and_gen (line 159) | async def check_and_gen(as_client: httpx.AsyncClient, ghunt_creds: GHunt...
  function auth_dialog (line 169) | def auth_dialog() -> Tuple[Dict[str, str], str] :
  function load_and_auth (line 205) | async def load_and_auth(as_client: httpx.AsyncClient, help=True) -> GHun...

FILE: ghunt/helpers/banner.py
  function show_banner (line 3) | def show_banner():

FILE: ghunt/helpers/calendar.py
  function fetch_all (line 15) | async def fetch_all(ghunt_creds: GHuntCreds, as_client: httpx.AsyncClien...
  function out (line 31) | def out(calendar: Calendar, events: CalendarEvents, email_address: str, ...

FILE: ghunt/helpers/drive.py
  function get_users_from_file (line 8) | def get_users_from_file(file: DriveFile) -> List[DriveExtractedUser]:
  function get_comments_from_file (line 41) | def get_comments_from_file(comments: DriveCommentList) -> List[Tuple[str...

FILE: ghunt/helpers/gcp.py
  function is_cloud_functions_panel_existing (line 9) | async def is_cloud_functions_panel_existing(project_id: str):
  function project_nb_from_key (line 14) | async def project_nb_from_key(as_client: httpx.AsyncClient, ghunt_creds:...

FILE: ghunt/helpers/gmail.py
  function is_email_registered (line 4) | async def is_email_registered(as_client: httpx.AsyncClient, email: str) ...

FILE: ghunt/helpers/gmaps.py
  function get_datetime (line 18) | def get_datetime(datepublished: str):
  function get_reviews (line 48) | async def get_reviews(as_client: httpx.AsyncClient, gaia_id: str) -> Tup...
  function avg_location (line 156) | def avg_location(locs: Tuple[float, float]):
  function translate_confidence (line 171) | def translate_confidence(percents: int):
  function sanitize_location (line 188) | def sanitize_location(location: Dict[str, str]):
  function calculate_probable_location (line 212) | def calculate_probable_location(geolocator: Nominatim, reviews_and_photo...
  function output (line 305) | def output(err: str, stats: Dict[str, int], gaia_id: str):

FILE: ghunt/helpers/ia.py
  function detect_face (line 12) | async def detect_face(vision_api: VisionHttp, as_client: httpx.AsyncClie...

FILE: ghunt/helpers/iam.py
  function test_all_permissions (line 12) | async def test_all_permissions(as_client: httpx.AsyncClient, ghunt_creds...

FILE: ghunt/helpers/knowledge.py
  function get_domain_of_service (line 11) | def get_domain_of_service(service: str) -> str:
  function get_origin_of_key (line 16) | def get_origin_of_key(key_name: str) -> str:
  function get_api_key (line 21) | def get_api_key(key_name: str) -> str:
  function get_gmaps_type_translation (line 26) | def get_gmaps_type_translation(type_name: str) -> str:
  function get_user_type_definition (line 31) | def get_user_type_definition(type_name: str) -> str:
  function get_package_sig (line 36) | def get_package_sig(package_name: str) -> str:

FILE: ghunt/helpers/listener.py
  class DataBridge (line 8) | class DataBridge(SmartObj):
    method __init__ (line 9) | def __init__(self):
  class Server (line 12) | class Server(BaseHTTPRequestHandler):
    method _set_response (line 13) | def _set_response(self):
    method do_GET (line 19) | def do_GET(self):
    method do_POST (line 24) | def do_POST(self):
    method log_message (line 33) | def log_message(self, format, *args):
  function run (line 36) | def run(server_class=HTTPServer, handler_class=Server, port=60067):

FILE: ghunt/helpers/playgames.py
  function get_player (line 14) | async def get_player(ghunt_creds: GHuntCreds, as_client: httpx.AsyncClie...
  function search_player (line 46) | async def search_player(ghunt_creds: GHuntCreds, as_client: httpx.AsyncC...
  function output (line 51) | def output(player: Player):

FILE: ghunt/helpers/playstore.py
  function app_exists (line 4) | async def app_exists(as_client: httpx.AsyncClient, package: str) -> bool:

FILE: ghunt/helpers/utils.py
  function get_httpx_client (line 22) | def get_httpx_client() -> httpx.AsyncClient:
  function oprint (line 29) | def oprint(obj: any) -> str:
  function chunkify (line 34) | def chunkify(lst, n):
  function within_docker (line 42) | def within_docker() -> bool:
  function gen_sapisidhash (line 45) | def gen_sapisidhash(sapisid: str, origin: str, timestamp: str = str(int(...
  function inject_osid (line 48) | def inject_osid(cookies: Dict[str, str], osids: Dict[str, str], service:...
  function is_headers_syntax_good (line 53) | def is_headers_syntax_good(headers: Dict[str, str]) -> bool:
  function get_url_image_flathash (line 60) | async def get_url_image_flathash(as_client: httpx.AsyncClient, image_url...
  function is_default_profile_pic (line 66) | async def is_default_profile_pic(as_client: httpx.AsyncClient, image_url...
  function get_class_name (line 77) | def get_class_name(obj) -> str:
  function get_datetime_utc (line 80) | def get_datetime_utc(date_str):
  function ppnb (line 86) | def ppnb(nb: float|int) -> float:
  function parse_oauth_flow_response (line 101) | def parse_oauth_flow_response(body: str):
  function humanize_list (line 108) | def humanize_list(array: List[any]):
  function unicode_patch (line 126) | def unicode_patch(txt: str):
  function show_version (line 135) | def show_version():
  function check_new_version (line 147) | def check_new_version() -> tuple[bool, dict[str, str]]:

FILE: ghunt/lib/httpx.py
  class AsyncClient (line 3) | class AsyncClient(httpx.AsyncClient):
    method __init__ (line 4) | def __init__(self, *args, **kwargs):
    method _merge_cookies (line 7) | def _merge_cookies(self, cookies: dict):

FILE: ghunt/modules/drive.py
  function show_user (line 21) | def show_user(user: DriveExtractedUser):
  function hunt (line 34) | async def hunt(as_client: httpx.AsyncClient, file_id: str, json_file: bo...

FILE: ghunt/modules/email.py
  function hunt (line 17) | async def hunt(as_client: httpx.AsyncClient, email_address: str, json_fi...

FILE: ghunt/modules/gaia.py
  function hunt (line 17) | async def hunt(as_client: httpx.AsyncClient, gaia_id: str, json_file: Pa...

FILE: ghunt/modules/geolocate.py
  function main (line 16) | async def main(as_client: httpx.AsyncClient, bssid: str, input_file: Pat...

FILE: ghunt/modules/login.py
  function check_and_login (line 14) | async def check_and_login(as_client: httpx.AsyncClient, clean: bool=Fals...

FILE: ghunt/modules/spiderdal.py
  class Asset (line 16) | class Asset:
  function identify_public_pkgs (line 21) | async def identify_public_pkgs(as_client: httpx.AsyncClient, pkg_name: s...
  function analyze_single (line 28) | async def analyze_single(as_client: httpx.AsyncClient, dal: DigitalAsset...
  function main (line 76) | async def main(url: str, package: str, fingerprint: str, strict: bool, j...

FILE: ghunt/objects/apis.py
  class EndpointConfig (line 19) | class EndpointConfig(SmartObj):
    method __init__ (line 20) | def __init__(self,
  class GAPI (line 45) | class GAPI(SmartObj):
    method __init__ (line 46) | def __init__(self):
    method _load_api (line 59) | def _load_api(self, creds: GHuntCreds, headers: Dict[str, str]):
    method _load_endpoint (line 69) | def _load_endpoint(self, endpoint: EndpointConfig):
    method _check_and_gen_authorization_token (line 108) | async def _check_and_gen_authorization_token(self, as_client: httpx.As...
    method _query (line 127) | async def _query(self, endpoint_name: str, as_client: httpx.AsyncClien...
  class Parser (line 153) | class Parser(SmartObj):

FILE: ghunt/objects/base.py
  class SmartObj (line 17) | class SmartObj():
  class AndroidCreds (line 20) | class AndroidCreds(SmartObj):
    method __init__ (line 21) | def __init__(self) -> None:
  class GHuntCreds (line 25) | class GHuntCreds(SmartObj):
    method __init__ (line 31) | def __init__(self, creds_path: str = "") -> None:
    method are_creds_loaded (line 44) | def are_creds_loaded(self) -> bool:
    method load_creds (line 47) | def load_creds(self, silent=False) -> None:
    method save_creds (line 71) | def save_creds(self, silent=False):
  class Position (line 90) | class Position(SmartObj):
    method __init__ (line 91) | def __init__(self):
  class MapsLocation (line 95) | class MapsLocation(SmartObj):
    method __init__ (line 96) | def __init__(self):
  class MapsReview (line 105) | class MapsReview(SmartObj):
    method __init__ (line 106) | def __init__(self):
  class MapsPhoto (line 113) | class MapsPhoto(SmartObj):
    method __init__ (line 114) | def __init__(self):
  class DriveExtractedUser (line 121) | class DriveExtractedUser(SmartObj):
    method __init__ (line 122) | def __init__(self):

FILE: ghunt/objects/encoders.py
  class GHuntEncoder (line 5) | class GHuntEncoder(json.JSONEncoder):
    method default (line 9) | def default(self, o: object) -> dict:

FILE: ghunt/objects/utils.py
  class TMPrinter (line 10) | class TMPrinter():
    method __init__ (line 14) | def __init__(self, rc: Console=Console(highlight=False)):
    method out (line 18) | def out(self, text: str, style: str=""):
    method clear (line 24) | def clear(self):

FILE: ghunt/parsers/calendar.py
  class ConferenceProperties (line 8) | class ConferenceProperties(Parser):
    method __init__ (line 9) | def __init__(self):
    method _scrape (line 12) | def _scrape(self, conference_props_data: Dict[str, any]):
  class Calendar (line 16) | class Calendar(Parser):
    method __init__ (line 17) | def __init__(self):
    method _scrape (line 23) | def _scrape(self, calendar_data: Dict[str, any]):
  class CalendarReminder (line 31) | class CalendarReminder(Parser):
    method __init__ (line 32) | def __init__(self):
    method _scrape (line 36) | def _scrape(self, reminder_data: Dict[str, any]):
  class CalendarPerson (line 40) | class CalendarPerson(Parser):
    method __init__ (line 41) | def __init__(self):
    method _scrape (line 46) | def _scrape(self, person_data: Dict[str, any]):
  class CalendarTime (line 51) | class CalendarTime(Parser):
    method __init__ (line 52) | def __init__(self):
    method _scrape (line 56) | def _scrape(self, time_data: Dict[str, any]):
  class CalendarReminders (line 64) | class CalendarReminders(Parser):
    method __init__ (line 65) | def __init__(self):
    method _scrape (line 69) | def _scrape(self, reminders_data: Dict[str, any]):
  class CalendarEvent (line 77) | class CalendarEvent(Parser):
    method __init__ (line 78) | def __init__(self):
    method _scrape (line 100) | def _scrape(self, event_data: Dict[str, any]):
  class CalendarEvents (line 136) | class CalendarEvents(Parser):
    method __init__ (line 137) | def __init__(self):
    method _scrape (line 146) | def _scrape(self, events_data: Dict[str, any]):

FILE: ghunt/parsers/clientauthconfig.py
  class CacBrand (line 5) | class CacBrand(Parser):
    method __init__ (line 6) | def __init__(self):
    method _scrape (line 27) | def _scrape(self, base_model_data: Dict[str, any]):
  class CacBrandState (line 52) | class CacBrandState(Parser):
    method __init__ (line 53) | def __init__(self):
    method _scrape (line 62) | def _scrape(self, brand_state_data: Dict[str, any]):
  class CacLimits (line 72) | class CacLimits(Parser):
    method __init__ (line 73) | def __init__(self):
    method _scrape (line 78) | def _scrape(self, limits_data: Dict[str, int]):
  class CacReview (line 83) | class CacReview(Parser):
    method __init__ (line 84) | def __init__(self):
    method _scrape (line 100) | def _scrape(self, review_data: Dict[str, any]):
  class CacRiscConfiguration (line 116) | class CacRiscConfiguration(Parser):
    method __init__ (line 117) | def __init__(self):
    method _scrape (line 123) | def _scrape(self, risc_configuration_data: Dict[str, any]):
  class CacVerifiedBrand (line 129) | class CacVerifiedBrand(Parser):
    method __init__ (line 130) | def __init__(self):
    method _scrape (line 138) | def _scrape(self, verified_brand_data: Dict[str, any]):
  class CacDisplayName (line 152) | class CacDisplayName(Parser):
    method __init__ (line 153) | def __init__(self):
    method _scrape (line 157) | def _scrape(self, display_name_data: Dict[str, str]):
  class CacStoredIconUrl (line 161) | class CacStoredIconUrl(Parser):
    method __init__ (line 162) | def __init__(self):
    method _scrape (line 166) | def _scrape(self, stored_icon_url_data: Dict[str, str]):
  class CacSupportEmail (line 170) | class CacSupportEmail(Parser):
    method __init__ (line 171) | def __init__(self):
    method _scrape (line 175) | def _scrape(self, support_email_data: Dict[str, str]):
  class CacHomePageUrl (line 179) | class CacHomePageUrl(Parser):
    method __init__ (line 180) | def __init__(self):
    method _scrape (line 184) | def _scrape(self, home_page_url_data: Dict[str, str]):
  class CacPrivacyPolicyUrl (line 188) | class CacPrivacyPolicyUrl(Parser):
    method __init__ (line 189) | def __init__(self):
    method _scrape (line 193) | def _scrape(self, privacy_policy_url_data: Dict[str, str]):
  class CacTermsOfServiceUrl (line 197) | class CacTermsOfServiceUrl(Parser):
    method __init__ (line 198) | def __init__(self):
    method _scrape (line 202) | def _scrape(self, terms_of_service_url_data: Dict[str, str]):

FILE: ghunt/parsers/digitalassetslinks.py
  class DalStatements (line 5) | class DalStatements(Parser):
    method __init__ (line 6) | def __init__(self):
    method _scrape (line 11) | def _scrape(self, digital_assets_links_base_model_data: Dict[str, any]):
  class DalStatement (line 20) | class DalStatement(Parser):
    method __init__ (line 21) | def __init__(self):
    method _scrape (line 26) | def _scrape(self, digital_assets_links_unknown_model1_data: Dict[str, ...
  class DalSource (line 33) | class DalSource(Parser):
    method __init__ (line 34) | def __init__(self):
    method _scrape (line 37) | def _scrape(self, digital_assets_links_source_data: Dict[str, any]):
  class DalWeb (line 41) | class DalWeb(Parser):
    method __init__ (line 42) | def __init__(self):
    method _scrape (line 45) | def _scrape(self, digital_assets_links_web_data: Dict[str, str]):
  class DalTarget (line 48) | class DalTarget(Parser):
    method __init__ (line 49) | def __init__(self):
    method _scrape (line 53) | def _scrape(self, digital_assets_links_target_data: Dict[str, any]):
  class DalAndroidApp (line 59) | class DalAndroidApp(Parser):
    method __init__ (line 60) | def __init__(self):
    method _scrape (line 64) | def _scrape(self, digital_assets_links_android_app_data: Dict[str, any]):
  class DalCertificate (line 69) | class DalCertificate(Parser):
    method __init__ (line 70) | def __init__(self):
    method _scrape (line 73) | def _scrape(self, digital_assets_links_certificate_data: Dict[str, str]):

FILE: ghunt/parsers/drive.py
  class DriveFile (line 8) | class DriveFile(Parser):
    method __init__ (line 9) | def __init__(self):
    method _scrape (line 90) | def _scrape(self, file_data: Dict[str, any]):
  class DriveLabels (line 202) | class DriveLabels(Parser):
    method __init__ (line 203) | def __init__(self):
    method _scrape (line 211) | def _scrape(self, labels_data: Dict[str, bool]):
  class DriveUserPermission (line 219) | class DriveUserPermission(Parser):
    method __init__ (line 220) | def __init__(self):
    method _scrape (line 225) | def _scrape(self, user_permission_data: Dict[str, str]):
  class DriveUser (line 230) | class DriveUser(Parser):
    method __init__ (line 231) | def __init__(self):
    method _scrape (line 241) | def _scrape(self, user_data: Dict[str, any]):
  class DriveCapabilities (line 252) | class DriveCapabilities(Parser):
    method __init__ (line 253) | def __init__(self):
    method _scrape (line 328) | def _scrape(self, capabilities_data: Dict[str, bool]):
  class DriveVideoMediaMetadata (line 403) | class DriveVideoMediaMetadata(Parser):
    method __init__ (line 404) | def __init__(self):
    method _scrape (line 409) | def _scrape(self, video_media_metadata_data: Dict[str, any]):
  class DriveLabelInfo (line 414) | class DriveLabelInfo(Parser):
    method __init__ (line 415) | def __init__(self):
    method _scrape (line 419) | def _scrape(self, label_info_data: Dict[str, any]):
  class DrivePermission (line 423) | class DrivePermission(Parser):
    method __init__ (line 424) | def __init__(self):
    method _scrape (line 443) | def _scrape(self, permission_data: Dict[str, any]):
  class DrivePermissionsSummary (line 463) | class DrivePermissionsSummary(Parser):
    method __init__ (line 464) | def __init__(self):
    method _scrape (line 469) | def _scrape(self, permissions_summary_data: Dict[str, any]):
  class DriveMiniPermission (line 482) | class DriveMiniPermission(Parser):
    method __init__ (line 483) | def __init__(self):
    method _scrape (line 489) | def _scrape(self, unknown_model4_data: Dict[str, any]):
  class DrivePicture (line 495) | class DrivePicture(Parser):
    method __init__ (line 496) | def __init__(self):
    method _scrape (line 499) | def _scrape(self, picture_data: Dict[str, str]):
  class DriveImageMediaMetadata (line 502) | class DriveImageMediaMetadata(Parser):
    method __init__ (line 503) | def __init__(self):
    method _scrape (line 508) | def _scrape(self, image_media_metadata_data: Dict[str, int]):
  class DriveLinkShareMetadata (line 513) | class DriveLinkShareMetadata(Parser):
    method __init__ (line 514) | def __init__(self):
    method _scrape (line 520) | def _scrape(self, link_share_metadata_data: Dict[str, any]):
  class DriveChildList (line 526) | class DriveChildList(Parser):
    method __init__ (line 527) | def __init__(self):
    method _scrape (line 533) | def _scrape(self, child_list_data: Dict[str, any]):
  class DriveChildReference (line 543) | class DriveChildReference(Parser):
    method __init__ (line 544) | def __init__(self):
    method _scrape (line 550) | def _scrape(self, child_reference_data: Dict[str, str]):
  class DriveApp (line 556) | class DriveApp(Parser):
    method __init__ (line 557) | def __init__(self):
    method _scrape (line 582) | def _scrape(self, app_data: Dict[str, any]):
  class DriveOpenWithLinks (line 607) | class DriveOpenWithLinks(Parser):
    method __init__ (line 608) | def __init__(self):
    method _scrape (line 611) | def _scrape(self, open_with_links_data: Dict[str, str]):
  class DriveParentReference (line 614) | class DriveParentReference(Parser):
    method __init__ (line 615) | def __init__(self):
    method _scrape (line 622) | def _scrape(self, parent_reference_data: Dict[str, any]):
  class DriveSource (line 629) | class DriveSource(Parser):
    method __init__ (line 630) | def __init__(self):
    method _scrape (line 634) | def _scrape(self, source_data: Dict[str, str]):
  class DriveFolderProperties (line 638) | class DriveFolderProperties(Parser):
    method __init__ (line 639) | def __init__(self):
    method _scrape (line 647) | def _scrape(self, folder_properties_data: Dict[str, bool]):
  class DriveCommentList (line 655) | class DriveCommentList(Parser):
    method __init__ (line 656) | def __init__(self):
    method _scrape (line 661) | def _scrape(self, comment_list_data: Dict[str, any]):
  class DriveComment (line 670) | class DriveComment(Parser):
    method __init__ (line 671) | def __init__(self):
    method _scrape (line 687) | def _scrape(self, comment_data: Dict[str, any]):
  class DriveCommentContext (line 709) | class DriveCommentContext(Parser):
    method __init__ (line 710) | def __init__(self):
    method _scrape (line 714) | def _scrape(self, context_data: Dict[str, str]):
  class DriveCommentReply (line 718) | class DriveCommentReply(Parser):
    method __init__ (line 719) | def __init__(self):
    method _scrape (line 729) | def _scrape(self, comment_reply_data: Dict[str, any]):

FILE: ghunt/parsers/geolocate.py
  class GeolocationResponse (line 7) | class GeolocationResponse(Parser):
    method __init__ (line 8) | def __init__(self):
    method _scrape (line 12) | def _scrape(self, base_model_data: dict[str, any]):

FILE: ghunt/parsers/identitytoolkit.py
  class ITKProjectConfig (line 5) | class ITKProjectConfig(Parser):
    method __init__ (line 6) | def __init__(self):
    method _scrape (line 10) | def _scrape(self, itk_project_config_data: Dict[str, any]):
  class ITKPublicKeys (line 14) | class ITKPublicKeys(Parser):
    method __init__ (line 15) | def __init__(self):
    method _scrape (line 22) | def _scrape(self, itk_public_keys_data: Dict[str, str]):
  class ITKSessionCookiePublicKeys (line 29) | class ITKSessionCookiePublicKeys(Parser):
    method __init__ (line 30) | def __init__(self):
    method _scrape (line 33) | def _scrape(self, itk_session_cookie_public_keys_data: Dict[str, list]):
  class ITKSessionCookiePublicKey (line 40) | class ITKSessionCookiePublicKey(Parser):
    method __init__ (line 41) | def __init__(self):
    method _scrape (line 49) | def _scrape(self, itk_session_cookie_public_key_data: Dict[str, str]):
  class ITKSignupNewUser (line 57) | class ITKSignupNewUser(Parser):
    method __init__ (line 58) | def __init__(self):
    method _scrape (line 66) | def _scrape(self, itk_signup_data: Dict[str, str]):
  class ITKVerifyPassword (line 74) | class ITKVerifyPassword(Parser):
    method __init__ (line 75) | def __init__(self):
    method _scrape (line 85) | def _scrape(self, itk_verify_password_data: Dict[str, any]):

FILE: ghunt/parsers/mobilesdk.py
  class MobileSDKDynamicConfig (line 5) | class MobileSDKDynamicConfig(Parser):
    method __init__ (line 6) | def __init__(self):
    method _scrape (line 13) | def _scrape(self, dynamic_config_base_model_data: Dict[str, str]):

FILE: ghunt/parsers/people.py
  class PersonGplusExtendedData (line 12) | class PersonGplusExtendedData(Parser):
    method __init__ (line 13) | def __init__(self):
    method _scrape (line 17) | def _scrape(self, gplus_data):
  class PersonDynamiteExtendedData (line 23) | class PersonDynamiteExtendedData(Parser):
    method __init__ (line 24) | def __init__(self):
    method _scrape (line 30) | def _scrape(self, dynamite_data):
  class PersonExtendedData (line 38) | class PersonExtendedData(Parser):
    method __init__ (line 39) | def __init__(self):
    method _scrape (line 43) | def _scrape(self, extended_data: Dict[str, any]):
  class PersonPhoto (line 50) | class PersonPhoto(Parser):
    method __init__ (line 51) | def __init__(self):
    method _scrape (line 56) | async def _scrape(self, as_client: httpx.AsyncClient, photo_data: Dict...
  class PersonEmail (line 69) | class PersonEmail(Parser):
    method __init__ (line 70) | def __init__(self):
    method _scrape (line 73) | def _scrape(self, email_data: Dict[str, any]):
  class PersonName (line 76) | class PersonName(Parser):
    method __init__ (line 77) | def __init__(self):
    method _scrape (line 82) | def _scrape(self, name_data: Dict[str, any]):
  class PersonProfileInfo (line 88) | class PersonProfileInfo(Parser):
    method __init__ (line 89) | def __init__(self):
    method _scrape (line 92) | def _scrape(self, profile_data: Dict[str, any]):
  class PersonSourceIds (line 96) | class PersonSourceIds(Parser):
    method __init__ (line 97) | def __init__(self):
    method _scrape (line 100) | def _scrape(self, source_ids_data: Dict[str, any]):
  class PersonInAppReachability (line 104) | class PersonInAppReachability(Parser):
    method __init__ (line 105) | def __init__(self):
    method _scrape (line 108) | def _scrape(self, apps_data, container_name: str):
  class PersonContainers (line 113) | class PersonContainers(dict):
  class Person (line 116) | class Person(Parser):
    method __init__ (line 117) | def __init__(self):
    method _scrape (line 128) | async def _scrape(self, as_client: httpx.AsyncClient, person_data: Dic...

FILE: ghunt/parsers/playgames.py
  class PlayerProfile (line 9) | class PlayerProfile(Parser):
    method __init__ (line 10) | def __init__(self):
    method _scrape (line 22) | def _scrape(self, player_data: Dict[str, any]):
  class PlayerPlayedApp (line 37) | class PlayerPlayedApp(Parser):
    method __init__ (line 38) | def __init__(self):
    method _scrape (line 45) | def _scrape(self, played_app_data: Dict[str, any]):
  class PlayerExperienceInfo (line 53) | class PlayerExperienceInfo(Parser):
    method __init__ (line 54) | def __init__(self):
    method _scrape (line 61) | def _scrape(self, experience_data: Dict[str, any]):
  class PlayerLevel (line 71) | class PlayerLevel(Parser):
    method __init__ (line 72) | def __init__(self):
    method _scrape (line 77) | def _scrape(self, level_data: Dict[str, any]):
  class PlayerProfileSettings (line 82) | class PlayerProfileSettings(Parser):
    method __init__ (line 83) | def __init__(self):
    method _scrape (line 86) | def _scrape(self, profile_settings_data: Dict[str, any]):
  class PlayedGames (line 91) | class PlayedGames(Parser):
    method __init__ (line 92) | def __init__(self):
    method _scrape (line 95) | def _scrape(self, games_data: Dict[str, any]):
  class PlayGame (line 101) | class PlayGame(Parser):
    method __init__ (line 102) | def __init__(self):
    method _scrape (line 109) | def _scrape(self, game_data: Dict[str, any]):
  class PlayGameMarketData (line 119) | class PlayGameMarketData(Parser):
    method __init__ (line 120) | def __init__(self):
    method _scrape (line 123) | def _scrape(self, market_data: Dict[str, any]):
  class PlayGameMarketInstance (line 130) | class PlayGameMarketInstance(Parser):
    method __init__ (line 131) | def __init__(self):
    method _scrape (line 148) | def _scrape(self, instance_data: Dict[str, any]):
  class PlayGameMarketRating (line 175) | class PlayGameMarketRating(Parser):
    method __init__ (line 176) | def __init__(self):
    method _scrape (line 180) | def _scrape(self, rating_data: Dict[str, any]):
  class PlayGameMarketBadge (line 184) | class PlayGameMarketBadge(Parser):
    method __init__ (line 185) | def __init__(self):
    method _scrape (line 191) | def _scrape(self, badge_data: Dict[str, any]):
  class PlayGameData (line 201) | class PlayGameData(Parser):
    method __init__ (line 202) | def __init__(self):
    method _scrape (line 216) | def _scrape(self, game_data: Dict[str, any]):
  class PlayGameInstance (line 240) | class PlayGameInstance(Parser):
    method __init__ (line 241) | def __init__(self):
    method _scrape (line 249) | def _scrape(self, instance_data: Dict[str, any]):
  class PlayGameAndroidInstance (line 259) | class PlayGameAndroidInstance(Parser):
    method __init__ (line 260) | def __init__(self):
    method _scrape (line 265) | def _scrape(self, android_instance_data: Dict[str, any]):
  class PlayGameImageAsset (line 270) | class PlayGameImageAsset(Parser):
    method __init__ (line 271) | def __init__(self):
    method _scrape (line 277) | def _scrape(self, image_data: Dict[str, any]):
  class PlayGameCategory (line 283) | class PlayGameCategory(Parser):
    method __init__ (line 284) | def __init__(self):
    method _scrape (line 287) | def _scrape(self, category_data: Dict[str, any]):
  class PlayerAchievements (line 292) | class PlayerAchievements(Parser):
    method __init__ (line 293) | def __init__(self):
    method _scrape (line 296) | def _scrape(self, achievements_data: Dict[str, any]):
  class PlayerAchievement (line 312) | class PlayerAchievement(Parser):
    method __init__ (line 313) | def __init__(self):
    method _scrape (line 321) | def _scrape(self, achievement_item_data: Dict[str, any]):
  class PlayerAchievementDefinition (line 329) | class PlayerAchievementDefinition(Parser):
    method __init__ (line 330) | def __init__(self):
    method _scrape (line 343) | def _scrape(self, achievement_def_data: Dict[str, any]):
  class Player (line 358) | class Player(Parser):
    method __init__ (line 359) | def __init__(self, profile: PlayerProfile = PlayerProfile(),

FILE: ghunt/parsers/playgateway.py
  class PlayerSearchResult (line 8) | class PlayerSearchResult(Parser):
    method __init__ (line 9) | def __init__(self):
    method _scrape (line 14) | def _scrape(self, player_result_data):
  class PlayerSearchResults (line 19) | class PlayerSearchResults(Parser):
    method __init__ (line 20) | def __init__(self):
    method _scrape (line 23) | def _scrape(self, proto_results: PlayerSearchResultsProto):
  class PlayerProfile (line 29) | class PlayerProfile(Parser):
    method __init__ (line 34) | def __init__(self):
    method _scrape (line 38) | def _scrape(self, proto_results: GetPlayerResponseProto):

FILE: ghunt/parsers/vision.py
  class VisionPosition (line 6) | class VisionPosition(Parser):
    method __init__ (line 7) | def __init__(self):
    method _scrape (line 12) | def _scrape(self, position_data: Dict[str, int]):
  class VisionLandmark (line 17) | class VisionLandmark(Parser):
    method __init__ (line 18) | def __init__(self):
    method _scrape (line 22) | def _scrape(self, landmark_data: Dict[str, any]):
  class VisionVertice (line 26) | class VisionVertice(Parser):
    method __init__ (line 27) | def __init__(self):
    method _scrape (line 31) | def _scrape(self, vertice_data: Dict[str, int]):
  class VisionVertices (line 35) | class VisionVertices(Parser):
    method __init__ (line 36) | def __init__(self):
    method _scrape (line 39) | def _scrape(self, vertices_data: List[Dict[str, int]]):
  class VisionFaceAnnotation (line 45) | class VisionFaceAnnotation(Parser):
    method __init__ (line 46) | def __init__(self):
    method _scrape (line 63) | def _scrape(self, face_data: Dict[str, any]):
  class VisionFaceDetection (line 86) | class VisionFaceDetection(Parser):
    method __init__ (line 87) | def __init__(self):
    method _scrape (line 90) | def _scrape(self, vision_data: Dict[str, any]):
Condensed preview — 91 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (655K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 14,
    "preview": "github: mxrch\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 616,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/workflows/sponsors.yml",
    "chars": 697,
    "preview": "name: Generate Sponsors README\non:\n  workflow_dispatch:\n  schedule:\n    - cron: 30 15 * * 0-6\njobs:\n  deploy:\n    runs-o"
  },
  {
    "path": ".gitignore",
    "chars": 51,
    "preview": ".DS_Store\nbuild/\ndist/\n__pycache__/\nghunt.egg-info/"
  },
  {
    "path": "LICENSE.md",
    "chars": 34263,
    "preview": "### For easier reading : https://choosealicense.com/licenses/agpl-3.0/\n\n\\\nGNU Affero General Public License\n============"
  },
  {
    "path": "README.md",
    "chars": 3895,
    "preview": "![](assets/long_banner.png)\n\n<br>\n\n#### 🌐 GHunt Online version : https://osint.industries\n#### 🐍 Now Python 3.13 compati"
  },
  {
    "path": "examples/email_registered.py",
    "chars": 469,
    "preview": "import os\n\nimport httpx\nimport asyncio\n\nimport sys\n\nfrom ghunt.helpers.gmail import is_email_registered\n\n\nasync def main"
  },
  {
    "path": "examples/get_people_name.py",
    "chars": 1676,
    "preview": "import httpx\n\nimport asyncio\nimport sys\n\nfrom ghunt.apis.peoplepa import PeoplePaHttp\nfrom ghunt.objects.base import GHu"
  },
  {
    "path": "ghunt/__init__.py",
    "chars": 50,
    "preview": "from ghunt import globals as gb; gb.init_globals()"
  },
  {
    "path": "ghunt/apis/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/apis/accounts.py",
    "chars": 1687,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/calendar.py",
    "chars": 3548,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/clientauthconfig.py",
    "chars": 1595,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/digitalassetslinks.py",
    "chars": 2782,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/drive.py",
    "chars": 4195,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/fireconsolepa.py",
    "chars": 1691,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/geolocation.py",
    "chars": 2022,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/identitytoolkit.py",
    "chars": 1603,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/mobilesdk.py",
    "chars": 3322,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/peoplepa.py",
    "chars": 7332,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/playgames.py",
    "chars": 4216,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/apis/playgateway.py",
    "chars": 4517,
    "preview": "from ghunt.objects.apis import GAPI, EndpointConfig\nfrom ghunt.objects.base import GHuntCreds\nfrom ghunt import globals "
  },
  {
    "path": "ghunt/apis/vision.py",
    "chars": 3793,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.errors import *\nimport ghunt.globals as gb\nfrom ghunt.objects.apis "
  },
  {
    "path": "ghunt/cli.py",
    "chars": 4783,
    "preview": "from rich_argparse import RichHelpFormatter\n\nimport argparse\nfrom typing import *\nimport sys\nfrom pathlib import Path\n\n\n"
  },
  {
    "path": "ghunt/config.py",
    "chars": 2926,
    "preview": "headers = {\n    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0',\n    'Connection': 'K"
  },
  {
    "path": "ghunt/errors.py",
    "chars": 871,
    "preview": "class GHuntKnowledgeError(Exception):\n    pass\n\nclass GHuntCorruptedHeadersError(Exception):\n    pass\n\nclass GHuntUnknow"
  },
  {
    "path": "ghunt/ghunt.py",
    "chars": 488,
    "preview": "import os\nimport sys\n\n\ndef main():\n    version = sys.version_info\n    if (version < (3, 10)):\n        print('[-] GHunt o"
  },
  {
    "path": "ghunt/globals.py",
    "chars": 329,
    "preview": "# This file is only intended to serve global variables at a project-wide level.\n\n\ndef init_globals():\n    from ghunt.obj"
  },
  {
    "path": "ghunt/helpers/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/helpers/auth.py",
    "chars": 9198,
    "preview": "import asyncio\nimport json\nimport base64\nimport os\nfrom typing import *\n\nimport httpx\nfrom bs4 import BeautifulSoup as b"
  },
  {
    "path": "ghunt/helpers/banner.py",
    "chars": 1117,
    "preview": "from ghunt import globals as gb\n\ndef show_banner():\n\n    banner = \"\"\"\n    [red] .d8888b.  [/][blue]888    888[/][red]   "
  },
  {
    "path": "ghunt/helpers/calendar.py",
    "chars": 3627,
    "preview": "from xmlrpc.client import Boolean\nfrom dateutil.relativedelta import relativedelta\nfrom beautifultable import BeautifulT"
  },
  {
    "path": "ghunt/helpers/drive.py",
    "chars": 2299,
    "preview": "from typing import *\n\nfrom ghunt.parsers.drive import DriveComment, DriveCommentList, DriveCommentReply, DriveFile\nfrom "
  },
  {
    "path": "ghunt/helpers/gcp.py",
    "chars": 1132,
    "preview": "import dns.message\nimport dns.asyncquery\nimport httpx\n\nfrom ghunt.objects.base import GHuntCreds\nfrom ghunt.apis.identit"
  },
  {
    "path": "ghunt/helpers/gmail.py",
    "chars": 364,
    "preview": "import httpx\n\n\nasync def is_email_registered(as_client: httpx.AsyncClient, email: str) -> bool:\n    \"\"\"\n        Abuse th"
  },
  {
    "path": "ghunt/helpers/gmaps.py",
    "chars": 16749,
    "preview": "from dateutil.relativedelta import relativedelta\nfrom datetime import datetime\nimport json\nfrom geopy import distance\nfr"
  },
  {
    "path": "ghunt/helpers/ia.py",
    "chars": 1098,
    "preview": "import os\n\nfrom ghunt import globals as gb\nfrom ghunt.apis.vision import VisionHttp\n\nimport httpx\n\nfrom base64 import b6"
  },
  {
    "path": "ghunt/helpers/iam.py",
    "chars": 1140,
    "preview": "import httpx\nimport asyncio\n\nfrom ghunt.objects.base import GHuntCreds\nfrom ghunt.apis.mobilesdk import MobileSDKPaHttp\n"
  },
  {
    "path": "ghunt/helpers/knowledge.py",
    "chars": 1858,
    "preview": "from ghunt.knowledge.services import services_baseurls\nfrom ghunt.knowledge.keys import keys\nfrom ghunt.knowledge.maps i"
  },
  {
    "path": "ghunt/helpers/listener.py",
    "chars": 1679,
    "preview": "import os\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nfrom typing import *\n\nfrom ghunt.objects.base impor"
  },
  {
    "path": "ghunt/helpers/playgames.py",
    "chars": 3604,
    "preview": "from ghunt.objects.base import GHuntCreds\nfrom ghunt.apis.playgames import PlayGames\nfrom ghunt.apis.playgateway import "
  },
  {
    "path": "ghunt/helpers/playstore.py",
    "chars": 259,
    "preview": "import httpx\n\n\nasync def app_exists(as_client: httpx.AsyncClient, package: str) -> bool:\n    params = {\n        \"id\": pa"
  },
  {
    "path": "ghunt/helpers/utils.py",
    "chars": 5211,
    "preview": "from pathlib import Path\nfrom PIL import Image\nimport hashlib\nfrom typing import *\nfrom time import time\nfrom datetime i"
  },
  {
    "path": "ghunt/knowledge/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/knowledge/drive.py",
    "chars": 3847,
    "preview": "default_file_capabilities = [\n    'can_block_owner',\n    'can_copy',\n    'can_download',\n    'can_print',\n    'can_read'"
  },
  {
    "path": "ghunt/knowledge/iam.py",
    "chars": 274252,
    "preview": "permissions = [\n    \"accessapproval.requests.approve\",\n    \"accessapproval.requests.dismiss\",\n    \"accessapproval.reques"
  },
  {
    "path": "ghunt/knowledge/keys.py",
    "chars": 861,
    "preview": "keys = {\n    \"pantheon\": {\"key\": \"AIzaSyCI-zsRP85UVOi0DjtiCwWBwQ1djDy741g\", \"origin\": \"https://console.cloud.google.com\""
  },
  {
    "path": "ghunt/knowledge/maps.py",
    "chars": 1034,
    "preview": "types_translations = {\n    'airport': 'Airport',\n    'atm': 'ATM',\n    'bar': 'Bar',\n    'bank_intl': 'Bank',\n    'bus':"
  },
  {
    "path": "ghunt/knowledge/people.py",
    "chars": 1001,
    "preview": "# https://developers.google.com/people/api/rest/v1/people#usertype\nuser_types = {\n    \"USER_TYPE_UNKNOWN\": \"The user typ"
  },
  {
    "path": "ghunt/knowledge/services.py",
    "chars": 103,
    "preview": "services_baseurls = {\n    \"cloudconsole\": \"console.cloud.google.com\",\n    \"cl\": \"calendar.google.com\"\n}"
  },
  {
    "path": "ghunt/knowledge/sig.py",
    "chars": 546,
    "preview": "sigs = {\n    \"com.google.android.play.games\": \"38918a453d07199354f8b19af05ec6562ced5788\",\n    \"com.google.android.apps.d"
  },
  {
    "path": "ghunt/lib/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/lib/httpx.py",
    "chars": 255,
    "preview": "import httpx\n\nclass AsyncClient(httpx.AsyncClient):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*a"
  },
  {
    "path": "ghunt/modules/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/modules/drive.py",
    "chars": 8416,
    "preview": "import os\n\nfrom ghunt.helpers.utils import *\nfrom ghunt.objects.base import DriveExtractedUser, GHuntCreds\nfrom ghunt.ap"
  },
  {
    "path": "ghunt/modules/email.py",
    "chars": 6333,
    "preview": "import os\n\nfrom ghunt import globals as gb\nfrom ghunt.helpers.utils import get_httpx_client\nfrom ghunt.objects.base impo"
  },
  {
    "path": "ghunt/modules/gaia.py",
    "chars": 4491,
    "preview": "import os\n\nfrom ghunt import globals as gb\nfrom ghunt.objects.base import GHuntCreds\nfrom ghunt.apis.peoplepa import Peo"
  },
  {
    "path": "ghunt/modules/geolocate.py",
    "chars": 2450,
    "preview": "import os\n\nfrom ghunt import globals as gb\nfrom ghunt.helpers.utils import get_httpx_client\nfrom ghunt.apis.geolocation "
  },
  {
    "path": "ghunt/modules/login.py",
    "chars": 3325,
    "preview": "import os\nfrom typing import *\n\nimport httpx\nfrom pathlib import Path\n\nfrom ghunt import globals as gb\nfrom ghunt.helper"
  },
  {
    "path": "ghunt/modules/spiderdal.py",
    "chars": 7929,
    "preview": "import asyncio\nfrom dataclasses import dataclass\nfrom pathlib import Path\n\nfrom ghunt import globals as gb\nfrom ghunt.ob"
  },
  {
    "path": "ghunt/objects/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/objects/apis.py",
    "chars": 7255,
    "preview": "from ghunt.errors import GHuntCorruptedHeadersError\nfrom ghunt.helpers.knowledge import get_origin_of_key, get_api_key\nf"
  },
  {
    "path": "ghunt/objects/base.py",
    "chars": 3979,
    "preview": "from typing import *\nfrom pathlib import Path\nimport json\nfrom dateutil.relativedelta import relativedelta\nfrom datetime"
  },
  {
    "path": "ghunt/objects/encoders.py",
    "chars": 574,
    "preview": "import json\nfrom datetime import datetime\n\n\nclass GHuntEncoder(json.JSONEncoder):\n    \"\"\"\n        Converts non-default t"
  },
  {
    "path": "ghunt/objects/session.py",
    "chars": 627,
    "preview": "from typing import *\n\nimport httpx\nfrom ghunt.helpers.utils import get_httpx_client\nfrom ghunt.helpers import auth\nfrom "
  },
  {
    "path": "ghunt/objects/utils.py",
    "chars": 662,
    "preview": "from ghunt.helpers.utils import *\nfrom ghunt.errors import *\nfrom ghunt.objects.base import SmartObj\n\nfrom typing import"
  },
  {
    "path": "ghunt/parsers/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/parsers/calendar.py",
    "chars": 6593,
    "preview": "from datetime import datetime\nfrom typing import *\n\nfrom ghunt.helpers.utils import get_datetime_utc\nfrom ghunt.objects."
  },
  {
    "path": "ghunt/parsers/clientauthconfig.py",
    "chars": 8326,
    "preview": "from typing import *\nfrom ghunt.objects.apis import Parser\n\n\nclass CacBrand(Parser):\n\tdef __init__(self):\n\t\tself.brand_i"
  },
  {
    "path": "ghunt/parsers/digitalassetslinks.py",
    "chars": 2748,
    "preview": "from typing import *\nfrom ghunt.objects.apis import Parser\n\n\nclass DalStatements(Parser):\n\tdef __init__(self):\n\t\tself.st"
  },
  {
    "path": "ghunt/parsers/drive.py",
    "chars": 32616,
    "preview": "from typing import *\nfrom datetime import datetime\n\nfrom ghunt.helpers.utils import get_datetime_utc\nfrom ghunt.objects."
  },
  {
    "path": "ghunt/parsers/geolocate.py",
    "chars": 508,
    "preview": "from ghunt.objects.apis import Parser\nfrom ghunt.objects.base import Position\n\nfrom typing import *\n\n\nclass GeolocationR"
  },
  {
    "path": "ghunt/parsers/identitytoolkit.py",
    "chars": 3259,
    "preview": "from typing import *\nfrom ghunt.objects.apis import Parser\n\n\nclass ITKProjectConfig(Parser):\n\tdef __init__(self):\n\t\tself"
  },
  {
    "path": "ghunt/parsers/mobilesdk.py",
    "chars": 717,
    "preview": "from typing import *\nfrom ghunt.objects.apis import Parser\n\n\nclass MobileSDKDynamicConfig(Parser):\n\tdef __init__(self):\n"
  },
  {
    "path": "ghunt/parsers/people.py",
    "chars": 7449,
    "preview": "from typing import *\nfrom datetime import datetime\n\nfrom ghunt.errors import *\nfrom ghunt.helpers.utils import is_defaul"
  },
  {
    "path": "ghunt/parsers/playgames.py",
    "chars": 15797,
    "preview": "from typing import *\nfrom datetime import datetime\n\nfrom ghunt.objects.apis import Parser\n\n\n### Profile\n\nclass PlayerPro"
  },
  {
    "path": "ghunt/parsers/playgateway.py",
    "chars": 1671,
    "preview": "from typing import *\n\nfrom ghunt.protos.playgatewaypa.search_player_results_pb2 import PlayerSearchResultsProto\nfrom ghu"
  },
  {
    "path": "ghunt/parsers/vision.py",
    "chars": 3724,
    "preview": "from ghunt.objects.apis import Parser\n\nfrom typing import *\n\n\nclass VisionPosition(Parser):\n    def __init__(self):\n    "
  },
  {
    "path": "ghunt/protos/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/protos/playgatewaypa/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ghunt/protos/playgatewaypa/definitions/get_player.proto",
    "chars": 193,
    "preview": "syntax = \"proto3\";\n\n\nmessage GetPlayerProto {\n\n    message Form {\n  \n        message Query {\n            string id = 1;\n"
  },
  {
    "path": "ghunt/protos/playgatewaypa/definitions/get_player_response.proto",
    "chars": 3837,
    "preview": "syntax = \"proto3\";\n\n\nmessage GetPlayerResponseProto {\n\n  message field1_type {\n\n    message Results {\n\n      message Pla"
  },
  {
    "path": "ghunt/protos/playgatewaypa/definitions/search_player.proto",
    "chars": 217,
    "preview": "syntax = \"proto3\";\n\n\nmessage PlayerSearchProto {\n\n    message SearchForm {\n  \n        message Query {\n            string"
  },
  {
    "path": "ghunt/protos/playgatewaypa/definitions/search_player_results.proto",
    "chars": 652,
    "preview": "syntax = \"proto3\";\n\n\nmessage PlayerSearchResultsProto {\n\n  message field1_type {\n\n    message Results {\n\n      message f"
  },
  {
    "path": "ghunt/protos/playgatewaypa/get_player_pb2.py",
    "chars": 2384,
    "preview": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: definitions/get_player.prot"
  },
  {
    "path": "ghunt/protos/playgatewaypa/get_player_response_pb2.py",
    "chars": 40124,
    "preview": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: definitions/get_player_resp"
  },
  {
    "path": "ghunt/protos/playgatewaypa/search_player_pb2.py",
    "chars": 2590,
    "preview": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: definitions/search_player.p"
  },
  {
    "path": "ghunt/protos/playgatewaypa/search_player_results_pb2.py",
    "chars": 8118,
    "preview": "# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: definitions/search_player_r"
  },
  {
    "path": "ghunt/version.py",
    "chars": 71,
    "preview": "metadata = {\n    \"version\": \"2.3.4\",\n    \"name\": \"🕷️  Spider Edition\"\n}"
  },
  {
    "path": "main.py",
    "chars": 68,
    "preview": "if __name__ == \"__main__\":\n    from ghunt import ghunt; ghunt.main()"
  },
  {
    "path": "pyproject.toml",
    "chars": 1523,
    "preview": "[project]\nname = \"ghunt\"\nversion = \"2.3.4\"\nauthors = [\n    {name = \"mxrch\", email = \"mxrch.dev@pm.me\"},\n]\ndescription = "
  }
]

About this extraction

This page contains the full source code of the mxrch/GHunt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 91 files (599.5 KB), approximately 145.6k tokens, and a symbol index with 474 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!