Full Code of sudoguy/tiktok_bot for AI

master 3740c6d0e9a1 cached
47 files
59.5 KB
16.3k tokens
122 symbols
1 requests
Download .txt
Repository: sudoguy/tiktok_bot
Branch: master
Commit: 3740c6d0e9a1
Files: 47
Total size: 59.5 KB

Directory structure:
gitextract_wm8u40tj/

├── .bumpversion.cfg
├── .flake8
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── pythonpackage.yml
├── .gitignore
├── .isort.cfg
├── .pre-commit-config.yaml
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── docs/
│   ├── css/
│   │   └── custom.css
│   └── index.md
├── mkdocs.yml
├── pyproject.toml
├── tests/
│   ├── api/
│   │   └── test_api.py
│   ├── conftest.py
│   └── test_tiktok-bot.py
└── tiktok_bot/
    ├── __init__.py
    ├── api/
    │   ├── __init__.py
    │   ├── api.py
    │   └── config.py
    ├── bot/
    │   ├── __init__.py
    │   └── bot.py
    ├── client/
    │   ├── __init__.py
    │   ├── client.py
    │   └── utils.py
    └── models/
        ├── __init__.py
        ├── category.py
        ├── comment.py
        ├── feed.py
        ├── feed_enums.py
        ├── follow.py
        ├── follower.py
        ├── hashtag.py
        ├── like.py
        ├── login.py
        ├── music.py
        ├── post.py
        ├── qr-code.py
        ├── request.py
        ├── search.py
        ├── sticker.py
        ├── tag.py
        ├── user.py
        └── video.py

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

================================================
FILE: .bumpversion.cfg
================================================
[bumpversion]
commit = True
tag = True
current_version = 0.6.4

[bumpversion:file:pyproject.toml]

[bumpversion:file:tiktok_bot/__init__.py]

[bumpversion:file:tests/test_tiktok-bot.py]



================================================
FILE: .flake8
================================================
[flake8]
max-line-length = 100
ignore = C812,W503


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [sudoguy]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/workflows/pythonpackage.yml
================================================
name: Main

on: [push, pull_request]

jobs:
  Linting:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1
      - name: Set up Python 3.7
        uses: actions/setup-python@v1
        with:
          python-version: 3.7
      - name: Linting
        run: |
          pip install pre-commit
          pre-commit run --all-files

  Linux:
    needs: Linting
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.6, 3.7]

    steps:
    - uses: actions/checkout@v1
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install Poetry
      run: |
        PATH=$PATH:/home/runner/.local/bin

        pip install --pre --user poetry

        poetry config virtualenvs.create false
        poetry shell
    - name: Install dependencies
      run: |
        PATH=$PATH:/home/runner/.local/bin

        poetry install
    - name: Test
      run: |
        PATH=$PATH:/home/runner/.local/bin

        poetry run pytest -q tests

  MacOS:
    needs: Linting
    runs-on: macos-latest
    strategy:
      matrix:
        python-version: [3.6, 3.7]

    steps:
    - uses: actions/checkout@v1
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install Poetry
      run: |
        PATH=$PATH:/Users/runner/.local/bin

        pip install --pre --user poetry

        poetry config virtualenvs.create false
        poetry shell
    - name: Install dependencies
      run: |
        PATH=$PATH:/Users/runner/.local/bin

        poetry install
    - name: Test
      run: |
        PATH=$PATH:/Users/runner/.local/bin

        poetry run pytest -q tests

  Windows:
    needs: Linting
    runs-on: windows-latest
    strategy:
      matrix:
        python-version: [3.6, 3.7]

    steps:
    - uses: actions/checkout@v1
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install Poetry
      run: |
        pip install --pre --user poetry
        poetry shell
    - name: Install dependencies
      run: |
        poetry install
    - name: Test
      run: |
        poetry run pytest -q tests


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

# VS Code
.vscode


================================================
FILE: .isort.cfg
================================================
[settings]
line_length=100
indent=4

multi_line_output=3
include_trailing_comma=True

known_first_party=tiktok_bot
known_third_party = httpx,loguru,pydantic,pytest,tqdm,typing_extensions


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v2.4.0
  hooks:
    - id: check-added-large-files
    - id: debug-statements
    - id: end-of-file-fixer
      exclude: '.bumpversion.cfg'
    - id: check-symlinks
    - id: trailing-whitespace
    - id: mixed-line-ending
      args: ['--fix=lf']

- repo: https://github.com/humitos/mirrors-autoflake
  rev: v1.1
  hooks:
    - id: autoflake
      args: ['--in-place', '--remove-all-unused-imports', '--remove-unused-variable']

- repo: https://github.com/asottile/seed-isort-config
  rev: v1.9.3
  hooks:
  - id: seed-isort-config
- repo: https://github.com/pre-commit/mirrors-isort
  rev: v4.3.21
  hooks:
  - id: isort

- repo: https://github.com/psf/black
  rev: 19.10b0
  hooks:
  - id: black
    files: '\.py$'

- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v2.4.0
  hooks:
    - id: flake8
      additional_dependencies: [
      'flake8-blind-except',
      'flake8-commas',
      'flake8-comprehensions',
      'flake8-deprecated',
      'flake8-mutable',
      'flake8-tidy-imports',
      'flake8-print',
      ]


================================================
FILE: .travis.yml
================================================
language: python

os:
  - linux

stages:
  - lint
  - test
  - name: deploy
    if: tag IS present

python:
  - "3.6"
  - "3.7"
  - "3.8"
  - "nightly"
  - "pypy3"

install:
  - pip install --upgrade pip

script: skip

jobs:
  include:
    - stage: lint
      install:
        - pip install pre-commit
        - pre-commit install-hooks
      script:
        - pre-commit run --all-files
    - stage: test
      install:
        - pip install --pre poetry
        - poetry install -v
      script:
        - poetry run pytest
    - stage: deploy
      script:
        - echo Deploying to PyPI...

before_deploy:
  - pip install --upgrade pip
  - pip install poetry
  - poetry config http-basic.pypi $PYPI_USER $PYPI_PASSWORD
  - poetry build

deploy:
  provider: script
  script: poetry publish
  skip_cleanup: true
  on:
    all_branches: true  # Travis recognizes tag names as "branches"
    condition: $TRAVIS_BUILD_STAGE_NAME = deploy
    repo: sudoguy/tiktok_bot
    tags: true

notifications:
  email:
    on_success: change
    on_failure: always

before_cache:
  - rm -f $HOME/.cache/pip/log/debug.log

cache:
  pip: true
  directories:
    - "$HOME/.cache/pre-commit"
    - .venv


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## 0.6.1 (November 17, 2019)

### Added

- Changelog

## 0.6.0 (November 17, 2019)

### Added

- Method for searches posts(videos) by hashtag name - `search_posts_by_hashtag`

### Fixed

- Set some fields in `music` and `post` models as `Optional`


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
 advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
 address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
 professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at eskemerov@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 Evgeny Kemerov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# This project is no longer under development.
# A NEW project is being developed here: [sudoguy/tiktokpy](https://github.com/sudoguy/tiktokpy/)

---

<h1 align="center" style="font-size: 3rem;">
tiktok-bot
</h1>
<p align="center">
 <em>The most intelligent TikTok bot for Python.</em></p>

<p align="center">
<a href="https://travis-ci.org/sudoguy/tiktok_bot">
    <img src="https://travis-ci.org/sudoguy/tiktok_bot.svg?branch=master" alt="Build Status">
</a>
<a href="https://pepy.tech/project/tiktok-bot">
    <img src="https://pepy.tech/badge/tiktok-bot" alt="Downloads">
</a>
<a href="https://pypi.org/project/tiktok-bot/">
    <img src="https://badge.fury.io/py/tiktok-bot.svg" alt="Package version">
</a>
</p>


**Note**: *This project should be considered as an **"alpha"** release.*

---

## Quickstart

```python
from tiktok_bot import TikTokBot

bot = TikTokBot()

# getting your feed (list of posts)
my_feed = bot.list_for_you_feed(count=25)

popular_posts = [post for post in my_feed if post.statistics.play_count > 1_000_000]

# extract video urls without watermark (every post has helpers)
urls = [post.video_url_without_watermark for post in popular_posts]

# searching videos by hashtag name
posts = bot.search_posts_by_hashtag("cat", count=50)
```

## Installation

Install with pip:

```shell
pip install tiktok-bot
```

tiktok-bot requires Python 3.6+


================================================
FILE: docs/css/custom.css
================================================
div.autodoc-docstring {
  padding-left: 20px;
  margin-bottom: 30px;
  border-left: 5px solid rgba(230, 230, 230);
}

div.autodoc-members {
  padding-left: 20px;
  margin-bottom: 15px;
}


================================================
FILE: docs/index.md
================================================
# Welcome to MkDocs

For full documentation visit [mkdocs.org](https://mkdocs.org).

## Commands

* `mkdocs new [dir-name]` - Create a new project.
* `mkdocs serve` - Start the live-reloading docs server.
* `mkdocs build` - Build the documentation site.
* `mkdocs help` - Print this help message.

## Project layout

    mkdocs.yml    # The configuration file.
    docs/
        index.md  # The documentation homepage.
        ...       # Other markdown pages, images and other files.


================================================
FILE: mkdocs.yml
================================================
site_name: TikTok Bot
site_description: Free TikTok bot for Python.

theme:
    name: "material"

repo_name: sudoguy/tiktok_bot
repo_url: https://github.com/sudoguy/tiktok-bot
edit_uri: ""

nav:
    - Introduction: 'index.md'

markdown_extensions:
  - admonition
  - codehilite
  - mkautodoc

extra_css:
    - css/custom.css


================================================
FILE: pyproject.toml
================================================
[tool.poetry]
name = "tiktok_bot"
version = "0.6.4"
description = "Tik Tok API"

license = "MIT"
authors = ["Evgeny Kemerov <eskemerov@gmail.com>"]

readme = "README.md"
include = ["CHANGELOG.md", "LICENSE"]

repository = "https://github.com/sudoguy/tiktok_bot/"
homepage = "https://github.com/sudoguy/tiktok_bot/"

keywords = ["tiktok", "bot", "api", "wrapper", "tiktokbot"]

classifiers = [
    "Development Status :: 3 - Alpha",
    "Environment :: Console",
    "Intended Audience :: Science/Research",
    "Intended Audience :: Information Technology",
    "Intended Audience :: End Users/Desktop",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
    "Topic :: Software Development :: Libraries :: Python Modules",
]

[tool.poetry.dependencies]
python = "^3.6"
httpx = "^0.7.4"
pydantic = "^0.32.2"
loguru = "^0.3.2"
typing-extensions = "^3.7.4"
tqdm = "^4.38.0"

[tool.poetry.dev-dependencies]
pytest = "^5.1.2"
pytest-cov = "^2.7.1"
pytest-mock = "^1.10.4"
pre-commit = "^1.18.3"
pytest-sugar = "^0.9.2"
black = {version = "^19.3b0", allow-prereleases = true}
ipython = "^7.8.0"
flake8 = "^3.7.8"
flake8-blind-except = "^0.1.1"
flake8-commas = "^2.0.0"
flake8-comprehensions = "^2.2.0"
flake8-deprecated = "^1.3"
flake8-mutable = "^1.2.0"
flake8-tidy-imports = "^2.0.0"
flake8-print = "^3.1.0"
isort = "^4.3.21"
mypy = "^0.740"
mkdocs = "^1.0.4"
mkdocs-material = "^4.4.3"
mkautodoc = "^0.1.0"
bump2version = "^0.5.11"

[tool.black]
line-length = 100
target-version = ['py36', 'py37', 'py38']
include = '\.pyi?$'
exclude = '''
/(
    \.eggs
  | \.git
  | \.hg
  | \.mypy_cache
  | \.tox
  | \.venv
  | _build
  | buck-out
  | build
  | dist
)/
'''
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"


================================================
FILE: tests/api/test_api.py
================================================
from tiktok_bot.api import TikTokAPI


def test_encrypt_with_xor(api: TikTokAPI):
    assert api.encrypt_with_XOR("user@example.com") == "7076607745607d64687569602b666a68"
    assert api.encrypt_with_XOR("password") == "75647676726a7761"


================================================
FILE: tests/conftest.py
================================================
import pytest

from tiktok_bot.api import TikTokAPI


@pytest.fixture()
def api():
    return TikTokAPI()


================================================
FILE: tests/test_tiktok-bot.py
================================================
from tiktok_bot import __version__


def test_version():
    assert __version__ == "0.6.4"


================================================
FILE: tiktok_bot/__init__.py
================================================
from .bot import TikTokBot
from .client import client

__all__ = ["TikTokBot", "client"]

__version__ = "0.6.4"


================================================
FILE: tiktok_bot/api/__init__.py
================================================
from .api import TikTokAPI

__all__ = ["TikTokAPI"]


================================================
FILE: tiktok_bot/api/api.py
================================================
import itertools
from typing import List

from loguru import logger
from tqdm import tqdm

from tiktok_bot.client import HTTPClient
from tiktok_bot.models.category import ListCategoriesRequest, ListCategoriesResponse
from tiktok_bot.models.feed import ListFeedRequest, ListFeedResponse, ListForYouFeedResponse
from tiktok_bot.models.feed_enums import FeedType, PullType
from tiktok_bot.models.follow import FollowRequest, FollowResponse
from tiktok_bot.models.hashtag import ListPostsInHashtagRequest, ListPostsInHashtagResponse
from tiktok_bot.models.login import LoginRequest, LoginResponse
from tiktok_bot.models.post import Post
from tiktok_bot.models.search import (
    ChallengeInfo,
    HashtagSearchResponse,
    HashtagSearchResult,
    SearchRequest,
    UserSearchRequest,
    UserSearchResponse,
    UserSearchResult,
)
from tiktok_bot.models.user import UserProfileResponse

from .config import DEFAULT_HEADERS, DEFAULT_PARAMS


class TikTokAPI:
    def __init__(self):
        self.client = HTTPClient(
            base_url="https://api2.musical.ly/",
            default_headers=DEFAULT_HEADERS,
            default_params=DEFAULT_PARAMS,
        )

    @staticmethod
    def encrypt_with_XOR(value: str, key=5) -> str:
        return "".join([hex(int(x ^ key))[2:] for x in value.encode("utf-8")])

    def login(self, login_request: LoginRequest) -> LoginResponse:
        """ FIXME: Under development """

        url = "passport/user/login/"

        response = self.client.post(url=url, data=login_request.dict())

        login = LoginResponse(**response.json())

        return login

    def login_with_email(self, email: str, password: str, captcha: str = ""):
        """ FIXME: Under development """

        email = self.encrypt_with_XOR(email)
        password = self.encrypt_with_XOR(password)

        request = LoginRequest(email=email, password=password, captcha=captcha)

        return self.login(request)

    def _list_for_you_feed(self, list_feed_request: ListFeedRequest) -> ListForYouFeedResponse:
        "Lists posts in the For You feed."

        url = "aweme/v1/feed/"

        response = self.client.get(url=url, params=list_feed_request.dict())

        feed = ListForYouFeedResponse(**response.json())

        return feed

    def list_for_you_feed(self, count: int) -> List[Post]:
        "Lists posts in the For You feed with paginate."
        feed: List[Post] = []

        logger.info(f"Getting {count} posts from your feed")

        with tqdm(total=count, desc="List for you feed") as pbar:
            for cursor in itertools.count(start=0, step=6):
                request = ListFeedRequest(
                    count=count,
                    max_cursor=cursor,
                    pull_type=PullType.LoadMore,
                    type=FeedType.ForYou,
                    is_cold_start=1,
                )
                response = self._list_for_you_feed(list_feed_request=request)

                feed += response.aweme_list

                pbar.update(len(response.aweme_list))

                if not response.has_more or len(feed) >= count:
                    feed = feed[:count]
                    logger.info(f"Found {len(feed)} results")
                    break

        return feed

    def list_following_feed(self, list_feed_request: ListFeedRequest) -> ListFeedResponse:
        "Lists posts in the Following feed."

        url = "aweme/v1/feed/"

        response = self.client.get(url=url, params=list_feed_request.dict())

        feed = ListFeedResponse(**response.json())

        return feed

    def list_categories(
        self, list_categories_request: ListCategoriesRequest
    ) -> ListCategoriesResponse:
        "Lists popular categories/hashtags."

        url = "aweme/v1/category/list/"

        response = self.client.get(url=url, params=list_categories_request.dict())

        categories = ListCategoriesResponse(**response.json())

        return categories

    def get_user(self, user_id: str) -> UserProfileResponse:
        "Gets a user's profile."

        url = "aweme/v1/user/"

        response = self.client.get(url=url, params={"user_id": user_id})

        user = UserProfileResponse(**response.json())

        return user

    def _search_users(self, user_search_request: UserSearchRequest) -> UserSearchResponse:
        "Searches for users."

        url = "aweme/v1/discover/search/"

        response = self.client.get(url=url, params=user_search_request.dict())

        user_search = UserSearchResponse(**response.json())

        return user_search

    def search_users(self, keyword: str, count: int) -> List[UserSearchResult]:
        "Searches for users with paginate."

        results: List[UserSearchResult] = []

        logger.info(f'Search {count} users with keyword: "{keyword}"')

        with tqdm(total=count, desc="Searching users") as pbar:
            for cursor in itertools.count(start=0, step=10):
                user_search_request = UserSearchRequest(keyword=keyword, cursor=cursor)
                response = self._search_users(user_search_request=user_search_request)

                results += response.user_list
                pbar.update(len(response.user_list))

                if not response.has_more or len(results) >= count:
                    results = results[:count]
                    logger.info(f"Found {len(results)} results")
                    break

        return results

    def _search_posts_by_hashtag(
        self, search_request: ListPostsInHashtagRequest
    ) -> ListPostsInHashtagResponse:
        "Search posts by hashtag id."

        url = "aweme/v1/challenge/aweme/"

        response = self.client.get(url=url, params=search_request.dict())

        search = ListPostsInHashtagResponse(**response.json())

        return search

    def search_posts_by_hashtag(self, hashtag: ChallengeInfo, count: int) -> List[Post]:
        "Search posts by hashtag with paginate."

        results: List[Post] = []

        logger.info(f'Search {count} posts with hashtag: "{hashtag.cha_name}"')

        with tqdm(total=count, desc="Searching posts") as pbar:
            for cursor in itertools.count(start=0, step=10):
                search_request = ListPostsInHashtagRequest(ch_id=hashtag.cid, cursor=cursor)
                response = self._search_posts_by_hashtag(search_request)

                results += response.aweme_list
                pbar.update(len(response.aweme_list))

                if not response.has_more or len(results) >= count:
                    results = results[:count]
                    logger.info(f"Found {len(results)} results")
                    break

        return results

    def _search_hashtags(self, hashtag_search_request: SearchRequest) -> HashtagSearchResponse:
        "Searches for hashtags."

        url = "aweme/v1/challenge/search/"

        response = self.client.get(url=url, params=hashtag_search_request.dict())

        hashtag_search = HashtagSearchResponse(**response.json())

        return hashtag_search

    def _follow(self, request: FollowRequest) -> FollowResponse:
        "Send follow request."

        url = "aweme/v1/commit/follow/user/"

        response = self.client.get(url=url, params=request.dict())

        follow = FollowResponse(**response.json())

        return follow

    def search_hashtags(self, keyword: str, count: int) -> List[HashtagSearchResult]:
        "Searches for hashtags with paginate."

        results: List[HashtagSearchResult] = []

        logger.info(f'Search {count} hashtags with keyword: "{keyword}"')

        with tqdm(total=count, desc="Searching hashtags") as pbar:
            for cursor in itertools.count(start=0, step=10):
                search_request = SearchRequest(keyword=keyword, cursor=cursor)
                response = self._search_hashtags(search_request)

                results += response.challenge_list
                pbar.update(len(response.challenge_list))

                if not response.has_more or len(results) >= count:
                    results = results[:count]
                    logger.info(f"Found {len(results)} results")
                    break

        return results


================================================
FILE: tiktok_bot/api/config.py
================================================
# ToDo: Make it configurable
DEFAULT_PARAMS = {
    "os_api": "23",
    "device_type": "Pixel",
    "ssmix": "a",
    "manifest_version_code": "2018111632",
    "dpi": 420,
    "app_name": "musical_ly",
    "version_name": "9.1.0",
    "is_my_cn": 0,
    "ac": "wifi",
    "update_version_code": "2018111632",
    "channel": "googleplay",
    "device_platform": "android",
    "build_number": "9.9.0",
    "version_code": 910,
    "timezone_name": "America/New_York",
    "timezone_offset": 36000,
    "resolution": "1080*1920",
    "os_version": "7.1.2",
    "device_brand": "Google",
    "mcc_mnc": "23001",
    "is_my_cn": 0,
    "fp": "",
    "app_language": "en",
    "language": "en",
    "region": "US",
    "sys_region": "US",
    "account_region": "US",
    "carrier_region": "US",
    "carrier_region_v2": "505",
    "aid": "1233",
    "pass-region": 1,
    "pass-route": 1,
    "app_type": "normal",
    # "iid": "6742828344465966597",
    # "device_id": "6746627788566021893",
    # ToDo: Make it dynamic
    "iid": "6749111388298184454",
    "device_id": "6662384847253865990",
}

DEFAULT_HEADERS = {
    "Host": "api2.musical.ly",
    "X-SS-TC": "0",
    "User-Agent": f"com.zhiliaoapp.musically/{DEFAULT_PARAMS['manifest_version_code']}"
    + f" (Linux; U; Android {DEFAULT_PARAMS['os_version']};"
    + f" {DEFAULT_PARAMS['language']}_{DEFAULT_PARAMS['region']};"
    + f" {DEFAULT_PARAMS['device_type']};"
    + " Build/NHG47Q; Cronet/58.0.2991.0)",
    "Accept-Encoding": "gzip",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "X-Tt-Token": "",
    "sdk-version": "1",
    "Cookie": "null = 1;",
}


================================================
FILE: tiktok_bot/bot/__init__.py
================================================
from .bot import TikTokBot

__all__ = ["TikTokBot"]


================================================
FILE: tiktok_bot/bot/bot.py
================================================
import sys
from typing import List

from loguru import logger
from typing_extensions import Literal

from tiktok_bot.api import TikTokAPI
from tiktok_bot.models.category import Category, ListCategoriesRequest
from tiktok_bot.models.feed import ListFeedRequest
from tiktok_bot.models.feed_enums import FeedType, PullType
from tiktok_bot.models.post import Post
from tiktok_bot.models.search import ChallengeInfo
from tiktok_bot.models.user import CommonUserDetails, UserProfile


class TikTokBot:
    def __init__(self, log_level: Literal["INFO", "DEBUG"] = "INFO"):
        self.api = TikTokAPI()

        logger.remove()
        logger.add(sys.stderr, level=log_level)

    def list_categories(self, count: int = 10, cursor: int = 0) -> List[Category]:
        request = ListCategoriesRequest(count=count, cursor=cursor)
        categories = self.api.list_categories(request)

        return categories.category_list

    def get_user_by_id(self, user_id: str) -> UserProfile:
        user_response = self.api.get_user(user_id=user_id)

        return user_response.user

    def search_users(self, keyword: str, count: int = 6) -> List[CommonUserDetails]:
        users_search = self.api.search_users(keyword=keyword, count=count)

        users = [user.user_info for user in users_search]

        return users

    def search_hashtags(self, keyword: str, count: int = 6) -> List[ChallengeInfo]:
        hashtags_search = self.api.search_hashtags(keyword=keyword, count=count)

        hashtags = [tag.challenge_info for tag in hashtags_search]

        return hashtags

    def search_posts_by_hashtag(self, hashtag_name: str, count: int = 6) -> List[Post]:
        tags = self.search_hashtags(keyword=hashtag_name, count=1)

        if not tags:
            logger.info(f'Tag "{hashtag_name}" not found')
            return []

        posts = self.api.search_posts_by_hashtag(hashtag=tags[0], count=count)

        return posts

    def list_for_you_feed(self, count: int = 6) -> List[Post]:
        feed = self.api.list_for_you_feed(count=count)

        return feed

    def list_following_feed(self, count: int = 6, cursor: int = 0) -> List[Post]:
        """
        Lists posts in the Following feed.

        * Login required
        """
        request = ListFeedRequest(
            count=count,
            max_cursor=cursor,
            pull_type=PullType.LoadMore,
            type=FeedType.Following,
            is_cold_start=1,
        )
        feed = self.api.list_following_feed(request)

        return feed.aweme_list


================================================
FILE: tiktok_bot/client/__init__.py
================================================
from .client import HTTPClient

__all__ = ["HTTPClient"]


================================================
FILE: tiktok_bot/client/client.py
================================================
from collections import deque
from time import time
from typing import Deque, Optional
from uuid import uuid4

from httpx import Client, Response
from loguru import logger

from .utils import generate_as, generate_cp, generate_mas


class HTTPClient:
    def __init__(
        self,
        base_url: str,
        default_headers: Optional[dict] = None,
        default_params: Optional[dict] = None,
        history_len: int = 30,
    ):
        self.base_url = base_url
        self.default_headers = default_headers or {}
        self.default_params = default_params or {}

        self.history: Deque[Response] = deque(maxlen=history_len)

        self.http_client = Client(
            base_url=self.base_url, headers=default_headers, params=self.default_params
        )

    def get(self, url: str, params: dict, headers: Optional[dict] = None):
        custom_headers = headers or {}
        all_params = {**self._generate_params(), **params}

        logger.debug(f"Sending request to {url}", params=all_params, custom_headers=custom_headers)
        response = self.http_client.get(url=url, params=all_params, headers=custom_headers)

        self.history.append(response)

        body = response.text or "is empty!"

        logger.debug(f"Response return status_code: {response.status_code}, body: {body}")

        for cookie_name, cookie_data in response.cookies.items():
            self.http_client.cookies.set(cookie_name, cookie_data)
            logger.debug(f"New cookies: {dict(response.cookies)}")

        return response

    def post(
        self, url: str, data: dict, headers: Optional[dict] = None, params: Optional[dict] = None
    ):
        custom_headers = headers or {}
        custom_params = params or {}
        # merge parameters
        all_params = {**self._generate_params(), **custom_params}

        logger.debug(
            f"Sending request to {url}", params=all_params, custom_headers=custom_headers, data=data
        )

        response = self.http_client.post(
            url=url, params=all_params, data=data, headers=custom_headers,
        )

        self.history.append(response)

        body = response.text or "is empty!"
        logger.debug(f"Response return status_code: {response.status_code}, body: {body}")

        for cookie_name, cookie_data in response.cookies.items():
            self.http_client.cookies.set(cookie_name, cookie_data)
            logger.debug(f"New cookies: {dict(response.cookies)}")

        return response

    def _generate_params(self):
        now = str(int(round(time() * 1000)))

        params = {
            "_rticket": now,
            "ts": now,
            "mas": generate_mas(now),
            "as": generate_as(now),
            "cp": generate_cp(now),
            "idfa": str(uuid4()).upper(),
        }

        return params


================================================
FILE: tiktok_bot/client/utils.py
================================================
from hashlib import md5, sha1
from time import time


def generate_as(now: str) -> str:
    as_md5 = md5(now.encode()).hexdigest()

    return as_md5


def generate_cp(now: str) -> str:
    now += str(time())

    cp_md5 = md5(now.encode()).hexdigest()

    return cp_md5


def generate_mas(now: str) -> str:
    mas_sha = sha1(now.encode()).hexdigest()
    mas_md5 = md5(mas_sha.encode()).hexdigest()

    return mas_md5


================================================
FILE: tiktok_bot/models/__init__.py
================================================


================================================
FILE: tiktok_bot/models/category.py
================================================
from typing import List, Union

from pydantic import BaseModel, Schema

from .post import Post
from .request import CountOffsetParams, ListRequestParams, ListResponseData
from .user import CommonUserDetails


class ChallengeInfo(BaseModel):
    # The user who created the challenge, or an empty object
    author: Union[CommonUserDetails, BaseModel]

    # The name of the challenge
    cha_name: str

    # The ID of the challenge
    cid: str

    # A description of the challenge
    desc: str

    # ???
    is_pgcshow: bool

    # An in-app link to the challenge
    schema_: str = Schema(default=..., alias="schema")

    # The type of challenge - 0 for hashtag?
    type: int

    # The number of users who have uploaded a video for the challenge
    user_count: int

    class Config:
        fields = {"schema_": "schema"}


class Category(BaseModel):
    # A list of posts in the category
    aweme_list: List[Post]

    # The type of category - 0 for hashtag?
    category_type: int

    # Information about the category
    challenge_info: ChallengeInfo

    # A description of the category type, e.g. "Trending Hashtag"
    desc: str


class ListCategoriesRequest(ListRequestParams, CountOffsetParams):
    pass


class ListCategoriesResponse(ListResponseData, CountOffsetParams):
    # A list of categories
    category_list: List[Category]


================================================
FILE: tiktok_bot/models/comment.py
================================================
from typing import List, Optional

from pydantic import BaseModel
from typing_extensions import Literal

from .request import BaseResponseData, CountOffsetParams, ListRequestParams, ListResponseData
from .tag import Tag
from .user import CommonUserDetails


class Comment(BaseModel):
    # The ID of the post
    aweme_id: str

    # The ID of the comment
    cid: str

    # The timestamp in seconds when the comment was posted
    create_time: int

    # The number of times the comment has been liked
    digg_count: int

    # If this comment is replying to a comment, this array contains the original comment
    reply_comment: Optional[List["Comment"]] = None

    # If this comment is replying to a comment, the ID of that comment - "0" if not a reply
    reply_id: str

    # The status of the comment - 1 = published, 4 = published by you?
    status: int

    # The comment text
    text: str

    # Details about any tags in the comment
    text_extra: List[Tag]

    # Details about the author
    user: CommonUserDetails

    # 1 if the user likes the comment
    user_digged: Literal[0, 1]


class ListCommentsRequest(ListRequestParams, CountOffsetParams):
    # The ID of the post to list comments for
    aweme_id: str

    # ??? - default is 2
    comment_style: Optional[int] = None

    # ???
    digged_cid = None

    # ???
    insert_cids = None


class ListCommentsResponse(ListResponseData, CountOffsetParams):
    comments: List[Comment]


class PostCommentRequest(BaseModel):
    # The ID of the post to comment on
    aweme_id: str

    # The comment text
    text: str

    # The ID of the comment that is being replied to
    reply_id: Optional[str] = None

    # Details about any tags in the comment
    text_extra: List[Tag]

    # ???
    is_self_see: Literal[0, 1]


class PostCommentResponse(BaseResponseData):
    # The comment that was posted
    comment: Comment


================================================
FILE: tiktok_bot/models/feed.py
================================================
from typing import List, Optional

from .feed_enums import FeedType, PullType
from .post import Post
from .request import (
    CursorOffsetRequestParams,
    CursorOffsetResponseParams,
    ListRequestParams,
    ListResponseData,
)


class ListFeedRequest(ListRequestParams, CursorOffsetRequestParams):
    # The type of feed to load
    type: FeedType

    # Your device's current volume level on a scale of 0 to 1, e.g. 0.5
    volume: float = 0.5

    # How the feed was requested
    pull_type: PullType

    # ??? - empty
    req_from: Optional[str] = None

    # ??? - 0
    is_cold_start: Optional[int] = None

    # ???
    gaid: Optional[str] = None

    # A user agent for your device
    ad_user_agent: Optional[str] = None

    class Config:
        use_enum_values = True


class ListFeedResponse(ListResponseData, CursorOffsetResponseParams):
    # A list of posts in the feed
    aweme_list: List[Post]


class ListForYouFeedResponse(ListFeedResponse):
    # ??? - 1
    home_model: int

    # ??? - 1
    refresh_clear: int


================================================
FILE: tiktok_bot/models/feed_enums.py
================================================
from enum import IntEnum


class FeedType(IntEnum):
    ForYou = 0
    Following = 1


class PullType(IntEnum):
    # The feed was loaded by default, e.g. by clicking the tab or loading the app
    Default = 0

    # The feed was explicitly refreshed by the user, e.g. by swiping down
    Refresh = 1

    # More posts were requested by the user, e.g. by swiping up
    LoadMore = 2


================================================
FILE: tiktok_bot/models/follow.py
================================================
from typing import List

from pydantic import BaseModel
from typing_extensions import Literal

from .request import (
    BaseResponseData,
    ListRequestParams,
    ListResponseData,
    TimeOffsetRequestParams,
    TimeOffsetResponseParams,
)
from .user import CommonUserDetails


class FollowRequest(BaseModel):
    # The id of the user to follow
    user_id: str

    # 0 to unfollow, 1 to follow
    type: Literal[0, 1]


class FollowResponse(BaseResponseData):
    # 0 if not following, 1 if following
    follow_status: Literal[0, 1]

    # 0 if not watching, 1 if watching
    watch_status: Literal[0, 1]


class ListReceivedFollowRequestsRequest(ListRequestParams, TimeOffsetRequestParams):
    pass


class ListReceivedFollowRequestsResponse(ListResponseData, TimeOffsetResponseParams):
    # A list of users who have requested to follow you
    request_users: List[CommonUserDetails]


class ApproveFollowRequest(BaseModel):
    # The id of the user to approve
    from_user_id: str


class ApproveFollowResponse(BaseResponseData):
    # 0 if the user was successfully approved
    approve_status: int


class RejectFollowRequest(BaseModel):
    # The id of the user to reject
    from_user_id: str


class RejectFollowResponse(BaseResponseData):
    # 0 if the user was successfully rejected
    reject_status: int


================================================
FILE: tiktok_bot/models/follower.py
================================================
from typing import List

from .request import (
    ListRequestParams,
    ListResponseData,
    TimeOffsetRequestParams,
    TimeOffsetResponseParams,
)
from .user import CommonUserDetails


class ListFollowersRequest(ListRequestParams, TimeOffsetRequestParams):
    # The id of the user whose followers to retrieve
    user_id: str


class ListFollowersResponse(ListResponseData, TimeOffsetResponseParams):
    # A list of the user's followers
    followers: List[CommonUserDetails]


class ListFollowingRequest(ListRequestParams, TimeOffsetRequestParams):
    # The id of the user whose followers to retrieve
    user_id: str


class ListFollowingResponse(ListResponseData, TimeOffsetResponseParams):
    # A list of users the user is following
    followings: List[CommonUserDetails]


================================================
FILE: tiktok_bot/models/hashtag.py
================================================
from typing import List

from .post import Post
from .request import CountOffsetParams, ListRequestParams, ListResponseData


class ListPostsInHashtagRequest(ListRequestParams, CountOffsetParams):
    # The ID of the hashtag
    ch_id: str

    # ??? - set to 0
    query_type: int = 0

    # ??? - set to 5
    type: int = 5


class ListPostsInHashtagResponse(ListResponseData, CountOffsetParams):
    # A list of posts containing the hashtag
    aweme_list: List[Post]


================================================
FILE: tiktok_bot/models/like.py
================================================
from typing_extensions import Literal

from .request import BaseResponseData


class LikePostRequest(BaseResponseData):
    # The id of the post to like
    aweme_id: str

    # 0 to unlike, 1 to like
    type: Literal[0, 1]


class LikePostResponse(BaseResponseData):
    #
    # 0 if liked, 1 if not liked
    #
    # Note: for some reason, this value is the opposite of what you would expect
    is_digg: Literal[0, 1]


================================================
FILE: tiktok_bot/models/login.py
================================================
from typing import List, Union

from pydantic import BaseModel


class LoginRequest(BaseModel):
    # Unsure, but looks to be hard-coded to 1
    mix_mode: int = 1

    # The unique username ("username") of the user
    username: str = ""

    # The email address associated with the user account
    email: str = ""

    # The mobile number associated with the user account
    mobile: str = ""

    # ???
    account: str = ""

    # The password to the user account
    password: str = ""

    # The captcha answer - only required if a captcha was shown
    captcha: str = ""


class LoginSuccessData(BaseModel):
    # ???
    area: str

    # The URL of the user's avatar
    avatar_url: str

    # ???
    bg_img_url: str

    # The user's birthday
    birthday: str

    # If the user allows people to find them by their phone number
    can_be_found_by_phone: int

    # ???
    connects: List[BaseModel]

    # ???
    description: str

    # The email address associated with the account
    email: str

    # The number of users that follow the user
    followers_count: int

    # The number of users the user is following
    followings_count: int

    # An integer representing the gender of the user
    gender: int

    # ???
    industry: str

    # Indicates if the user account is blocked
    is_blocked: int

    # ???
    is_blocking: int

    # ???
    is_recommend_allowed: int

    # ???
    media_id: int

    # The mobile number of the user
    mobile: str

    # The name of the user - does not appear to be used
    name: str

    # Indicates if the user is new or not
    new_user: int

    # A Chinese character hint
    recommend_hint_message: str

    # The screen name of the user - does not appear to be used
    screen_name: str

    # The session ID used to authenticate subsequent requests in the sessionid cookie
    session_key: str

    # ???
    skip_edit_profile: int

    # ???
    user_auth_info: str

    # The ID of the user
    user_id: str

    # If the user is verified or not
    user_verified: bool

    # ???
    verified_agency: str

    # ???
    verified_content: str

    # The number of users that have visited the user's profile recently
    visit_count_recent: int


class LoginErrorData(BaseModel):
    # If required, the captcha that must solved
    captcha: str

    # A message explaining why the request failed
    description: str

    # An error code
    error_code: int


class LoginResponse(BaseModel):
    data: Union[LoginSuccessData, LoginErrorData]

    # A message indicating whether the request was successful or not
    message: str


================================================
FILE: tiktok_bot/models/music.py
================================================
from typing import Optional

from pydantic import BaseModel

from .request import Media


class MusicTrack(BaseModel):
    # The name of the musician
    author: str

    # A HD version of the music's cover art
    cover_hd: Optional[Media]

    # A large version of the music's cover art
    cover_large: Optional[Media]

    # A medium version of the music's cover art
    cover_medium: Optional[Media]

    # A thumbnail version of the music's cover art
    cover_thumb: Media

    # The duration of the track
    duration: int

    # The ID of the track
    id: str

    # The handle of the owner of the track
    owner_handle: Optional[str]

    # The ID of the owner of the track
    owner_id: Optional[str]

    # The nickname of the owner of the track
    owner_nickname: Optional[str]

    # The link to play this track
    play_url: Media

    # The title of this track
    title: str

    # The number of posts that use this track
    user_count: int


================================================
FILE: tiktok_bot/models/post.py
================================================
from typing import List, Optional

from pydantic import BaseModel

from .music import MusicTrack
from .request import (
    BaseResponseData,
    CursorOffsetRequestParams,
    CursorOffsetResponseParams,
    ListRequestParams,
    ListResponseData,
)
from .user import CommonUserDetails
from .video import Video


class PostStatistics(BaseModel):
    # The ID of the post
    aweme_id: str

    # The number of comments on the post
    comment_count: int

    # The number of times the post has been liked
    digg_count: int

    # The number of times the post has been forwarded (looks unused?)
    forward_count: Optional[int]

    # The number of times the post has been viewed - doesn't appear to be public, so always 0
    play_count: int

    # The number of times the post has been shared
    share_count: int


class PostStatus(BaseModel):
    # True if the post allows comments
    allow_comment: bool

    # True if the post allows sharing
    allow_share: bool

    # 0 if the post can be downloaded
    download_status: int

    # True if the post is currently being reviewed
    in_reviewing: Optional[bool]

    # True if the post has been deleted
    is_delete: bool

    # True if the post is private
    is_private: bool

    # True if the post contains content that is not allowed on the platform
    is_prohibited: Optional[bool]

    # 0 if the post is public
    private_status: Optional[int]

    # 1 if the post has been reviewed
    reviewed: Optional[int]


class PostTags(BaseModel):
    # 0 if the tag is for a user; 1 if the tag is for a hashtag
    type: int

    # The name of the hashtag
    hashtag_name: Optional[str]

    # The ID of the tagged user
    user_id: Optional[str]


class RiskInfo(BaseModel):
    # The text shown if the post has been flagged
    content: str

    # ???
    risk_sink: bool = False

    # The risk type associated with the post - 0 if no risk; 1 if low; 2 if high
    type: int

    # ??? - only present if the post has been flagged
    vote: Optional[bool]

    # True if a warning should be shown to the user
    warn: bool


class ShareInfo(BaseModel):
    # ???
    bool_persist: Optional[int]

    # The description used when sharing (if set)
    share_desc: str

    # The description used when sharing a link only (if set)
    share_link_desc: Optional[str]

    # The quote used when sharing (if set)
    share_quote: Optional[str]

    # The signature used when sharing (if set)
    share_signature_desc: Optional[str]

    # The signature URL used when sharing (if set)
    share_signature_url: Optional[str]

    # The title used when sharing
    share_title: str

    # The link to share
    share_url: str

    # The description used when sharing on Weibo
    share_weibo_desc: str


class StickerInfo(BaseModel):
    # The ID of the sticker, e.g. 22094
    id: str

    # The display name of the sticker, e.g. Long Face
    name: str


class Post(BaseModel):
    # Details about the author
    author: Optional[CommonUserDetails]

    # The ID of the author
    author_user_id: str

    # The ID of the post
    aweme_id: str

    # The type of post - 0 for a musical.ly
    aweme_type: int

    # The timestamp in seconds when the post was created
    create_time: int

    # A description of the post
    desc: str

    # Details about the music used in the post
    music: Optional[MusicTrack]

    # True if the end user should not be provided the option to download the video
    prevent_download: Optional[bool]

    # An age rating for the post, e.g. 12
    rate: int

    # The 2-letter region the post was created in, e.g. US
    region: str

    # Risk information about the post
    risk_infos: Optional[RiskInfo]

    # Information used when sharing the post
    share_info: Optional[ShareInfo]

    # A link to the video on the musical.ly website that is used when sharing
    share_url: str

    # Statistics about the post
    statistics: PostStatistics

    # Status information about the post
    status: PostStatus

    # Information about the sticker used in the post
    sticker_detail: Optional[StickerInfo]

    # The ID of the sticker used in the post (looks to be deprecated by sticker_detail)
    stickers: Optional[str]

    # Tagged users and hashtags used in the description
    text_extra: List[PostTags]

    # 1 if the logged in user has liked this post
    user_digged: int

    # Details about the video in the post
    video: Video

    @property
    def video_url(self):
        url = filter(lambda url: "watermark" in url, self.video.download_addr.url_list)

        return next(url)

    @property
    def video_url_without_watermark(self):
        return self.video_url.replace("watermark=1", "watermark=0")


class GetPostResponse(BaseResponseData):
    aweme_detail: Post


class ListPostsRequest(ListRequestParams, CursorOffsetRequestParams):
    # The id of the user whose posts to retrieve
    user_id: str


class ListPostsResponse(ListResponseData, CursorOffsetResponseParams):
    aweme_list: List[Post]


================================================
FILE: tiktok_bot/models/qr-code.py
================================================
from typing import List

from pydantic import BaseModel

from .request import BaseResponseData


class QRCodeRequest(BaseModel):
    # The internal version to use; currently 4
    schema_type: int

    # The ID of the user to get a QR code for
    object_id: str


class QRCodeUrl(BaseModel):
    # An in-app link to the QR code
    uri: str

    # Contains a public link to the QR code image (first element in array)
    url_list: List[str]


class QRCodeResponse(BaseResponseData):
    # Contains a link to the QR code
    qrcode_url: QRCodeUrl


================================================
FILE: tiktok_bot/models/request.py
================================================
import abc
from typing import List, Optional, Union

from pydantic import BaseModel


class RequiredUserDefinedRequestParams(BaseModel, abc.ABC):
    # The 16-character ID of your installation, e.g. 4549764744226841084
    iid: str

    # A 16-character hexadecimal identifier associated with your device, e.g. 4b903fbb9d457937
    openudid: str

    # The ID of your device that has already been registered with musical.ly
    device_id: str

    # An anti-fraud fingerprint of your device requested from a different API
    fp: str


class StaticRequestParams(RequiredUserDefinedRequestParams):
    # Your Android version, e.g. 23
    os_api: str

    # Your device model, e.g. Pixel 2
    device_type: str

    # ??? - set to "a"
    ssmix: str

    # The SS_VERSION_CODE metadata value from the AndroidManifest.xml file, e.g. 2018060103
    manifest_version_code: str

    # Your device's pixel density, e.g. 480
    dpi: int

    # The application name - hard-coded to "musical_ly"
    app_name: str

    # The SS_VERSION_NAME metadata value from the AndroidManifest.xml file, e.g. 7.2.0
    version_name: str

    # The UTC offset in seconds of your timezone, e.g. 37800 for Australia/Lord_Howe
    timezone_offset: int

    # ??? - are we in China / using the Chinese version? Set to 0
    is_my_cn: int

    # Network connection type, e.g. "wifi"
    ac: str

    # The UPDATE_VERSION_CODE metadata value from the AndroidManifest.xml file, e.g. 2018060103
    update_version_code: str

    # The channel you downloaded the app through, e.g. googleplay
    channel: str

    # Your device's platform, e.g. android
    device_platform: str

    # The build int of the application, e.g. 7.2.0
    build_number: str

    # A numeric version of the version_name metadata value, e.g. 720
    version_code: int

    # The name of your timezone as per the tz database, e.g. Australia/Sydney
    timezone_name: str

    # The region of the account you are logging into, e.g. AU.
    # This field is only present if you are logging in from a device that hasn't had a
    # user logged in before.
    account_region: Optional[str] = None

    # Your Optional[str]ice's resolution, e.g. 1080*1920
    resolution: str

    # Your device's operating system version, e.g. 8.0.0
    os_version: str

    # Your device's brand, e.g. Google
    device_brand: str

    # ??? - empty
    mcc_mnc: str

    # The application's two-letter language code, e.g. en
    app_language: str

    # Your i18n language, e.g. en
    language: str

    # Your region's i18n locale, e.g. US
    region: str

    # Your device's i18n locale, e.g. US
    sys_region: str

    # Your carrier's region (a two-letter country code), e.g. AU
    carrier_region: str

    # You carrer's mobile country code (MCC), e.g. 505
    carrier_region_v2: str

    # A hard-coded i18n constant set to "1233"
    aid: str

    # ??? - set to 1
    pass_region: int

    # ??? - set to 1
    pass_route: int

    class Config:
        fields = {"pass_region": "pass-region", "pass_route": "pass-route"}


class AntiSpamParams(BaseModel):
    # A 20-character anti-spam parameter
    as_: str

    # A 20-character anti-spam parameter
    cp: str

    # An encoded version of the 'as' anti-spam parameter
    mas: str

    class Config:
        fields = {"as_": "as"}


class BaseRequestParams(StaticRequestParams, AntiSpamParams):
    # The current timestamp in seconds since UNIX epoch
    ts: int

    # The current timestamp in milliseconds since UNIX epoch
    _rticket: str


class ListRequestParams(BaseModel):
    # The number of results to return
    count: int = 10

    # How the request will be retried on failure - defaults to "no_retry"
    retry_type: Optional[str] = None


class TimeOffsetRequestParams(BaseModel):
    """
    A timestamp in seconds - the most recent results before this time will be listed.
    Use min_time from the response data here for pagination.
    """

    max_time: int


class TimeOffsetResponseParams(BaseModel):
    # The timestamp in seconds associated with the first result
    max_time: int

    # The timestamp in seconds associated with the last result - use as max_time for pagination
    min_time: int


class CursorOffsetRequestParams(BaseModel):
    """
    A timestamp in milliseconds - the most recent results before this time will be listed.
    Use max_cursor from the response data here for pagination. Use 0 for the most recent.
    """

    max_cursor: int


class CursorOffsetResponseParams(BaseModel):
    # The timestamp in milliseconds associated with the first result
    min_cursor: int

    # The timestamp in milliseconds associated with the last result - use for pagination
    max_cursor: int


class CountOffsetParams(BaseModel):
    # The number of results to skip
    cursor: int = 0


class ExtraResponseData(BaseModel):
    # ???
    fatal_item_ids: Optional[List[int]] = None

    # A log ID for this request
    logid: Optional[str] = None

    # The current timestamp in milliseconds
    now: int


class BaseResponseData(BaseModel, abc.ABC):
    # 0 if the request was successful
    status_code: int

    extra: ExtraResponseData


class ListResponseData(BaseResponseData):
    # Whether there are more results that can be requested
    has_more: Union[bool, int]

    # The total number of results returned - not present in all list requests
    total: Optional[int] = None


class Media(BaseModel):
    # A list of HTTP URLs to this media
    url_list: List[str]


================================================
FILE: tiktok_bot/models/search.py
================================================
from typing import List, Optional

from pydantic import BaseModel
from typing_extensions import Literal

from .category import ChallengeInfo
from .request import CountOffsetParams, ListRequestParams, ListResponseData
from .user import CommonUserDetails


class SearchRequest(ListRequestParams, CountOffsetParams):
    # The term to search for
    keyword: str


class UserSearchRequest(SearchRequest):
    # Required - the scope of the search - users = 1.
    type: int = 1


class SubstringPosition(BaseModel):
    """
    Represents the location of a substring in a string.
    e.g. For the string "The quick brown fox", the substring "quick" would be:
    {
        begin: 4,
        end: 8
    }
    """

    # The start index of the substring
    begin: int

    # The end index of the substring
    end: int


class UserSearchResult(BaseModel):
    # If the user's nickname contains the search term, this array contains the location of the term
    position: Optional[List[SubstringPosition]] = None

    # If the user's username (unique_id) contains the search term,
    # this array contains the location of the term
    uniqid_position: Optional[List[SubstringPosition]] = None

    # Information about the user
    user_info: CommonUserDetails


class UserSearchResponse(ListResponseData, CountOffsetParams):
    # A list of users that match the search term
    user_list: List[UserSearchResult]

    # The scope of the search - users = 1
    type: int


class HashtagSearchResult(BaseModel):
    # Information about the hashtag
    challenge_info: ChallengeInfo

    # If the hashtag contains the search term, this array contains the location of the term
    position: Optional[List[SubstringPosition]] = None


class HashtagSearchResponse(ListResponseData, CountOffsetParams):
    # A list of hashtags that match the search term
    challenge_list: List[HashtagSearchResult]

    # True if a challenge matches the search term
    is_match: bool

    # 1 if the search term is disabled
    keyword_disabled: Literal[0, 1]


================================================
FILE: tiktok_bot/models/sticker.py
================================================
from typing import Any, List

from pydantic import BaseModel

from .post import Post
from .request import BaseResponseData, CountOffsetParams, ListRequestParams, ListResponseData, Media


class Sticker(BaseModel):
    # ???
    children: Any

    # A description of the sticker
    desc: str

    # The ID of the sticker
    effect_id: str

    # The icon associated with the sticker
    icon_url: Media

    # The ID of the sticker
    id: str

    # True if the current user has favorited the sticker
    is_favorite: bool

    # The name of the sticker
    name: str

    # The ID the user that owns the sticker (empty if owned by the Effect Assistant)
    owner_id: str

    # The nickname of the owner, e.g. "Effect Assistant"
    owner_nickname: str

    # ???
    tags: List[Any]

    # The total number of posts using this sticker
    user_count: int


class ListPostsByStickerRequest(ListRequestParams, CountOffsetParams):
    # The ID of the sticker
    sticker_id: str


class ListPostsByStickerResponse(ListResponseData, CountOffsetParams):
    # A list of posts using the sticker
    aweme_list: List[Post]

    # Currently empty
    stickers: List[Any]


class GetStickersRequest(BaseModel):
    # A list of sticker ids to get information about
    sticker_ids: str


class GetStickersResponse(BaseResponseData):
    sticker_infos: List[Sticker]


================================================
FILE: tiktok_bot/models/tag.py
================================================
"""
  Represents a text link to a user, e.g. "@username" in a comment
"""
from pydantic import BaseModel


class Tag(BaseModel):
    # The type of user being tagged?
    at_user_type: str

    # The zero-based index in the text where the tag starts
    end: int

    # The zero-based index in the text where the tag ends
    start: int

    # The type of tag?
    type: int

    # The ID of the user being tagged
    user_id: str


================================================
FILE: tiktok_bot/models/user.py
================================================
from typing import Optional, Union

from pydantic import BaseModel

from .request import BaseResponseData, Media


class CommonUserDetails(BaseModel):
    # A large version of the user's avatar
    avatar_larger: Media

    # A medium version of the user's avatar
    avatar_medium: Media

    # A thumbnail version of the user's avatar
    avatar_thumb: Media

    # The timestamp in seconds when the user's account was created
    create_time: Optional[int] = None

    # The badge name with a verified user (e.g. comedian, style guru)
    custom_verify: str

    # 1 if you follow this user
    follow_status: int

    # 1 if this user follows you
    follower_status: int

    # The user's Instagram handle
    ins_id: str

    # Indicates if the user has been crowned
    is_verified: bool

    # The user's profile name
    nickname: str

    # A 2-letter country code representing the user's region, e.g. US
    region: str

    # If the user is live, a str ID used to join their stream, else 0
    room_id: Optional[Union[str, int]] = None

    # 1 if the user's profile is set to private
    secret: int

    # The user's profile signature
    signature: str

    # The user's Twitter handle
    twitter_id: str

    # The user's ID
    uid: str

    # The user's musername
    unique_id: str

    # 1 if the user has been crowned
    verification_type: int

    # The user's YouTube channel ID
    youtube_channel_id: str


class UserProfile(CommonUserDetails):
    # The number of videos the user has uploaded
    aweme_count: int

    # The number of videos the user has liked
    favoriting_count: int

    # The number of users who follow this user
    follower_count: int

    # The number of users this user follows
    following_count: int

    # The total number of likes the user has received
    total_favorited: int


class UserProfileResponse(BaseResponseData):
    user: UserProfile


================================================
FILE: tiktok_bot/models/video.py
================================================
from pydantic import BaseModel

from .request import Media


class Video(BaseModel):
    # A medium version of the video's cover image
    cover: Media

    # A high-quality link to download the video
    download_addr: Media

    # The video's duration in milliseconds
    duration: int

    # Whether the download link has a watermark
    has_watermark: bool

    # The video's height, e.g. 960
    height: int

    # A high-quality version of the video's cover image
    origin_cover: Media

    # The quality of the video, e.g. 720p
    ratio: str

    # The video's width, e.g. 540
    width: int
Download .txt
gitextract_wm8u40tj/

├── .bumpversion.cfg
├── .flake8
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── pythonpackage.yml
├── .gitignore
├── .isort.cfg
├── .pre-commit-config.yaml
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── docs/
│   ├── css/
│   │   └── custom.css
│   └── index.md
├── mkdocs.yml
├── pyproject.toml
├── tests/
│   ├── api/
│   │   └── test_api.py
│   ├── conftest.py
│   └── test_tiktok-bot.py
└── tiktok_bot/
    ├── __init__.py
    ├── api/
    │   ├── __init__.py
    │   ├── api.py
    │   └── config.py
    ├── bot/
    │   ├── __init__.py
    │   └── bot.py
    ├── client/
    │   ├── __init__.py
    │   ├── client.py
    │   └── utils.py
    └── models/
        ├── __init__.py
        ├── category.py
        ├── comment.py
        ├── feed.py
        ├── feed_enums.py
        ├── follow.py
        ├── follower.py
        ├── hashtag.py
        ├── like.py
        ├── login.py
        ├── music.py
        ├── post.py
        ├── qr-code.py
        ├── request.py
        ├── search.py
        ├── sticker.py
        ├── tag.py
        ├── user.py
        └── video.py
Download .txt
SYMBOL INDEX (122 symbols across 25 files)

FILE: tests/api/test_api.py
  function test_encrypt_with_xor (line 4) | def test_encrypt_with_xor(api: TikTokAPI):

FILE: tests/conftest.py
  function api (line 7) | def api():

FILE: tests/test_tiktok-bot.py
  function test_version (line 4) | def test_version():

FILE: tiktok_bot/api/api.py
  class TikTokAPI (line 29) | class TikTokAPI:
    method __init__ (line 30) | def __init__(self):
    method encrypt_with_XOR (line 38) | def encrypt_with_XOR(value: str, key=5) -> str:
    method login (line 41) | def login(self, login_request: LoginRequest) -> LoginResponse:
    method login_with_email (line 52) | def login_with_email(self, email: str, password: str, captcha: str = ""):
    method _list_for_you_feed (line 62) | def _list_for_you_feed(self, list_feed_request: ListFeedRequest) -> Li...
    method list_for_you_feed (line 73) | def list_for_you_feed(self, count: int) -> List[Post]:
    method list_following_feed (line 101) | def list_following_feed(self, list_feed_request: ListFeedRequest) -> L...
    method list_categories (line 112) | def list_categories(
    method get_user (line 125) | def get_user(self, user_id: str) -> UserProfileResponse:
    method _search_users (line 136) | def _search_users(self, user_search_request: UserSearchRequest) -> Use...
    method search_users (line 147) | def search_users(self, keyword: str, count: int) -> List[UserSearchRes...
    method _search_posts_by_hashtag (line 169) | def _search_posts_by_hashtag(
    method search_posts_by_hashtag (line 182) | def search_posts_by_hashtag(self, hashtag: ChallengeInfo, count: int) ...
    method _search_hashtags (line 204) | def _search_hashtags(self, hashtag_search_request: SearchRequest) -> H...
    method _follow (line 215) | def _follow(self, request: FollowRequest) -> FollowResponse:
    method search_hashtags (line 226) | def search_hashtags(self, keyword: str, count: int) -> List[HashtagSea...

FILE: tiktok_bot/bot/bot.py
  class TikTokBot (line 16) | class TikTokBot:
    method __init__ (line 17) | def __init__(self, log_level: Literal["INFO", "DEBUG"] = "INFO"):
    method list_categories (line 23) | def list_categories(self, count: int = 10, cursor: int = 0) -> List[Ca...
    method get_user_by_id (line 29) | def get_user_by_id(self, user_id: str) -> UserProfile:
    method search_users (line 34) | def search_users(self, keyword: str, count: int = 6) -> List[CommonUse...
    method search_hashtags (line 41) | def search_hashtags(self, keyword: str, count: int = 6) -> List[Challe...
    method search_posts_by_hashtag (line 48) | def search_posts_by_hashtag(self, hashtag_name: str, count: int = 6) -...
    method list_for_you_feed (line 59) | def list_for_you_feed(self, count: int = 6) -> List[Post]:
    method list_following_feed (line 64) | def list_following_feed(self, count: int = 6, cursor: int = 0) -> List...

FILE: tiktok_bot/client/client.py
  class HTTPClient (line 12) | class HTTPClient:
    method __init__ (line 13) | def __init__(
    method get (line 30) | def get(self, url: str, params: dict, headers: Optional[dict] = None):
    method post (line 49) | def post(
    method _generate_params (line 76) | def _generate_params(self):

FILE: tiktok_bot/client/utils.py
  function generate_as (line 5) | def generate_as(now: str) -> str:
  function generate_cp (line 11) | def generate_cp(now: str) -> str:
  function generate_mas (line 19) | def generate_mas(now: str) -> str:

FILE: tiktok_bot/models/category.py
  class ChallengeInfo (line 10) | class ChallengeInfo(BaseModel):
    class Config (line 35) | class Config:
  class Category (line 39) | class Category(BaseModel):
  class ListCategoriesRequest (line 53) | class ListCategoriesRequest(ListRequestParams, CountOffsetParams):
  class ListCategoriesResponse (line 57) | class ListCategoriesResponse(ListResponseData, CountOffsetParams):

FILE: tiktok_bot/models/comment.py
  class Comment (line 11) | class Comment(BaseModel):
  class ListCommentsRequest (line 46) | class ListCommentsRequest(ListRequestParams, CountOffsetParams):
  class ListCommentsResponse (line 60) | class ListCommentsResponse(ListResponseData, CountOffsetParams):
  class PostCommentRequest (line 64) | class PostCommentRequest(BaseModel):
  class PostCommentResponse (line 81) | class PostCommentResponse(BaseResponseData):

FILE: tiktok_bot/models/feed.py
  class ListFeedRequest (line 13) | class ListFeedRequest(ListRequestParams, CursorOffsetRequestParams):
    class Config (line 35) | class Config:
  class ListFeedResponse (line 39) | class ListFeedResponse(ListResponseData, CursorOffsetResponseParams):
  class ListForYouFeedResponse (line 44) | class ListForYouFeedResponse(ListFeedResponse):

FILE: tiktok_bot/models/feed_enums.py
  class FeedType (line 4) | class FeedType(IntEnum):
  class PullType (line 9) | class PullType(IntEnum):

FILE: tiktok_bot/models/follow.py
  class FollowRequest (line 16) | class FollowRequest(BaseModel):
  class FollowResponse (line 24) | class FollowResponse(BaseResponseData):
  class ListReceivedFollowRequestsRequest (line 32) | class ListReceivedFollowRequestsRequest(ListRequestParams, TimeOffsetReq...
  class ListReceivedFollowRequestsResponse (line 36) | class ListReceivedFollowRequestsResponse(ListResponseData, TimeOffsetRes...
  class ApproveFollowRequest (line 41) | class ApproveFollowRequest(BaseModel):
  class ApproveFollowResponse (line 46) | class ApproveFollowResponse(BaseResponseData):
  class RejectFollowRequest (line 51) | class RejectFollowRequest(BaseModel):
  class RejectFollowResponse (line 56) | class RejectFollowResponse(BaseResponseData):

FILE: tiktok_bot/models/follower.py
  class ListFollowersRequest (line 12) | class ListFollowersRequest(ListRequestParams, TimeOffsetRequestParams):
  class ListFollowersResponse (line 17) | class ListFollowersResponse(ListResponseData, TimeOffsetResponseParams):
  class ListFollowingRequest (line 22) | class ListFollowingRequest(ListRequestParams, TimeOffsetRequestParams):
  class ListFollowingResponse (line 27) | class ListFollowingResponse(ListResponseData, TimeOffsetResponseParams):

FILE: tiktok_bot/models/hashtag.py
  class ListPostsInHashtagRequest (line 7) | class ListPostsInHashtagRequest(ListRequestParams, CountOffsetParams):
  class ListPostsInHashtagResponse (line 18) | class ListPostsInHashtagResponse(ListResponseData, CountOffsetParams):

FILE: tiktok_bot/models/like.py
  class LikePostRequest (line 6) | class LikePostRequest(BaseResponseData):
  class LikePostResponse (line 14) | class LikePostResponse(BaseResponseData):

FILE: tiktok_bot/models/login.py
  class LoginRequest (line 6) | class LoginRequest(BaseModel):
  class LoginSuccessData (line 29) | class LoginSuccessData(BaseModel):
  class LoginErrorData (line 118) | class LoginErrorData(BaseModel):
  class LoginResponse (line 129) | class LoginResponse(BaseModel):

FILE: tiktok_bot/models/music.py
  class MusicTrack (line 8) | class MusicTrack(BaseModel):

FILE: tiktok_bot/models/post.py
  class PostStatistics (line 17) | class PostStatistics(BaseModel):
  class PostStatus (line 37) | class PostStatus(BaseModel):
  class PostTags (line 66) | class PostTags(BaseModel):
  class RiskInfo (line 77) | class RiskInfo(BaseModel):
  class ShareInfo (line 94) | class ShareInfo(BaseModel):
  class StickerInfo (line 123) | class StickerInfo(BaseModel):
  class Post (line 131) | class Post(BaseModel):
    method video_url (line 193) | def video_url(self):
    method video_url_without_watermark (line 199) | def video_url_without_watermark(self):
  class GetPostResponse (line 203) | class GetPostResponse(BaseResponseData):
  class ListPostsRequest (line 207) | class ListPostsRequest(ListRequestParams, CursorOffsetRequestParams):
  class ListPostsResponse (line 212) | class ListPostsResponse(ListResponseData, CursorOffsetResponseParams):

FILE: tiktok_bot/models/qr-code.py
  class QRCodeRequest (line 8) | class QRCodeRequest(BaseModel):
  class QRCodeUrl (line 16) | class QRCodeUrl(BaseModel):
  class QRCodeResponse (line 24) | class QRCodeResponse(BaseResponseData):

FILE: tiktok_bot/models/request.py
  class RequiredUserDefinedRequestParams (line 7) | class RequiredUserDefinedRequestParams(BaseModel, abc.ABC):
  class StaticRequestParams (line 21) | class StaticRequestParams(RequiredUserDefinedRequestParams):
    class Config (line 114) | class Config:
  class AntiSpamParams (line 118) | class AntiSpamParams(BaseModel):
    class Config (line 128) | class Config:
  class BaseRequestParams (line 132) | class BaseRequestParams(StaticRequestParams, AntiSpamParams):
  class ListRequestParams (line 140) | class ListRequestParams(BaseModel):
  class TimeOffsetRequestParams (line 148) | class TimeOffsetRequestParams(BaseModel):
  class TimeOffsetResponseParams (line 157) | class TimeOffsetResponseParams(BaseModel):
  class CursorOffsetRequestParams (line 165) | class CursorOffsetRequestParams(BaseModel):
  class CursorOffsetResponseParams (line 174) | class CursorOffsetResponseParams(BaseModel):
  class CountOffsetParams (line 182) | class CountOffsetParams(BaseModel):
  class ExtraResponseData (line 187) | class ExtraResponseData(BaseModel):
  class BaseResponseData (line 198) | class BaseResponseData(BaseModel, abc.ABC):
  class ListResponseData (line 205) | class ListResponseData(BaseResponseData):
  class Media (line 213) | class Media(BaseModel):

FILE: tiktok_bot/models/search.py
  class SearchRequest (line 11) | class SearchRequest(ListRequestParams, CountOffsetParams):
  class UserSearchRequest (line 16) | class UserSearchRequest(SearchRequest):
  class SubstringPosition (line 21) | class SubstringPosition(BaseModel):
  class UserSearchResult (line 38) | class UserSearchResult(BaseModel):
  class UserSearchResponse (line 50) | class UserSearchResponse(ListResponseData, CountOffsetParams):
  class HashtagSearchResult (line 58) | class HashtagSearchResult(BaseModel):
  class HashtagSearchResponse (line 66) | class HashtagSearchResponse(ListResponseData, CountOffsetParams):

FILE: tiktok_bot/models/sticker.py
  class Sticker (line 9) | class Sticker(BaseModel):
  class ListPostsByStickerRequest (line 44) | class ListPostsByStickerRequest(ListRequestParams, CountOffsetParams):
  class ListPostsByStickerResponse (line 49) | class ListPostsByStickerResponse(ListResponseData, CountOffsetParams):
  class GetStickersRequest (line 57) | class GetStickersRequest(BaseModel):
  class GetStickersResponse (line 62) | class GetStickersResponse(BaseResponseData):

FILE: tiktok_bot/models/tag.py
  class Tag (line 7) | class Tag(BaseModel):

FILE: tiktok_bot/models/user.py
  class CommonUserDetails (line 8) | class CommonUserDetails(BaseModel):
  class UserProfile (line 67) | class UserProfile(CommonUserDetails):
  class UserProfileResponse (line 84) | class UserProfileResponse(BaseResponseData):

FILE: tiktok_bot/models/video.py
  class Video (line 6) | class Video(BaseModel):
Condensed preview — 47 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (66K chars).
[
  {
    "path": ".bumpversion.cfg",
    "chars": 187,
    "preview": "[bumpversion]\ncommit = True\ntag = True\ncurrent_version = 0.6.4\n\n[bumpversion:file:pyproject.toml]\n\n[bumpversion:file:tik"
  },
  {
    "path": ".flake8",
    "chars": 50,
    "preview": "[flake8]\nmax-line-length = 100\nignore = C812,W503\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 643,
    "preview": "# These are supported funding model platforms\n\ngithub: [sudoguy]\npatreon: # Replace with a single Patreon username\nopen_"
  },
  {
    "path": ".github/workflows/pythonpackage.yml",
    "chars": 2376,
    "preview": "name: Main\n\non: [push, pull_request]\n\njobs:\n  Linting:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/chec"
  },
  {
    "path": ".gitignore",
    "chars": 1222,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".isort.cfg",
    "chars": 187,
    "preview": "[settings]\nline_length=100\nindent=4\n\nmulti_line_output=3\ninclude_trailing_comma=True\n\nknown_first_party=tiktok_bot\nknown"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 1108,
    "preview": "repos:\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: v2.4.0\n  hooks:\n    - id: check-added-large-files\n "
  },
  {
    "path": ".travis.yml",
    "chars": 1189,
    "preview": "language: python\n\nos:\n  - linux\n\nstages:\n  - lint\n  - test\n  - name: deploy\n    if: tag IS present\n\npython:\n  - \"3.6\"\n  "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 413,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3351,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2019 Evgeny Kemerov\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 1372,
    "preview": "# This project is no longer under development.\n# A NEW project is being developed here: [sudoguy/tiktokpy](https://githu"
  },
  {
    "path": "docs/css/custom.css",
    "chars": 187,
    "preview": "div.autodoc-docstring {\n  padding-left: 20px;\n  margin-bottom: 30px;\n  border-left: 5px solid rgba(230, 230, 230);\n}\n\ndi"
  },
  {
    "path": "docs/index.md",
    "chars": 485,
    "preview": "# Welcome to MkDocs\n\nFor full documentation visit [mkdocs.org](https://mkdocs.org).\n\n## Commands\n\n* `mkdocs new [dir-nam"
  },
  {
    "path": "mkdocs.yml",
    "chars": 325,
    "preview": "site_name: TikTok Bot\nsite_description: Free TikTok bot for Python.\n\ntheme:\n    name: \"material\"\n\nrepo_name: sudoguy/tik"
  },
  {
    "path": "pyproject.toml",
    "chars": 1775,
    "preview": "[tool.poetry]\nname = \"tiktok_bot\"\nversion = \"0.6.4\"\ndescription = \"Tik Tok API\"\n\nlicense = \"MIT\"\nauthors = [\"Evgeny Keme"
  },
  {
    "path": "tests/api/test_api.py",
    "chars": 238,
    "preview": "from tiktok_bot.api import TikTokAPI\n\n\ndef test_encrypt_with_xor(api: TikTokAPI):\n    assert api.encrypt_with_XOR(\"user@"
  },
  {
    "path": "tests/conftest.py",
    "chars": 106,
    "preview": "import pytest\n\nfrom tiktok_bot.api import TikTokAPI\n\n\n@pytest.fixture()\ndef api():\n    return TikTokAPI()\n"
  },
  {
    "path": "tests/test_tiktok-bot.py",
    "chars": 91,
    "preview": "from tiktok_bot import __version__\n\n\ndef test_version():\n    assert __version__ == \"0.6.4\"\n"
  },
  {
    "path": "tiktok_bot/__init__.py",
    "chars": 112,
    "preview": "from .bot import TikTokBot\nfrom .client import client\n\n__all__ = [\"TikTokBot\", \"client\"]\n\n__version__ = \"0.6.4\"\n"
  },
  {
    "path": "tiktok_bot/api/__init__.py",
    "chars": 52,
    "preview": "from .api import TikTokAPI\n\n__all__ = [\"TikTokAPI\"]\n"
  },
  {
    "path": "tiktok_bot/api/api.py",
    "chars": 8203,
    "preview": "import itertools\nfrom typing import List\n\nfrom loguru import logger\nfrom tqdm import tqdm\n\nfrom tiktok_bot.client import"
  },
  {
    "path": "tiktok_bot/api/config.py",
    "chars": 1627,
    "preview": "# ToDo: Make it configurable\nDEFAULT_PARAMS = {\n    \"os_api\": \"23\",\n    \"device_type\": \"Pixel\",\n    \"ssmix\": \"a\",\n    \"m"
  },
  {
    "path": "tiktok_bot/bot/__init__.py",
    "chars": 52,
    "preview": "from .bot import TikTokBot\n\n__all__ = [\"TikTokBot\"]\n"
  },
  {
    "path": "tiktok_bot/bot/bot.py",
    "chars": 2543,
    "preview": "import sys\nfrom typing import List\n\nfrom loguru import logger\nfrom typing_extensions import Literal\n\nfrom tiktok_bot.api"
  },
  {
    "path": "tiktok_bot/client/__init__.py",
    "chars": 57,
    "preview": "from .client import HTTPClient\n\n__all__ = [\"HTTPClient\"]\n"
  },
  {
    "path": "tiktok_bot/client/client.py",
    "chars": 2833,
    "preview": "from collections import deque\nfrom time import time\nfrom typing import Deque, Optional\nfrom uuid import uuid4\n\nfrom http"
  },
  {
    "path": "tiktok_bot/client/utils.py",
    "chars": 422,
    "preview": "from hashlib import md5, sha1\nfrom time import time\n\n\ndef generate_as(now: str) -> str:\n    as_md5 = md5(now.encode()).h"
  },
  {
    "path": "tiktok_bot/models/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tiktok_bot/models/category.py",
    "chars": 1355,
    "preview": "from typing import List, Union\n\nfrom pydantic import BaseModel, Schema\n\nfrom .post import Post\nfrom .request import Coun"
  },
  {
    "path": "tiktok_bot/models/comment.py",
    "chars": 1901,
    "preview": "from typing import List, Optional\n\nfrom pydantic import BaseModel\nfrom typing_extensions import Literal\n\nfrom .request i"
  },
  {
    "path": "tiktok_bot/models/feed.py",
    "chars": 1042,
    "preview": "from typing import List, Optional\n\nfrom .feed_enums import FeedType, PullType\nfrom .post import Post\nfrom .request impor"
  },
  {
    "path": "tiktok_bot/models/feed_enums.py",
    "chars": 383,
    "preview": "from enum import IntEnum\n\n\nclass FeedType(IntEnum):\n    ForYou = 0\n    Following = 1\n\n\nclass PullType(IntEnum):\n    # Th"
  },
  {
    "path": "tiktok_bot/models/follow.py",
    "chars": 1328,
    "preview": "from typing import List\n\nfrom pydantic import BaseModel\nfrom typing_extensions import Literal\n\nfrom .request import (\n  "
  },
  {
    "path": "tiktok_bot/models/follower.py",
    "chars": 788,
    "preview": "from typing import List\n\nfrom .request import (\n    ListRequestParams,\n    ListResponseData,\n    TimeOffsetRequestParams"
  },
  {
    "path": "tiktok_bot/models/hashtag.py",
    "chars": 471,
    "preview": "from typing import List\n\nfrom .post import Post\nfrom .request import CountOffsetParams, ListRequestParams, ListResponseD"
  },
  {
    "path": "tiktok_bot/models/like.py",
    "chars": 422,
    "preview": "from typing_extensions import Literal\n\nfrom .request import BaseResponseData\n\n\nclass LikePostRequest(BaseResponseData):\n"
  },
  {
    "path": "tiktok_bot/models/login.py",
    "chars": 2607,
    "preview": "from typing import List, Union\n\nfrom pydantic import BaseModel\n\n\nclass LoginRequest(BaseModel):\n    # Unsure, but looks "
  },
  {
    "path": "tiktok_bot/models/music.py",
    "chars": 962,
    "preview": "from typing import Optional\n\nfrom pydantic import BaseModel\n\nfrom .request import Media\n\n\nclass MusicTrack(BaseModel):\n "
  },
  {
    "path": "tiktok_bot/models/post.py",
    "chars": 5030,
    "preview": "from typing import List, Optional\n\nfrom pydantic import BaseModel\n\nfrom .music import MusicTrack\nfrom .request import (\n"
  },
  {
    "path": "tiktok_bot/models/qr-code.py",
    "chars": 547,
    "preview": "from typing import List\n\nfrom pydantic import BaseModel\n\nfrom .request import BaseResponseData\n\n\nclass QRCodeRequest(Bas"
  },
  {
    "path": "tiktok_bot/models/request.py",
    "chars": 5501,
    "preview": "import abc\nfrom typing import List, Optional, Union\n\nfrom pydantic import BaseModel\n\n\nclass RequiredUserDefinedRequestPa"
  },
  {
    "path": "tiktok_bot/models/search.py",
    "chars": 2033,
    "preview": "from typing import List, Optional\n\nfrom pydantic import BaseModel\nfrom typing_extensions import Literal\n\nfrom .category "
  },
  {
    "path": "tiktok_bot/models/sticker.py",
    "chars": 1360,
    "preview": "from typing import Any, List\n\nfrom pydantic import BaseModel\n\nfrom .post import Post\nfrom .request import BaseResponseDa"
  },
  {
    "path": "tiktok_bot/models/tag.py",
    "chars": 430,
    "preview": "\"\"\"\n  Represents a text link to a user, e.g. \"@username\" in a comment\n\"\"\"\nfrom pydantic import BaseModel\n\n\nclass Tag(Bas"
  },
  {
    "path": "tiktok_bot/models/user.py",
    "chars": 1906,
    "preview": "from typing import Optional, Union\n\nfrom pydantic import BaseModel\n\nfrom .request import BaseResponseData, Media\n\n\nclass"
  },
  {
    "path": "tiktok_bot/models/video.py",
    "chars": 602,
    "preview": "from pydantic import BaseModel\n\nfrom .request import Media\n\n\nclass Video(BaseModel):\n    # A medium version of the video"
  }
]

About this extraction

This page contains the full source code of the sudoguy/tiktok_bot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 47 files (59.5 KB), approximately 16.3k tokens, and a symbol index with 122 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!