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
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
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.