├── .github
└── workflows
│ ├── test-build.yaml
│ └── test-python.yaml
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── CONTRIBUTING.md
├── LICENSE
├── LICENSE.AGPL
├── README.md
├── debug
└── requests_sample.py
├── dist
├── compatible
│ └── openapi-3.0.yaml
└── docs
│ └── openapi-3.0.yaml
├── other
└── error
│ └── GenericError.json
├── requirements.txt
├── src
├── config
│ ├── component
│ │ └── response_header.yaml
│ └── placeholder.json
└── openapi
│ ├── openapi-3.0.yaml
│ ├── paths
│ ├── bookmarks.yaml
│ ├── follow.yaml
│ ├── other.yaml
│ ├── post.yaml
│ ├── profile.yaml
│ ├── timeline.yaml
│ ├── tweet.yaml
│ ├── user.yaml
│ ├── usertweets.yaml
│ ├── v1.1-get.yaml
│ ├── v1.1-post.yaml
│ └── v2.0-get.yaml
│ ├── resources
│ └── parameters.yaml
│ ├── response
│ └── error.yaml
│ └── schemas
│ ├── content.yaml
│ ├── general.yaml
│ ├── instruction.yaml
│ ├── timeline.yaml
│ ├── tweet.yaml
│ ├── typename.yaml
│ └── user.yaml
├── test
├── dart-dio
│ └── openapi-generator-config.yaml
├── local.sh
├── python
│ ├── openapi-generator-config.yaml
│ ├── test_serialize.py
│ └── test_serialize_guest.py
└── typescript-fetch
│ └── openapi-generator-config.yaml
└── tools
├── build.py
├── build_config.py
├── generater.py
├── hooks.py
└── login.py
/.github/workflows/test-build.yaml:
--------------------------------------------------------------------------------
1 | name: build-test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 |
18 | # Openapi Generator Setup
19 | - name: Get OpenJDK 11
20 | uses: actions/setup-java@v2
21 | with:
22 | distribution: "temurin"
23 | java-version: 11
24 |
25 | - name: Get Java version
26 | run: java -version
27 |
28 | - name: Get Openapi Generator cache file
29 | id: openapi-generator-cache
30 | run: echo "file=openapi-generator-cli.jar" >> $GITHUB_OUTPUT
31 |
32 | - name: Openapi Generator cache
33 | uses: actions/cache@v3
34 | with:
35 | path: ${{ steps.openapi-generator-cache.outputs.file }}
36 | key: ${{ runner.os }}-openapi-generator-${{ hashFiles('openapi-generator-cli.jar') }}
37 | restore-keys: |
38 | ${{ runner.os }}-openapi-generator-
39 |
40 | - name: Get Openapi Generator
41 | run: |
42 | wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.12.0/openapi-generator-cli-7.12.0.jar -O openapi-generator-cli.jar --no-verbose
43 | if: steps.openapi-generator-cache.outputs.cache-hit != 'true'
44 |
45 | # Python Setup
46 | - name: Set up Python 3.10
47 | uses: actions/setup-python@v4
48 | with:
49 | python-version: "3.10"
50 | architecture: "x64"
51 |
52 | - name: Get Python version
53 | run: python -V
54 |
55 | - name: Get pip cache dir
56 | id: pip-cache
57 | run: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
58 |
59 | - name: pip cache
60 | uses: actions/cache@v3
61 | with:
62 | path: ${{ steps.pip-cache.outputs.dir }}
63 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
64 | restore-keys: |
65 | ${{ runner.os }}-pip-
66 |
67 | - name: Install dependencies
68 | run: |
69 | python -m pip install --upgrade pip
70 | pip install -r requirements.txt
71 |
72 | # generate
73 | - name: Build twitter-openapi
74 | run: |
75 | python tools/build.py
76 |
77 | # clean up dependencies
78 | - name: Clean up dependencies
79 | run: |
80 | pip uninstall -y -r requirements.txt
81 |
82 | # generate
83 | - name: Generate
84 | run: |
85 | java -jar openapi-generator-cli.jar generate -c test/python/openapi-generator-config.yaml -g python
86 | java -jar openapi-generator-cli.jar generate -c test/dart-dio/openapi-generator-config.yaml -g dart-dio
87 | java -jar openapi-generator-cli.jar generate -c test/typescript-fetch/openapi-generator-config.yaml -g typescript-fetch
88 |
--------------------------------------------------------------------------------
/.github/workflows/test-python.yaml:
--------------------------------------------------------------------------------
1 | name: test-python
2 |
3 | on:
4 | pull_request_target:
5 | types: [labeled]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build_and_preview:
10 | runs-on: ubuntu-latest
11 | name: Test
12 | if: contains(github.event.pull_request.labels.*.name, 'safe to test')
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | with:
17 | ref: "${{ github.event.pull_request.merge_commit_sha }}"
18 |
19 | # Openapi Generator Setup
20 | - name: Get OpenJDK 11
21 | uses: actions/setup-java@v2
22 | with:
23 | distribution: "temurin"
24 | java-version: 11
25 |
26 | - name: Get Java version
27 | run: java -version
28 |
29 | - name: Get Openapi Generator cache file
30 | id: openapi-generator-cache
31 | run: echo "file=openapi-generator-cli.jar" >> $GITHUB_OUTPUT
32 |
33 | - name: Openapi Generator cache
34 | uses: actions/cache@v3
35 | with:
36 | path: ${{ steps.openapi-generator-cache.outputs.file }}
37 | key: ${{ runner.os }}-openapi-generator-${{ hashFiles('openapi-generator-cli.jar') }}
38 | restore-keys: |
39 | ${{ runner.os }}-openapi-generator-
40 |
41 | - name: Get Openapi Generator
42 | run: |
43 | wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.13.0/openapi-generator-cli-7.13.0.jar -O openapi-generator-cli.jar --no-verbose
44 | if: steps.openapi-generator-cache.outputs.cache-hit != 'true'
45 |
46 | # Python Setup
47 | - name: Set up Python 3.10
48 | uses: actions/setup-python@v4
49 | with:
50 | python-version: "3.10"
51 | architecture: "x64"
52 |
53 | - name: Get Python version
54 | run: python -V
55 |
56 | - name: Get pip cache dir
57 | id: pip-cache
58 | run: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
59 |
60 | - name: pip cache
61 | uses: actions/cache@v3
62 | with:
63 | path: ${{ steps.pip-cache.outputs.dir }}
64 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
65 | restore-keys: |
66 | ${{ runner.os }}-pip-
67 |
68 | - name: Install dependencies
69 | run: |
70 | python -m pip install --upgrade pip
71 | pip install -r requirements.txt
72 |
73 | # generate
74 | - name: Build twitter-openapi
75 | run: |
76 | python tools/build.py
77 |
78 | # clean up dependencies
79 | - name: Clean up dependencies
80 | run: |
81 | pip uninstall -y -r requirements.txt
82 |
83 | # generate
84 | - name: Generate Python client
85 | run: |
86 | java -jar openapi-generator-cli.jar generate -c test/python/openapi-generator-config.yaml -g python
87 |
88 | # test
89 | - name: Test Python client
90 | run: |
91 | pip install ./python_generated
92 |
93 | - name: Run Python client test
94 | run: |
95 | python test/python/test_serialize.py
96 | env:
97 | TWITTER_SESSION: ${{ secrets.TWITTER_SESSION }}
98 | ERROR_UNCATCHED: "False"
99 | SLEEP_TIME: 2
100 | CUESOR_TEST_COUNT: 5
101 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .venv
2 | __pycache__/
3 | cookie.json
4 | *_generated
5 | *.jar
6 | cache/
7 | tools/input.js
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "redhat.vscode-yaml",
4 | "ms-python.vscode-pylance",
5 | "ms-python.python",
6 | "ms-python.debugpy",
7 | "charliermarsh.ruff"
8 | ]
9 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Python: 現在のファイル",
6 | "type": "debugpy",
7 | "request": "launch",
8 | "program": "${file}",
9 | "console": "integratedTerminal",
10 | "justMyCode": false
11 | },
12 | {
13 | "name": "build",
14 | "type": "debugpy",
15 | "request": "launch",
16 | "program": "tools/build.py",
17 | "console": "integratedTerminal",
18 | "justMyCode": true
19 | },
20 | {
21 | "name": "test",
22 | "type": "debugpy",
23 | "request": "launch",
24 | "program": "test/python/test_serialize.py",
25 | "console": "integratedTerminal",
26 | "justMyCode": false,
27 | "preLaunchTask": "build-task",
28 | "env": {
29 | "ERROR_UNCATCHED": "True",
30 | "STRICT_MODE": "True",
31 | "MULTI_THREAD": "True",
32 | "CUESOR_TEST_COUNT": "3"
33 | }
34 | },
35 | {
36 | "name": "test-guest",
37 | "type": "debugpy",
38 | "request": "launch",
39 | "program": "test/python/test_serialize_guest.py",
40 | "console": "integratedTerminal",
41 | "justMyCode": false,
42 | "preLaunchTask": "build-task"
43 | }
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "yaml.format.enable": true,
3 | "yaml.completion": true,
4 | "yaml.validate": true,
5 | "yaml.hover": true,
6 | "yaml.schemas": {
7 | "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/refs/heads/main/schemas/v3.0/schema.yaml": "src/**/*.yaml"
8 | },
9 | "[json]": {
10 | "editor.defaultFormatter": "esbenp.prettier-vscode"
11 | },
12 | "[jsonc]": {
13 | "editor.defaultFormatter": "esbenp.prettier-vscode"
14 | },
15 | "[yaml]": {
16 | "editor.defaultFormatter": "esbenp.prettier-vscode"
17 | },
18 | "[markdown]": {
19 | "editor.defaultFormatter": "esbenp.prettier-vscode"
20 | },
21 | // "python.analysis.typeCheckingMode": "basic",
22 | "[python]": {
23 | "editor.codeActionsOnSave": {
24 | "source.organizeImports": "explicit",
25 | "source.fixAll": "explicit"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build-task",
6 | "type": "shell",
7 | "osx": {
8 | "command": [
9 | "source .venv/bin/activate;",
10 | "python3 tools/build.py;",
11 | "java -jar openapi-generator-cli.jar generate -c test/python/openapi-generator-config.yaml -g python;",
12 | "python3 -m pip install ./python_generated;"
13 | ]
14 | },
15 | "linux": {
16 | "command": [
17 | "source .venv/bin/activate;",
18 | "python3 tools/build.py;",
19 | "java -jar openapi-generator-cli.jar generate -c test/python/openapi-generator-config.yaml -g python;",
20 | "python3 -m pip install ./python_generated;"
21 | ]
22 | },
23 | "windows": {
24 | "command": [
25 | ".venv/Scripts/activate;",
26 | "python tools/build.py;",
27 | "scoop reset temurin17-jdk;",
28 | "java -jar openapi-generator-cli.jar generate -c test/python/openapi-generator-config.yaml -g python;",
29 | "python -m pip install ./python_generated;"
30 | ]
31 | }
32 | },
33 | {
34 | "label": "init-venv",
35 | "type": "shell",
36 | "runOptions": {
37 | "runOn": "folderOpen"
38 | },
39 | "osx": {
40 | "command": [
41 | "python3.10 -m venv .venv;",
42 | ".venv/bin/python3 -m pip install -r requirements.txt;",
43 | "pip install urllib3>=2.1.0;",
44 | "curl https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.13.0/openapi-generator-cli-7.13.0.jar -o openapi-generator-cli.jar;"
45 | ]
46 | },
47 | "linux": {
48 | "command": [
49 | "python3 -m venv .venv;",
50 | ".venv/bin/python3 -m pip install -r requirements.txt;",
51 | "pip install urllib3>=2.1.0;",
52 | "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.13.0/openapi-generator-cli-7.13.0.jar -O openapi-generator-cli.jar;"
53 | ]
54 | },
55 | "windows": {
56 | "command": [
57 | "python -m venv .venv;",
58 | ".venv/Scripts/python -m pip install -r requirements.txt;",
59 | "pip install urllib3>=2.1.0;",
60 | "Invoke-WebRequest https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.13.0/openapi-generator-cli-7.13.0.jar -OutFile openapi-generator-cli.jar;"
61 | ]
62 | }
63 | }
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # CONTRIBUTING
2 |
3 | - `src` *.yaml files should be written according to the [v3.0/schema.json](https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.0/schema.json)
4 | - `dist` Do not rewrite this file as it is an automatically generated OpenAPI file.
5 | - `dist` Do not include this in the pull request due to potential conflicts.
6 |
7 | Users using vscode can build and test with the GUI.
8 | [.vscode/launch.json](.vscode/launch.json) is a configuration file for vscode.
9 |
10 | ## Good first issues
11 |
12 | - Add a new endpoint
13 | - Add type to `type: object # todo`
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | twitter-openapi License
2 |
3 | Copyright (c) 2023 yuki
4 |
5 |
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | The Software shall not be used for any activity that involves the following types
18 | of behavior, commonly known as spam:
19 |
20 | 1. Sending unsolicited or excessive messages or posts.
21 | 2. Aggressively following, unfollowing, or liking tweets to artificially boost engagement.
22 | 3. Engaging in aggressive automated actions that disrupt or annoy other users.
23 | 4. Distributing false or misleading information.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | SOFTWARE.
32 |
--------------------------------------------------------------------------------
/LICENSE.AGPL:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published by
637 | the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Twitter OpenAPI
2 |
3 | Twitter OpenAPI(Swagger) specification
4 |
5 | [issues](https://github.com/fa0311/twitter-openapi/issues) / [discussions](https://github.com/fa0311/twitter-openapi/discussions)
6 |
7 | - [Python](https://github.com/fa0311/twitter_openapi_python)
8 | - [Dart](https://github.com/fa0311/twitter_openapi_dart)
9 | - [TypeScript](https://github.com/fa0311/twitter-openapi-typescript)
10 | - [React Documents](https://github.com/fa0311/twitter-openapi-docs)
11 |
12 | ## Usage
13 |
14 | ```shell
15 | openapi-generator-cli generate -g -i https://raw.githubusercontent.com/fa0311/twitter-openapi/main/dist/docs/openapi-3.0.yaml -o ./generated
16 | ```
17 |
18 | There are several outputs, but the one that most closely follows the OpenAPI specification is `dist/docs`.
19 | However, a lot of syntax that is not supported by some generators is used.
20 |
21 | You can also modify the hook to make the generated results more user-friendly. [build_config.py](./tools/build_config.py)
22 |
23 | Note that the license also inherits to the output.
24 |
25 | ## Contribute
26 |
27 | [CONTRIBUTING.md](./CONTRIBUTING.md)
28 |
29 | ### build
30 |
31 | ```shell
32 | python -V # Python 3.10.8
33 | pip install -r requirements.txt
34 | python tools/build.py
35 | ```
36 |
37 | ## License
38 |
39 | This project is dual licensed. You can choose one of the following licenses:
40 |
41 | - [Custom License](./LICENSE)
42 | - [GNU Affero General Public License v3.0](./LICENSE.AGPL)
43 |
--------------------------------------------------------------------------------
/debug/requests_sample.py:
--------------------------------------------------------------------------------
1 | import json
2 | from pathlib import Path
3 |
4 | import requests
5 |
6 | if Path("cookie.json").exists():
7 | with open("cookie.json", "r") as f:
8 | cookies = json.load(f)
9 | cookies_dict = {k["name"]: k["value"] for k in cookies}
10 |
11 |
12 | data = requests.get(
13 | "https://twitter.com/i/api/graphql/BQ6xjFU6Mgm-WhEP3OiT9w/UserByScreenName",
14 | cookies=cookies_dict,
15 | headers={
16 | "accept": "*/*",
17 | "accept-encoding": "identity",
18 | "accept-language": "en-US,en;q=0.9",
19 | "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
20 | "content-type": "application/json",
21 | "referer": "https://twitter.com",
22 | "sec-ch-ua": '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
23 | "sec-ch-ua-mobile": "?0",
24 | "sec-ch-ua-platform": '"Windows"',
25 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/Chrome/130.0.0.0 Safari/537.36",
26 | "x-csrf-token": cookies_dict["ct0"],
27 | "x-twitter-active-user": "yes",
28 | "x-twitter-auth-type": "OAuth2Session",
29 | "x-twitter-client-language": "en",
30 | },
31 | params={
32 | "variables": json.dumps(
33 | {
34 | "screen_name": "5unsetpowerln",
35 | },
36 | ),
37 | "features": json.dumps(
38 | {
39 | "hidden_profile_subscriptions_enabled": True,
40 | "rweb_tipjar_consumption_enabled": True,
41 | "responsive_web_graphql_exclude_directive_enabled": True,
42 | "verified_phone_label_enabled": False,
43 | "subscriptions_verification_info_is_identity_verified_enabled": True,
44 | "subscriptions_verification_info_verified_since_enabled": True,
45 | "highlights_tweets_tab_ui_enabled": True,
46 | "responsive_web_twitter_article_notes_tab_enabled": True,
47 | "subscriptions_feature_can_gift_premium": True,
48 | "creator_subscriptions_tweet_preview_api_enabled": True,
49 | "responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
50 | "responsive_web_graphql_timeline_navigation_enabled": True,
51 | }
52 | ),
53 | "fieldToggles": json.dumps(
54 | {
55 | "withAuxiliaryUserLabels": False,
56 | },
57 | ),
58 | },
59 | )
60 | print(data.json())
61 |
--------------------------------------------------------------------------------
/other/error/GenericError.json:
--------------------------------------------------------------------------------
1 | {
2 | "errors": [
3 | {
4 | "message": "_Missing: No status found with that ID.",
5 | "locations": [
6 | {
7 | "line": 2,
8 | "column": 3
9 | }
10 | ],
11 | "path": ["threaded_conversation_with_injections_v2"],
12 | "extensions": {
13 | "name": "GenericError",
14 | "source": "Server",
15 | "code": 144,
16 | "kind": "NonFatal",
17 | "tracing": {
18 | "trace_id": "e6de8b2b694ae128"
19 | }
20 | },
21 | "code": 144,
22 | "kind": "NonFatal",
23 | "name": "GenericError",
24 | "source": "Server",
25 | "tracing": {
26 | "trace_id": "e6de8b2b694ae128"
27 | }
28 | }
29 | ],
30 | "data": {}
31 | }
32 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fa0311/twitter-openapi/bc6ead94833764f54a6a4623871f7ad29926a7d9/requirements.txt
--------------------------------------------------------------------------------
/src/config/component/response_header.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 | paths: {}
6 |
7 | components:
8 | headers:
9 | x-connection-hash:
10 | schema:
11 | type: string
12 | x-rate-limit-limit:
13 | schema:
14 | type: integer
15 | x-rate-limit-remaining:
16 | schema:
17 | type: integer
18 | x-rate-limit-reset:
19 | schema:
20 | type: integer
21 | x-response-time:
22 | schema:
23 | type: integer
24 | x-tfe-preserve-body:
25 | schema:
26 | type: boolean
27 | x-transaction-id:
28 | schema:
29 | type: string
30 | x-twitter-response-tags:
31 | schema:
32 | type: string
33 |
--------------------------------------------------------------------------------
/src/openapi/openapi-3.0.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | description: |-
5 | Twitter OpenAPI(Swagger) specification
6 | termsOfService: https://github.com/fa0311
7 | contact:
8 | email: yuki@yuki0311.com
9 | license:
10 | name: custom license or AGPL-3.0-or-later
11 | url: https://github.com/fa0311/twitter-openapi#license
12 | version: 0.0.1
13 | servers:
14 | - url: https://x.com/i/api
15 | - url: https://twitter.com/i/api
16 |
17 | paths: {}
18 | components:
19 | schemas: {}
20 |
21 | securitySchemes:
22 | Accept:
23 | description: "*/*"
24 | in: header
25 | name: Accept
26 | type: apiKey
27 | AcceptEncoding:
28 | description: "gzip, deflate, br, zstd"
29 | in: header
30 | name: Accept-Encoding
31 | type: apiKey
32 | AcceptLanguage:
33 | description: "en-US,en;q=0.9"
34 | in: header
35 | name: Accept-Language
36 | type: apiKey
37 | Priority:
38 | description: "u=1, i"
39 | in: header
40 | name: Priority
41 | type: apiKey
42 | Referer:
43 | description: "https://x.com/home"
44 | in: header
45 | name: Referer
46 | type: apiKey
47 | SecChUa:
48 | description: '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"'
49 | in: header
50 | name: Sec-Ch-Ua
51 | type: apiKey
52 | SecChUaMobile:
53 | description: "?0"
54 | in: header
55 | name: Sec-Ch-Ua-Mobile
56 | type: apiKey
57 | SecChUaPlatform:
58 | description: '"Windows"'
59 | in: header
60 | name: Sec-Ch-Ua-Platform
61 | type: apiKey
62 | SecFetchDest:
63 | description: "empty"
64 | in: header
65 | name: Sec-Fetch-Dest
66 | type: apiKey
67 | SecFetchMode:
68 | description: "cors"
69 | in: header
70 | name: Sec-Fetch-Mode
71 | type: apiKey
72 | SecFetchSite:
73 | description: "same-origin"
74 | in: header
75 | name: Sec-Fetch-Site
76 | type: apiKey
77 | UserAgent:
78 | description: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
79 | in: header
80 | name: user-agent
81 | type: apiKey
82 | BearerAuth:
83 | description: AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA
84 | scheme: bearer
85 | type: http
86 | ClientTransactionId:
87 | type: apiKey
88 | in: header
89 | name: x-client-transaction-id
90 | ClientUuid:
91 | type: apiKey
92 | in: header
93 | name: x-client-uuid
94 | description: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
95 | CsrfToken:
96 | type: apiKey
97 | in: header
98 | name: x-csrf-token
99 | description: "document.cookie.split('; ').find(row => row.startsWith('ct0='));"
100 | GuestToken:
101 | type: apiKey
102 | in: header
103 | name: x-guest-token
104 | description: "document.cookie.split('; ').find(row => row.startsWith('gt='));"
105 | ActiveUser:
106 | description: "yes"
107 | in: header
108 | name: x-twitter-active-user
109 | type: apiKey
110 | AuthType:
111 | type: apiKey
112 | in: header
113 | name: x-twitter-auth-type
114 | description: "OAuth2Session if you are logged in"
115 | ClientLanguage:
116 | description: en
117 | in: header
118 | name: x-twitter-client-language
119 | type: apiKey
120 | CookieAuthToken:
121 | type: apiKey
122 | in: cookie
123 | name: auth_token
124 | description: "HttpOnly cookie"
125 | CookieCt0:
126 | type: apiKey
127 | in: cookie
128 | name: ct0
129 | description: "document.cookie.split('; ').find(row => row.startsWith('ct0='));"
130 | CookieGt0:
131 | type: apiKey
132 | in: cookie
133 | name: gt0
134 | description: "document.cookie.split('; ').find(row => row.startsWith('gt0='));"
135 |
136 | security:
137 | # Browser Schemes
138 | - Accept: []
139 | - AcceptEncoding: []
140 | - AcceptLanguage: []
141 | - Priority: []
142 | - Referer: []
143 | - SecChUa: []
144 | - SecChUaMobile: []
145 | - SecChUaPlatform: []
146 | - SecFetchDest: []
147 | - SecFetchMode: []
148 | - SecFetchSite: []
149 | # Other Auth Schemes
150 | - UserAgent: []
151 | - BearerAuth: []
152 | # X Schemes
153 | - ClientTransactionId: []
154 | - ClientUuid: []
155 | - CsrfToken: []
156 | - GuestToken: []
157 | # Twitter Schemes
158 | - ActiveUser: []
159 | - AuthType: []
160 | - ClientLanguage: []
161 | # Cookie Schemes
162 | - CookieCt0: []
163 | - CookieAuthToken: []
164 | - CookieGt0: []
165 |
166 | tags:
167 | - name: user
168 | description: Responses containing User objects.
169 | - name: users
170 | description: Responses containing List[User] objects.
171 | - name: user-list
172 | description: Responses containing instruction objects.
173 | - name: tweet
174 | description: Responses containing instruction objects.
175 | - name: post
176 | description: post
177 | - name: v1.1-get
178 | description: legacy APIs get
179 | - name: v1.1-post
180 | description: legacy APIs post
181 | - name: v2.0-get
182 | description: legacy APIs get
183 | - name: v2.0-post
184 | description: legacy APIs post
185 | - name: other
186 | description: other
187 |
--------------------------------------------------------------------------------
/src/openapi/paths/bookmarks.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /graphql/{pathQueryId}/Bookmarks:
8 | get:
9 | operationId: getBookmarks
10 | description: get bookmarks
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/BookmarksResponse"
18 | tags:
19 | - "tweet"
20 |
21 | components:
22 | schemas:
23 | BookmarksResponse:
24 | properties:
25 | data:
26 | $ref: "#/components/schemas/BookmarksResponseData"
27 | errors:
28 | type: array
29 | items:
30 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
31 |
32 | BookmarksResponseData:
33 | required:
34 | - "bookmark_timeline_v2"
35 | properties:
36 | bookmark_timeline_v2:
37 | $ref: "#/components/schemas/BookmarksTimeline"
38 |
39 | BookmarksTimeline:
40 | required:
41 | - "timeline"
42 | properties:
43 | timeline:
44 | $ref: "./../schemas/timeline.yaml#/components/schemas/Timeline"
45 |
--------------------------------------------------------------------------------
/src/openapi/paths/follow.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /graphql/{pathQueryId}/Following:
8 | get:
9 | operationId: getFollowing
10 | description: get user list of following
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/FollowResponse"
18 | tags:
19 | - "user-list"
20 |
21 | /graphql/{pathQueryId}/Followers:
22 | get:
23 | operationId: getFollowers
24 | description: get user list of followers
25 | responses:
26 | "200":
27 | description: Successful operation
28 | content:
29 | application/json:
30 | schema:
31 | $ref: "#/components/schemas/FollowResponse"
32 | tags:
33 | - "user-list"
34 |
35 | /graphql/{pathQueryId}/FollowersYouKnow:
36 | get:
37 | operationId: getFollowersYouKnow
38 | description: "get followers you know"
39 | responses:
40 | "200":
41 | description: Successful operation
42 | content:
43 | application/json:
44 | schema:
45 | $ref: "#/components/schemas/FollowResponse"
46 | tags:
47 | - "user-list"
48 |
49 | components:
50 | schemas:
51 | FollowResponse:
52 | required:
53 | - "data"
54 | properties:
55 | data:
56 | $ref: "#/components/schemas/FollowResponseData"
57 | errors:
58 | type: array
59 | items:
60 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
61 |
62 | FollowResponseData:
63 | properties:
64 | user:
65 | $ref: "#/components/schemas/FollowResponseUser"
66 |
67 | FollowResponseUser:
68 | required:
69 | - "result"
70 | properties:
71 | result:
72 | $ref: "#/components/schemas/FollowResponseResult"
73 |
74 | FollowResponseResult:
75 | required:
76 | - "__typename"
77 | - "timeline"
78 | properties:
79 | __typename:
80 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # User
81 | timeline:
82 | $ref: "#/components/schemas/FollowTimeline"
83 |
84 | FollowTimeline:
85 | required:
86 | - "timeline"
87 | properties:
88 | timeline:
89 | $ref: "./../schemas/timeline.yaml#/components/schemas/Timeline"
90 |
--------------------------------------------------------------------------------
/src/openapi/paths/other.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /other:
8 | get:
9 | operationId: other
10 | description: This is not an actual endpoint
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/OtherObjectAll"
18 | tags:
19 | - "other"
20 |
21 | components:
22 | schemas:
23 | OtherObjectAll:
24 | type: object
25 | properties:
26 | Session:
27 | $ref: "#/components/schemas/Session" # window.__INITIAL_STATE__
28 | Session:
29 | type: object
30 | required:
31 | - "country"
32 | - "communitiesActions"
33 | - "isActiveCreator"
34 | - "isRestrictedSession"
35 | - "guestId"
36 | - "hasCommunityMemberships"
37 | - "language"
38 | - "oneFactorLoginEligibility"
39 | - "ssoInitTokens"
40 | - "superFollowersCount"
41 | - "superFollowsApplicationStatus"
42 | - "user_id"
43 | - "userFeatures"
44 | - "isSuperFollowSubscriber"
45 | properties:
46 | country:
47 | type: string
48 | pattern: "^[A-Z]{2}$"
49 | communitiesActions:
50 | $ref: "#/components/schemas/CommunitiesActions"
51 | isActiveCreator:
52 | type: boolean
53 | isRestrictedSession:
54 | type: boolean
55 | guestId:
56 | type: string
57 | pattern: "^[0-9]+$"
58 | hasCommunityMemberships:
59 | type: boolean
60 | language:
61 | type: string
62 | pattern: "^[a-z]{2}$"
63 | oneFactorLoginEligibility:
64 | $ref: "#/components/schemas/OneFactorLoginEligibility"
65 | SsoInitTokens:
66 | type: object
67 | superFollowersCount:
68 | type: integer
69 | superFollowsApplicationStatus:
70 | type: string
71 | enum: [NotStarted]
72 | user_id:
73 | type: string
74 | pattern: "^[0-9]+$"
75 | userFeatures:
76 | $ref: "#/components/schemas/UserFeatures"
77 | isSuperFollowSubscriber:
78 | type: boolean
79 |
80 | CommunitiesActions:
81 | type: object
82 | required:
83 | - "create"
84 | properties:
85 | create:
86 | type: boolean
87 |
88 | OneFactorLoginEligibility:
89 | type: object
90 | required:
91 | - "fetchStatus"
92 | properties:
93 | fetchStatus:
94 | type: string # enum: none
95 |
96 | UserFeatures:
97 | type: object
98 | required:
99 | - "mediatool_studio_library"
100 | properties:
101 | mediatool_studio_library:
102 | type: boolean
103 |
--------------------------------------------------------------------------------
/src/openapi/paths/post.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /graphql/{pathQueryId}/FavoriteTweet:
8 | post:
9 | operationId: postFavoriteTweet
10 | description: favorite Tweet
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/FavoriteTweetResponse"
18 | tags:
19 | - "post"
20 |
21 | /graphql/{pathQueryId}/UnfavoriteTweet:
22 | post:
23 | operationId: postUnfavoriteTweet
24 | description: unfavorite Tweet
25 | responses:
26 | "200":
27 | description: Successful operation
28 | content:
29 | application/json:
30 | schema:
31 | $ref: "#/components/schemas/UnfavoriteTweetResponse"
32 | tags:
33 | - "post"
34 |
35 | /graphql/{pathQueryId}/CreateRetweet:
36 | post:
37 | operationId: postCreateRetweet
38 | description: create Retweet
39 | responses:
40 | "200":
41 | description: Successful operation
42 | content:
43 | application/json:
44 | schema:
45 | $ref: "#/components/schemas/CreateRetweetResponse"
46 | tags:
47 | - "post"
48 |
49 | /graphql/{pathQueryId}/DeleteRetweet:
50 | post:
51 | operationId: postDeleteRetweet
52 | description: delete Retweet
53 | responses:
54 | "200":
55 | description: Successful operation
56 | content:
57 | application/json:
58 | schema:
59 | $ref: "#/components/schemas/DeleteRetweetResponse"
60 | tags:
61 | - "post"
62 |
63 | /graphql/{pathQueryId}/CreateTweet:
64 | post:
65 | operationId: postCreateTweet
66 | description: create Tweet
67 | responses:
68 | "200":
69 | description: Successful operation
70 | content:
71 | application/json:
72 | schema:
73 | $ref: "#/components/schemas/CreateTweetResponse"
74 | tags:
75 | - "post"
76 |
77 | /graphql/{pathQueryId}/DeleteTweet:
78 | post:
79 | operationId: postDeleteTweet
80 | description: delete Retweet
81 | responses:
82 | "200":
83 | description: Successful operation
84 | content:
85 | application/json:
86 | schema:
87 | $ref: "#/components/schemas/DeleteTweetResponse"
88 | tags:
89 | - "post"
90 |
91 | /graphql/{pathQueryId}/CreateBookmark:
92 | post:
93 | operationId: postCreateBookmark
94 | description: create Bookmark
95 | responses:
96 | "200":
97 | description: Successful operation
98 | content:
99 | application/json:
100 | schema:
101 | $ref: "#/components/schemas/CreateBookmarkResponse"
102 | tags:
103 | - "post"
104 |
105 | /graphql/{pathQueryId}/DeleteBookmark:
106 | post:
107 | operationId: postDeleteBookmark
108 | description: delete Bookmark
109 | responses:
110 | "200":
111 | description: Successful operation
112 | content:
113 | application/json:
114 | schema:
115 | $ref: "#/components/schemas/DeleteBookmarkResponse"
116 | tags:
117 | - "post"
118 |
119 | components:
120 | schemas:
121 | # ---Favorite---
122 | FavoriteTweetResponse:
123 | required:
124 | - "data"
125 | properties:
126 | data:
127 | $ref: "#/components/schemas/FavoriteTweet"
128 | errors:
129 | type: array
130 | items:
131 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
132 |
133 | FavoriteTweet:
134 | properties:
135 | favorite_tweet:
136 | type: string
137 |
138 | # ---Unfavorite---
139 |
140 | UnfavoriteTweetResponse:
141 | required:
142 | - "data"
143 | properties:
144 | data:
145 | $ref: "#/components/schemas/UnfavoriteTweet"
146 | errors:
147 | type: array
148 | items:
149 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
150 |
151 | UnfavoriteTweet:
152 | properties:
153 | unfavorite_tweet:
154 | type: string
155 |
156 | # ---Retweet---
157 |
158 | CreateRetweetResponse:
159 | required:
160 | - "data"
161 | properties:
162 | data:
163 | $ref: "#/components/schemas/CreateRetweetResponseData"
164 | errors:
165 | type: array
166 | items:
167 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
168 |
169 | CreateRetweetResponseData:
170 | properties:
171 | create_retweet:
172 | $ref: "#/components/schemas/CreateRetweetResponseResult"
173 |
174 | CreateRetweetResponseResult:
175 | required:
176 | - "retweet_results"
177 | properties:
178 | retweet_results:
179 | $ref: "#/components/schemas/CreateRetweet"
180 |
181 | CreateRetweet:
182 | required:
183 | - "result"
184 | properties:
185 | result:
186 | $ref: "#/components/schemas/Retweet"
187 |
188 | Retweet:
189 | required:
190 | - "rest_id"
191 | - "legacy"
192 | properties:
193 | rest_id:
194 | type: string
195 | pattern: "^[0-9]+$"
196 | legacy:
197 | type: object
198 | required:
199 | - "full_text"
200 | properties:
201 | full_text:
202 | type: string
203 |
204 | # ---DeleteRetweet---
205 |
206 | DeleteRetweetResponse:
207 | required:
208 | - "data"
209 | properties:
210 | data:
211 | $ref: "#/components/schemas/DeleteRetweetResponseData"
212 | errors:
213 | type: array
214 | items:
215 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
216 |
217 | DeleteRetweetResponseData:
218 | properties:
219 | create_retweet:
220 | $ref: "#/components/schemas/DeleteRetweetResponseResult"
221 |
222 | DeleteRetweetResponseResult:
223 | required:
224 | - "source_tweet_results"
225 | properties:
226 | retweet_results:
227 | $ref: "#/components/schemas/DeleteRetweet"
228 |
229 | DeleteRetweet:
230 | required:
231 | - "result"
232 | properties:
233 | result:
234 | type: object
235 | items:
236 | $ref: "#/components/schemas/Retweet"
237 |
238 | # ---Tweet---
239 |
240 | CreateTweetResponse:
241 | required:
242 | - "data"
243 | properties:
244 | data:
245 | $ref: "#/components/schemas/CreateTweetResponseData"
246 | errors:
247 | type: array
248 | items:
249 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
250 |
251 | CreateTweetResponseData:
252 | properties:
253 | create_tweet:
254 | $ref: "#/components/schemas/CreateTweetResponseResult"
255 |
256 | CreateTweetResponseResult:
257 | required:
258 | - "tweet_results"
259 | properties:
260 | tweet_results:
261 | $ref: "#/components/schemas/CreateTweet"
262 |
263 | CreateTweet:
264 | required:
265 | - "result"
266 | properties:
267 | result:
268 | $ref: "./../schemas/tweet.yaml#/components/schemas/Tweet"
269 |
270 | # ---DeleteTweet---
271 |
272 | DeleteTweetResponse:
273 | required:
274 | - "data"
275 | properties:
276 | data:
277 | $ref: "#/components/schemas/DeleteTweetResponseData"
278 | errors:
279 | type: array
280 | items:
281 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
282 |
283 | DeleteTweetResponseData:
284 | properties:
285 | delete_retweet:
286 | $ref: "#/components/schemas/DeleteTweetResponseResult"
287 |
288 | DeleteTweetResponseResult:
289 | required:
290 | - "tweet_results"
291 | properties:
292 | tweet_results:
293 | type: object
294 |
295 | # ---Bookmark---
296 |
297 | CreateBookmarkResponse:
298 | required:
299 | - "data"
300 | properties:
301 | data:
302 | $ref: "#/components/schemas/CreateBookmarkResponseData"
303 | errors:
304 | type: array
305 | items:
306 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
307 |
308 | CreateBookmarkResponseData:
309 | properties:
310 | tweet_bookmark_put:
311 | type: string
312 |
313 | # ---DeleteBookmark---
314 |
315 | DeleteBookmarkResponse:
316 | required:
317 | - "data"
318 | properties:
319 | data:
320 | $ref: "#/components/schemas/DeleteBookmarkResponseData"
321 | errors:
322 | type: array
323 | items:
324 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
325 |
326 | DeleteBookmarkResponseData:
327 | properties:
328 | tweet_bookmark_delete:
329 | type: string
330 |
--------------------------------------------------------------------------------
/src/openapi/paths/profile.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /graphql/{pathQueryId}/ProfileSpotlightsQuery:
8 | get:
9 | operationId: getProfileSpotlightsQuery
10 | description: "get user by screen name"
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/ProfileResponse"
18 |
19 | components:
20 | schemas:
21 | ProfileResponse:
22 | required:
23 | - "data"
24 | properties:
25 | data:
26 | $ref: "#/components/schemas/ProfileResponseData"
27 | errors:
28 | type: array
29 | items:
30 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
31 |
32 | ProfileResponseData:
33 | properties:
34 | user_result_by_screen_name:
35 | $ref: "#/components/schemas/UserResultByScreenName"
36 |
37 | UserResultByScreenName:
38 | required:
39 | - "id"
40 | - "result"
41 | properties:
42 | id:
43 | type: string
44 | pattern: "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$" # base64
45 | result:
46 | $ref: "#/components/schemas/UserResultByScreenNameResult"
47 |
48 | UserResultByScreenNameResult:
49 | required:
50 | - "__typename"
51 | - "id"
52 | - "legacy"
53 | - "profilemodules"
54 | - "rest_id"
55 | properties:
56 | __typename:
57 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # User
58 | id:
59 | type: string
60 | pattern: "^[0-9a-zA-Z=]+$"
61 | legacy:
62 | $ref: "#/components/schemas/UserResultByScreenNameLegacy"
63 | profilemodules:
64 | type: object
65 | additionalProperties: true # todo
66 | rest_id:
67 | type: string
68 | pattern: "^[0-9]+$"
69 | UserResultByScreenNameLegacy:
70 | properties:
71 | blocking:
72 | type: boolean
73 | blocked_by:
74 | type: boolean
75 | protected:
76 | type: boolean
77 | following:
78 | type: boolean
79 | followed_by:
80 | type: boolean
81 | name:
82 | type: string
83 | screen_name:
84 | type: string
85 |
--------------------------------------------------------------------------------
/src/openapi/paths/timeline.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /graphql/{pathQueryId}/HomeTimeline:
8 | get:
9 | operationId: getHomeTimeline
10 | description: get tweet list of timeline
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/TimelineResponse"
18 | tags:
19 | - "tweet"
20 |
21 | /graphql/{pathQueryId}/HomeLatestTimeline:
22 | get:
23 | operationId: getHomeLatestTimeline
24 | description: get tweet list of timeline
25 | responses:
26 | "200":
27 | description: Successful operation
28 | content:
29 | application/json:
30 | schema:
31 | $ref: "#/components/schemas/TimelineResponse"
32 | tags:
33 | - "tweet"
34 |
35 | /graphql/{pathQueryId}/ListLatestTweetsTimeline:
36 | get:
37 | operationId: getListLatestTweetsTimeline
38 | description: get tweet list of timeline
39 | responses:
40 | "200":
41 | description: Successful operation
42 | content:
43 | application/json:
44 | schema:
45 | $ref: "#/components/schemas/ListLatestTweetsTimelineResponse"
46 | tags:
47 | - "tweet"
48 |
49 | /graphql/{pathQueryId}/SearchTimeline:
50 | get:
51 | operationId: getSearchTimeline
52 | description: search tweet list. product:[Top, Latest, People, Photos, Videos]
53 | responses:
54 | "200":
55 | description: Successful operation
56 | content:
57 | application/json:
58 | schema:
59 | $ref: "#/components/schemas/SearchTimelineResponse"
60 | tags:
61 | - "tweet"
62 |
63 | /graphql/{pathQueryId}/CommunityTweetsTimeline:
64 | get:
65 | operationId: getCommunityTweetsTimeline
66 | description: get tweet list of community. rankingMode:[Recency, Relevance]
67 | responses:
68 | "200":
69 | description: Successful operation
70 | content:
71 | application/json:
72 | schema:
73 | $ref: "#/components/schemas/CommunityTweetsTimelineResponse"
74 | tags:
75 | - "tweet"
76 |
77 | /graphql/{pathQueryId}/CommunityMediaTimeline:
78 | get:
79 | operationId: getCommunityMediaTimeline
80 | description: get media list of community
81 | responses:
82 | "200":
83 | description: Successful operation
84 | content:
85 | application/json:
86 | schema:
87 | $ref: "#/components/schemas/CommunityMediaTimelineResponse"
88 | tags:
89 | - "tweet"
90 |
91 | /graphql/{pathQueryId}/CommunityAboutTimeline:
92 | get:
93 | operationId: getCommunityAboutTimeline
94 | description: get about of community
95 | responses:
96 | "200":
97 | description: Successful operation
98 | content:
99 | application/json:
100 | schema:
101 | $ref: "#/components/schemas/CommunityAboutTimelineResponse"
102 | tags:
103 | - "tweet"
104 |
105 | /graphql/{pathQueryId}/NotificationsTimeline:
106 | get:
107 | operationId: getNotificationsTimeline
108 | description: get notification list. timeline_type:[All, Verified, Mentions]
109 | responses:
110 | "200":
111 | description: Successful operation
112 | content:
113 | application/json:
114 | schema:
115 | $ref: "#/components/schemas/NotificationsTimelineResponse"
116 | tags:
117 | - "tweet"
118 |
119 | components:
120 | schemas:
121 | TimelineResponse:
122 | required:
123 | - "data"
124 | properties:
125 | data:
126 | $ref: "#/components/schemas/HomeTimelineResponseData"
127 | errors:
128 | type: array
129 | items:
130 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
131 |
132 | HomeTimelineResponseData:
133 | required:
134 | - "home"
135 | properties:
136 | home:
137 | $ref: "#/components/schemas/HomeTimelineHome"
138 |
139 | HomeTimelineHome:
140 | required:
141 | - "home_timeline_urt"
142 | properties:
143 | home_timeline_urt:
144 | $ref: "./../schemas/timeline.yaml#/components/schemas/Timeline"
145 |
146 | ListLatestTweetsTimelineResponse:
147 | required:
148 | - "data"
149 | properties:
150 | data:
151 | $ref: "#/components/schemas/ListTweetsTimelineData"
152 | errors:
153 | type: array
154 | items:
155 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
156 |
157 | ListTweetsTimelineData:
158 | required:
159 | - "list"
160 | properties:
161 | list:
162 | $ref: "#/components/schemas/ListTweetsTimelineList"
163 |
164 | ListTweetsTimelineList:
165 | required:
166 | - "tweets_timeline"
167 | properties:
168 | tweets_timeline:
169 | $ref: "#/components/schemas/TimelineResult"
170 |
171 | SearchTimelineResponse:
172 | required:
173 | - "data"
174 | properties:
175 | data:
176 | $ref: "#/components/schemas/SearchTimelineData"
177 | errors:
178 | type: array
179 | items:
180 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
181 |
182 | SearchTimelineData:
183 | required:
184 | - "search_by_raw_query"
185 | properties:
186 | search_by_raw_query:
187 | $ref: "#/components/schemas/SearchByRawQuery"
188 |
189 | SearchByRawQuery:
190 | required:
191 | - "search_timeline"
192 | properties:
193 | search_timeline:
194 | $ref: "#/components/schemas/SearchTimeline"
195 |
196 | SearchTimeline:
197 | required:
198 | - "timeline"
199 | properties:
200 | timeline:
201 | $ref: "./../schemas/timeline.yaml#/components/schemas/Timeline"
202 |
203 | CommunityTweetsTimelineResponse:
204 | required:
205 | - "data"
206 | properties:
207 | data:
208 | $ref: "#/components/schemas/RankedCommunityTweetData"
209 | errors:
210 | type: array
211 | items:
212 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
213 |
214 | RankedCommunityTweetData:
215 | required:
216 | - "communityResults"
217 | properties:
218 | communityResults:
219 | $ref: "#/components/schemas/RankedCommunityResults"
220 |
221 | RankedCommunityResults:
222 | required:
223 | - "result"
224 | properties:
225 | result:
226 | $ref: "#/components/schemas/RankedCommunityResult"
227 |
228 | RankedCommunityResult:
229 | required:
230 | - "__typename"
231 | - "ranked_community_timeline"
232 | properties:
233 | __typename:
234 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # Community
235 | ranked_community_timeline:
236 | $ref: "./../schemas/timeline.yaml#/components/schemas/TimelineResult"
237 |
238 | CommunityMediaTimelineResponse:
239 | required:
240 | - "data"
241 | properties:
242 | data:
243 | $ref: "#/components/schemas/MediaCommunityTweetData"
244 | errors:
245 | type: array
246 | items:
247 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
248 |
249 | MediaCommunityTweetData:
250 | required:
251 | - "__typename"
252 | - "communityResults"
253 | properties:
254 | communityResults:
255 | $ref: "#/components/schemas/MediaCommunityResults"
256 |
257 | MediaCommunityResults:
258 | required:
259 | - "result"
260 | properties:
261 | result:
262 | $ref: "#/components/schemas/MediaCommunityResult"
263 |
264 | MediaCommunityResult:
265 | required:
266 | - "__typename"
267 | - "community_media_timeline"
268 | properties:
269 | __typename:
270 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # Community
271 | community_media_timeline:
272 | $ref: "./../schemas/timeline.yaml#/components/schemas/TimelineResult"
273 |
274 | CommunityAboutTimelineResponse:
275 | required:
276 | - "data"
277 | properties:
278 | data:
279 | $ref: "#/components/schemas/AboutCommunityTweetData"
280 | errors:
281 | type: array
282 | items:
283 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
284 |
285 | AboutCommunityTweetData:
286 | required:
287 | - "communityResults"
288 | properties:
289 | communityResults:
290 | $ref: "#/components/schemas/AboutCommunityResults"
291 |
292 | AboutCommunityResults:
293 | required:
294 | - "result"
295 | properties:
296 | result:
297 | $ref: "#/components/schemas/AboutCommunityResult"
298 |
299 | AboutCommunityResult:
300 | required:
301 | - "__typename"
302 | - "about_timeline"
303 | properties:
304 | __typename:
305 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # Community
306 | about_timeline:
307 | $ref: "./../schemas/timeline.yaml#/components/schemas/TimelineResult"
308 |
309 | NotificationsTimelineResponse:
310 | required:
311 | - "data"
312 | properties:
313 | data:
314 | $ref: "#/components/schemas/NotificationsTimelineData"
315 | errors:
316 | type: array
317 | items:
318 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
319 |
320 | NotificationsTimelineData:
321 | required:
322 | - "viewer_v2"
323 | properties:
324 | viewer_v2:
325 | $ref: "#/components/schemas/NotificationsViewerV2"
326 |
327 | NotificationsViewerV2:
328 | required:
329 | - "user_results"
330 | properties:
331 | user_results:
332 | $ref: "#/components/schemas/NotificationsUserResults"
333 |
334 | NotificationsUserResults:
335 | required:
336 | - "result"
337 | properties:
338 | result:
339 | $ref: "#/components/schemas/NotificationsResult"
340 |
341 | NotificationsResult:
342 | required:
343 | - "__typename"
344 | - "rest_id"
345 | - "notification_timeline"
346 | properties:
347 | __typename:
348 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # User
349 | rest_id:
350 | type: string
351 | pattern: "^[0-9]+$"
352 | notification_timeline:
353 | $ref: "./../schemas/timeline.yaml#/components/schemas/TimelineResult"
354 |
--------------------------------------------------------------------------------
/src/openapi/paths/tweet.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /graphql/{pathQueryId}/TweetDetail:
8 | get:
9 | operationId: getTweetDetail
10 | description: get TweetDetail
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/TweetDetailResponse"
18 | tags:
19 | - "tweet"
20 |
21 | /graphql/{pathQueryId}/TweetResultByRestId:
22 | get:
23 | operationId: getTweetResultByRestId
24 | description: get TweetResultByRestId
25 | responses:
26 | "200":
27 | description: Successful operation
28 | content:
29 | application/json:
30 | schema:
31 | $ref: "#/components/schemas/TweetResultByRestIdResponse"
32 | # tags:
33 | # - "tweet"
34 |
35 | /graphql/{pathQueryId}/Favoriters:
36 | get:
37 | operationId: getFavoriters
38 | description: get tweet favoriters
39 | responses:
40 | "200":
41 | description: Successful operation
42 | content:
43 | application/json:
44 | schema:
45 | $ref: "#/components/schemas/TweetFavoritersResponse"
46 | tags:
47 | - "user-list"
48 |
49 | /graphql/{pathQueryId}/Retweeters:
50 | get:
51 | operationId: getRetweeters
52 | description: get tweet retweeters
53 | responses:
54 | "200":
55 | description: Successful operation
56 | content:
57 | application/json:
58 | schema:
59 | $ref: "#/components/schemas/TweetRetweetersResponse"
60 | tags:
61 | - "user-list"
62 |
63 | components:
64 | schemas:
65 | TweetDetailResponse:
66 | required:
67 | - "data"
68 | properties:
69 | data:
70 | $ref: "#/components/schemas/TweetDetailResponseData"
71 | errors:
72 | type: array
73 | items:
74 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
75 |
76 | TweetDetailResponseData:
77 | properties:
78 | threaded_conversation_with_injections_v2:
79 | $ref: "./../schemas/timeline.yaml#/components/schemas/Timeline"
80 |
81 | TweetResultByRestIdResponse:
82 | required:
83 | - "data"
84 | properties:
85 | data:
86 | $ref: "#/components/schemas/TweetResultByRestIdData"
87 | errors:
88 | type: array
89 | items:
90 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
91 |
92 | TweetResultByRestIdData:
93 | properties:
94 | tweetResult:
95 | $ref: "./../schemas/content.yaml#/components/schemas/ItemResult"
96 |
97 | TweetFavoritersResponse:
98 | required:
99 | - "data"
100 | properties:
101 | data:
102 | $ref: "#/components/schemas/TweetFavoritersResponseData"
103 | errors:
104 | type: array
105 | items:
106 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
107 |
108 | TweetFavoritersResponseData:
109 | properties:
110 | favoriters_timeline:
111 | $ref: "./../schemas/timeline.yaml#/components/schemas/TimelineResult"
112 |
113 | TweetRetweetersResponse:
114 | required:
115 | - "data"
116 | properties:
117 | data:
118 | $ref: "#/components/schemas/TweetRetweetersResponseData"
119 | errors:
120 | type: array
121 | items:
122 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
123 |
124 | TweetRetweetersResponseData:
125 | properties:
126 | retweeters_timeline:
127 | $ref: "./../schemas/timeline.yaml#/components/schemas/TimelineResult"
128 |
--------------------------------------------------------------------------------
/src/openapi/paths/user.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /graphql/{pathQueryId}/UserByScreenName:
8 | get:
9 | operationId: getUserByScreenName
10 | description: "get user by screen name"
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/UserResponse"
18 | tags:
19 | - "user"
20 |
21 | /graphql/{pathQueryId}/UserByRestId:
22 | get:
23 | operationId: getUserByRestId
24 | description: "get user by rest id"
25 | responses:
26 | "200":
27 | description: Successful operation
28 | content:
29 | application/json:
30 | schema:
31 | $ref: "#/components/schemas/UserResponse"
32 | tags:
33 | - "user"
34 |
35 | /graphql/{pathQueryId}/UsersByRestIds:
36 | get:
37 | operationId: getUsersByRestIds
38 | description: "get users by rest ids"
39 | responses:
40 | "200":
41 | description: Successful operation
42 | content:
43 | application/json:
44 | schema:
45 | $ref: "#/components/schemas/UsersResponse"
46 | tags:
47 | - "users"
48 |
49 | components:
50 | schemas:
51 | UserResponse:
52 | required:
53 | - "data"
54 | properties:
55 | data:
56 | $ref: "#/components/schemas/UserResponseData"
57 | errors:
58 | type: array
59 | items:
60 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
61 |
62 | UserResponseData:
63 | properties:
64 | user:
65 | $ref: "./../schemas/user.yaml#/components/schemas/UserResults"
66 |
67 | UsersResponse:
68 | required:
69 | - "data"
70 | properties:
71 | data:
72 | $ref: "#/components/schemas/UsersResponseData"
73 | errors:
74 | type: array
75 | items:
76 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
77 |
78 | UsersResponseData:
79 | properties:
80 | users:
81 | type: array
82 | items:
83 | $ref: "./../schemas/user.yaml#/components/schemas/UserResults"
84 |
--------------------------------------------------------------------------------
/src/openapi/paths/usertweets.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /graphql/{pathQueryId}/UserTweets:
8 | get:
9 | operationId: getUserTweets
10 | description: "get user tweets"
11 | responses:
12 | "200":
13 | description: Successful operation
14 | content:
15 | application/json:
16 | schema:
17 | $ref: "#/components/schemas/UserTweetsResponse"
18 | tags:
19 | - "tweet"
20 |
21 | /graphql/{pathQueryId}/UserTweetsAndReplies:
22 | get:
23 | operationId: getUserTweetsAndReplies
24 | description: "get user replies tweets"
25 | responses:
26 | "200":
27 | description: Successful operation
28 | content:
29 | application/json:
30 | schema:
31 | $ref: "#/components/schemas/UserTweetsResponse"
32 | tags:
33 | - "tweet"
34 |
35 | /graphql/{pathQueryId}/UserHighlightsTweets:
36 | get:
37 | operationId: getUserHighlightsTweets
38 | description: "get user highlights tweets"
39 | responses:
40 | "200":
41 | description: Successful operation
42 | content:
43 | application/json:
44 | schema:
45 | $ref: "#/components/schemas/UserHighlightsTweetsResponse"
46 | tags:
47 | - "tweet"
48 |
49 | /graphql/{pathQueryId}/UserMedia:
50 | get:
51 | operationId: getUserMedia
52 | description: "get user media tweets"
53 | responses:
54 | "200":
55 | description: Successful operation
56 | content:
57 | application/json:
58 | schema:
59 | $ref: "#/components/schemas/UserTweetsResponse"
60 | tags:
61 | - "tweet"
62 |
63 | /graphql/{pathQueryId}/Likes:
64 | get:
65 | operationId: getLikes
66 | description: "get user likes tweets"
67 | responses:
68 | "200":
69 | description: Successful operation
70 | content:
71 | application/json:
72 | schema:
73 | $ref: "#/components/schemas/UserTweetsResponse"
74 | tags:
75 | - "tweet"
76 |
77 | components:
78 | schemas:
79 | UserTweetsResponse:
80 | required:
81 | - "data"
82 | properties:
83 | data:
84 | $ref: "#/components/schemas/UserTweetsData"
85 | errors:
86 | type: array
87 | items:
88 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
89 |
90 | UserTweetsData:
91 | properties:
92 | user:
93 | $ref: "#/components/schemas/UserTweetsUser"
94 |
95 | UserTweetsUser:
96 | required:
97 | - "result"
98 | properties:
99 | result:
100 | $ref: "#/components/schemas/UserTweetsResultV1"
101 |
102 | UserTweetsResultV1:
103 | required:
104 | - "__typename"
105 | - "timeline"
106 | properties:
107 | __typename:
108 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # User
109 | timeline:
110 | $ref: "./../schemas/timeline.yaml#/components/schemas/TimelineResult"
111 |
112 | UserTweetsResultV2:
113 | required:
114 | - "__typename"
115 | - "timeline_v2"
116 | properties:
117 | __typename:
118 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # User
119 | timeline_v2:
120 | $ref: "./../schemas/timeline.yaml#/components/schemas/TimelineResult"
121 |
122 | UserHighlightsTweetsResponse:
123 | required:
124 | - "data"
125 | properties:
126 | data:
127 | $ref: "#/components/schemas/UserHighlightsTweetsData"
128 | errors:
129 | type: array
130 | items:
131 | $ref: "./../response/error.yaml#/components/schemas/ErrorResponse"
132 |
133 | UserHighlightsTweetsData:
134 | properties:
135 | user:
136 | $ref: "#/components/schemas/UserHighlightsTweetsUser"
137 |
138 | UserHighlightsTweetsUser:
139 | required:
140 | - "result"
141 | properties:
142 | result:
143 | $ref: "#/components/schemas/UserHighlightsTweetsResult"
144 |
145 | UserHighlightsTweetsResult:
146 | required:
147 | - "timeline"
148 | - "__typename"
149 | properties:
150 | timeline:
151 | $ref: "#/components/schemas/UserHighlightsTweetsTimeline"
152 | __typename:
153 | $ref: "./../schemas/typename.yaml#/components/schemas/TypeName" # User
154 |
155 | UserHighlightsTweetsTimeline:
156 | required:
157 | - "timeline"
158 | properties:
159 | timeline:
160 | $ref: "./../schemas/timeline.yaml#/components/schemas/Timeline"
161 |
--------------------------------------------------------------------------------
/src/openapi/paths/v1.1-get.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /1.1/friends/following/list.json:
8 | get:
9 | operationId: getFriendsFollowingList
10 | description: get friends following list
11 | responses:
12 | "200":
13 | description: Successful operation
14 | tags:
15 | - "v1.1-get"
16 |
17 | /1.1/search/typeahead.json:
18 | get:
19 | operationId: getSearchTypeahead
20 | description: get search typeahead
21 | responses:
22 | "200":
23 | description: Successful operation
24 | tags:
25 | - "v1.1-get"
26 |
--------------------------------------------------------------------------------
/src/openapi/paths/v1.1-post.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /1.1/friendships/create.json:
8 | post:
9 | operationId: postCreateFriendships
10 | description: post create friendships
11 | responses:
12 | "200":
13 | description: Successful operation
14 | tags:
15 | - "v1.1-post"
16 |
17 | /1.1/friendships/destroy.json:
18 | post:
19 | operationId: postDestroyFriendships
20 | description: post destroy friendships
21 | responses:
22 | "200":
23 | description: Successful operation
24 | tags:
25 | - "v1.1-post"
26 |
--------------------------------------------------------------------------------
/src/openapi/paths/v2.0-get.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths:
7 | /2/search/adaptive.json:
8 | get:
9 | operationId: getSearchAdaptive
10 | description: get search adaptive
11 | responses:
12 | "200":
13 | description: Successful operation
14 | tags:
15 | - "v2.0-get"
16 |
--------------------------------------------------------------------------------
/src/openapi/resources/parameters.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths: {}
7 |
8 | components:
9 | parameters:
10 | queryId:
11 | name: queryId
12 | in: path
13 | required: true
14 | schema:
15 | type: string
16 |
--------------------------------------------------------------------------------
/src/openapi/response/error.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths: {}
7 | components:
8 | schemas:
9 | ErrorResponse:
10 | type: object
11 | required:
12 | - message
13 | - locations
14 | - path
15 | - extensions
16 | - code
17 | - kind
18 | - name
19 | - source
20 | - tracing
21 | properties:
22 | message:
23 | type: string
24 | locations:
25 | type: array
26 | items:
27 | $ref: "#/components/schemas/Location"
28 | path:
29 | type: array
30 | items:
31 | oneOf:
32 | - type: string
33 | - type: integer
34 | extensions:
35 | $ref: "#/components/schemas/ErrorExtensions"
36 | code:
37 | type: integer
38 | kind:
39 | type: string
40 | name:
41 | type: string
42 | source:
43 | type: string
44 | retry_after:
45 | type: integer
46 | tracing:
47 | $ref: "#/components/schemas/Tracing"
48 |
49 | Location:
50 | type: object
51 | required:
52 | - line
53 | - column
54 | properties:
55 | line:
56 | type: integer
57 | column:
58 | type: integer
59 |
60 | ErrorExtensions:
61 | type: object
62 | required:
63 | - name
64 | - source
65 | - code
66 | - kind
67 | - tracing
68 | properties:
69 | name:
70 | type: string
71 | source:
72 | type: string
73 | retry_after:
74 | type: integer
75 | code:
76 | type: integer
77 | kind:
78 | type: string
79 | tracing:
80 | $ref: "#/components/schemas/Tracing"
81 |
82 | Tracing:
83 | type: object
84 | required:
85 | - trace_id
86 | properties:
87 | trace_id:
88 | type: string
89 | pattern: "^[0-9a-f]{16}$"
90 |
--------------------------------------------------------------------------------
/src/openapi/schemas/content.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths: {}
7 | components:
8 | schemas:
9 | ContentUnion:
10 | oneOf:
11 | - $ref: "#/components/schemas/TimelineTimelineItem"
12 | - $ref: "#/components/schemas/TimelineTimelineModule"
13 | - $ref: "#/components/schemas/TimelineTimelineCursor"
14 | discriminator:
15 | propertyName: entryType
16 | mapping": # deprecated
17 | TimelineTimelineItem: "#/components/schemas/TimelineTimelineItem"
18 | TimelineTimelineModule: "#/components/schemas/TimelineTimelineModule"
19 | TimelineTimelineCursor: "#/components/schemas/TimelineTimelineCursor"
20 |
21 | ContentEntryType:
22 | type: string
23 | enum:
24 | [TimelineTimelineItem, TimelineTimelineCursor, TimelineTimelineModule]
25 |
26 | CursorType:
27 | type: string
28 | enum: [Top, Bottom, ShowMore, ShowMoreThreads, Gap, ShowMoreThreadsPrompt] # Gap???
29 |
30 | TimelineTimelineItem:
31 | required:
32 | - "__typename"
33 | - "entryType"
34 | - "itemContent"
35 | properties:
36 | __typename:
37 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineTimelineItem
38 | entryType:
39 | $ref: "#/components/schemas/ContentEntryType" # TimelineTimelineItem
40 | itemContent:
41 | $ref: "#/components/schemas/ItemContentUnion"
42 | clientEventInfo:
43 | $ref: "#/components/schemas/ClientEventInfo"
44 | feedbackInfo:
45 | type: object
46 | additionalProperties: true # todo
47 |
48 | TimelineTimelineModule:
49 | required:
50 | - "__typename"
51 | - "entryType"
52 | - "displayType"
53 | # - "items"
54 | # - "clientEventInfo"
55 | properties:
56 | __typename:
57 | type: string
58 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineTimelineModule
59 | entryType:
60 | $ref: "#/components/schemas/ContentEntryType" # TimelineTimelineCursor
61 | displayType:
62 | $ref: "#/components/schemas/DisplayType"
63 | items:
64 | type: array
65 | items:
66 | $ref: "#/components/schemas/ModuleItem"
67 | footer:
68 | type: object
69 | additionalProperties: true # todo
70 | header:
71 | type: object
72 | additionalProperties: true # todo
73 | clientEventInfo:
74 | $ref: "#/components/schemas/ClientEventInfo"
75 | metadata:
76 | type: object
77 | additionalProperties: true # todo
78 | feedbackInfo:
79 | $ref: "#/components/schemas/FeedbackInfo"
80 |
81 | TimelineTimelineCursor:
82 | required:
83 | - "__typename"
84 | - "cursorType"
85 | - "value"
86 | properties:
87 | __typename:
88 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineTimelineCursor
89 | entryType:
90 | $ref: "#/components/schemas/ContentEntryType" # null | TimelineTimelineCursor
91 | itemType:
92 | $ref: "#/components/schemas/ContentEntryType" # null | TimelineTimelineCursor
93 | cursorType:
94 | $ref: "#/components/schemas/CursorType"
95 | value:
96 | type: string
97 | stopOnEmptyResponse:
98 | type: boolean
99 | displayTreatment:
100 | $ref: "#/components/schemas/DisplayTreatment"
101 |
102 | DisplayTreatment:
103 | type: object
104 | required:
105 | - "actionText"
106 | properties:
107 | actionText:
108 | type: string
109 | labelText:
110 | type: string
111 |
112 | # ================= Module =================
113 |
114 | DisplayType:
115 | type: string
116 | enum:
117 | - Vertical
118 | - VerticalConversation
119 | - VerticalGrid
120 | - Carousel
121 |
122 | ModuleItem:
123 | required:
124 | - "entryId"
125 | - "item"
126 | properties:
127 | entryId:
128 | type: string
129 | pattern: "^(([a-zA-Z]+|[0-9]+|[0-9a-f]+)(-|$))+"
130 | item:
131 | $ref: "#/components/schemas/ModuleEntry"
132 | dispensable:
133 | type: boolean
134 |
135 | ModuleEntry:
136 | required:
137 | - "itemContent"
138 | properties:
139 | clientEventInfo:
140 | $ref: "#/components/schemas/ClientEventInfo"
141 | itemContent:
142 | $ref: "#/components/schemas/ItemContentUnion"
143 | feedbackInfo:
144 | $ref: "#/components/schemas/FeedbackInfo"
145 |
146 | FeedbackInfo:
147 | required:
148 | - "feedbackType"
149 | properties:
150 | feedbackKeys:
151 | type: array
152 | items:
153 | type: string
154 | clientEventInfo:
155 | $ref: "#/components/schemas/ClientEventInfo"
156 |
157 | # ================= ContentItem =================
158 |
159 | ItemContentUnion:
160 | oneOf:
161 | - $ref: "#/components/schemas/TimelineTweet"
162 | - $ref: "#/components/schemas/TimelineTimelineCursor"
163 | - $ref: "#/components/schemas/TimelineUser"
164 | - $ref: "#/components/schemas/TimelinePrompt"
165 | - $ref: "#/components/schemas/TimelineMessagePrompt"
166 | - $ref: "#/components/schemas/TimelineCommunity"
167 | - $ref: "#/components/schemas/TimelineTombstone"
168 | - $ref: "#/components/schemas/TimelineTrend"
169 | - $ref: "#/components/schemas/TimelineNotification"
170 |
171 | discriminator:
172 | propertyName: __typename
173 | mapping": # deprecated
174 | TimelineTweet: "#/components/schemas/TimelineTweet"
175 | TimelineTimelineCursor: "#/components/schemas/TimelineTimelineCursor"
176 | TimelineUser: "#/components/schemas/TimelineUser"
177 | TimelinePrompt: "#/components/schemas/TimelinePrompt"
178 | TimelineMessagePrompt: "#/components/schemas/TimelineMessagePrompt"
179 | TimelineCommunity: "#/components/schemas/TimelineCommunity"
180 | TimelineTombstone: "#/components/schemas/TimelineTombstone"
181 | TimelineTrend: "#/components/schemas/TimelineTrend"
182 | TimelineNotification: "#/components/schemas/TimelineNotification"
183 |
184 | ContentItemType:
185 | type: string
186 | enum:
187 | [
188 | TimelineTweet,
189 | TimelineTimelineCursor,
190 | TimelineUser,
191 | TimelinePrompt,
192 | TimelineMessagePrompt,
193 | TimelineCommunity,
194 | TimelineTombstone,
195 | TimelineTrend,
196 | TimelineNotification,
197 | ]
198 |
199 | TimelineTweet:
200 | required:
201 | - "__typename"
202 | - "itemType"
203 | - "tweetDisplayType"
204 | - "tweet_results"
205 | properties:
206 | __typename:
207 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineTweet
208 | itemType:
209 | $ref: "#/components/schemas/ContentItemType" # TimelineTweet
210 | tweetDisplayType:
211 | type: string
212 | enum: [Tweet, SelfThread, MediaGrid, CondensedTweet]
213 | tweet_results:
214 | $ref: "#/components/schemas/ItemResult"
215 | socialContext:
216 | $ref: "#/components/schemas/SocialContextUnion"
217 | promotedMetadata:
218 | type: object
219 | additionalProperties: true # todo
220 | highlights:
221 | $ref: "#/components/schemas/Highlight"
222 | hasModeratedReplies:
223 | type: boolean
224 |
225 | TimelineUser:
226 | required:
227 | - "__typename"
228 | - "itemType"
229 | - "userDisplayType"
230 | - "user_results"
231 | properties:
232 | __typename:
233 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineUser
234 | itemType:
235 | $ref: "#/components/schemas/ContentItemType" # TimelineUser
236 | socialContext:
237 | $ref: "#/components/schemas/SocialContextUnion"
238 | userDisplayType:
239 | type: string
240 | enum: [User, UserDetailed, SubscribableUser, UserConcise]
241 | user_results:
242 | $ref: "./user.yaml#/components/schemas/UserResults"
243 |
244 | ItemResult:
245 | properties:
246 | __typename:
247 | $ref: "./typename.yaml#/components/schemas/TypeName" # null | TimelineTweet | TweetUnavailable
248 | result:
249 | $ref: "./tweet.yaml#/components/schemas/TweetUnion"
250 |
251 | SocialContextUnion:
252 | oneOf:
253 | - $ref: "#/components/schemas/TimelineGeneralContext"
254 | - $ref: "#/components/schemas/TimelineTopicContext"
255 | discriminator:
256 | propertyName: type
257 | mapping": # deprecated
258 | TimelineGeneralContext: "#/components/schemas/TimelineGeneralContext"
259 | TimelineTopicContext: "#/components/schemas/TimelineTopicContext"
260 |
261 | SocialContextUnionType:
262 | type: string
263 | enum:
264 | - TimelineGeneralContext
265 | - TimelineTopicContext
266 |
267 | TimelineGeneralContext:
268 | type: object
269 | properties:
270 | type:
271 | $ref: "#/components/schemas/SocialContextUnionType"
272 | contextType:
273 | type: string # enum
274 | enum:
275 | - "Follow"
276 | - "Pin"
277 | - "Like"
278 | - "Location"
279 | - "Sparkle"
280 | - "Conversation"
281 | - "List"
282 | - "Community"
283 | - "Facepile"
284 | text:
285 | type: string
286 | landingUrl:
287 | $ref: "#/components/schemas/SocialContextLandingUrl"
288 | contextImageUrls:
289 | type: array
290 | items:
291 | type: string
292 | format: uri
293 |
294 | TimelineTopicContext:
295 | type: object
296 | properties:
297 | type:
298 | $ref: "#/components/schemas/SocialContextUnionType"
299 | topic:
300 | $ref: "#/components/schemas/TopicContext"
301 | functionalityType:
302 | type: string
303 | enum: ["Basic"]
304 |
305 | TopicContext:
306 | type: object
307 | properties:
308 | id:
309 | type: string
310 | topic_id:
311 | type: string
312 | name:
313 | type: string
314 | description:
315 | type: string
316 | icon_url:
317 | type: string
318 | format: uri
319 | following:
320 | type: boolean
321 | not_interested:
322 | type: boolean
323 |
324 | SocialContextLandingUrl:
325 | type: object
326 | properties:
327 | urlType:
328 | type: string # enum
329 | enum: ["DeepLink", "UrtEndpoint", "ExternalUrl"]
330 | url:
331 | type: string # twitter://user?id=900282258736545792
332 | format: uri
333 | urtEndpointOptions:
334 | $ref: "#/components/schemas/UrtEndpointOptions"
335 |
336 | UrtEndpointOptions:
337 | type: object
338 | required:
339 | - "title"
340 | - "requestParams"
341 | properties:
342 | title:
343 | type: string
344 | requestParams:
345 | type: array
346 | items:
347 | $ref: "#/components/schemas/UrtEndpointRequestParams"
348 |
349 | UrtEndpointRequestParams:
350 | type: object
351 | required:
352 | - "key"
353 | - "value"
354 | properties:
355 | key:
356 | type: string
357 | value:
358 | type: string
359 |
360 | Highlight:
361 | type: object
362 | required:
363 | - "textHighlights"
364 | properties:
365 | textHighlights:
366 | type: array
367 | items:
368 | $ref: "#/components/schemas/TextHighlight"
369 |
370 | TextHighlight:
371 | type: object
372 | required:
373 | - "startIndex"
374 | - "endIndex"
375 | properties:
376 | startIndex:
377 | type: integer
378 | endIndex:
379 | type: integer
380 |
381 | TimelinePrompt:
382 | properties:
383 | __typename:
384 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelinePrompt
385 | additionalProperties: true # todo
386 |
387 | TimelineMessagePrompt:
388 | properties:
389 | __typename:
390 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineMessagePrompt
391 | additionalProperties: true # todo
392 |
393 | TimelineCommunity:
394 | properties:
395 | __typename:
396 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineCommunity
397 | additionalProperties: true # todo
398 |
399 | ClientEventInfo:
400 | properties:
401 | component:
402 | # enum half_cover
403 | type: string
404 | element:
405 | type: string
406 | # august-2023-privacy-prompt-candidate
407 | # pattern: "^(([a-zA-Z]+|[0-9]+|[0-9a-f]+)(-|$))+"
408 | # pattern: "^(january|february|march|april|may|june|july|august|september|october|november|december)-[0-9]{4}-([a-z]-)+[a-z]+$"
409 | details:
410 | type: object
411 | additionalProperties: true # todo
412 |
413 | TimelineTombstone:
414 | properties:
415 | __typename:
416 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineTombstone
417 | itemType:
418 | $ref: "#/components/schemas/ContentItemType" # TimelineTombstone
419 | tombstoneDisplayType:
420 | type: string
421 | enum: [Inline]
422 | tombstoneInfo:
423 | $ref: "#/components/schemas/TombstoneInfo"
424 |
425 | TombstoneInfo:
426 | type: object
427 | properties:
428 | text:
429 | type: string
430 | richText:
431 | $ref: "#/components/schemas/TombstoneRichText"
432 |
433 | TombstoneRichText:
434 | type: object
435 | properties:
436 | rtl:
437 | type: boolean
438 | text:
439 | type: string
440 | entities:
441 | type: array
442 | items:
443 | $ref: "#/components/schemas/TombstoneEntity"
444 |
445 | TombstoneEntity:
446 | type: object
447 | properties:
448 | fromIndex:
449 | type: integer
450 | toIndex:
451 | type: integer
452 | ref:
453 | $ref: "#/components/schemas/TombstoneRef"
454 |
455 | TombstoneRef:
456 | type: object
457 | properties:
458 | type:
459 | type: string
460 | enum: [TimelineUrl]
461 | url:
462 | type: string
463 | format: uri
464 | urlType:
465 | type: string
466 | enum: [ExternalUrl]
467 |
468 | TimelineTrend:
469 | required:
470 | - "__typename"
471 | - "name"
472 | - "trend_url"
473 | - "trend_metadata"
474 | properties:
475 | __typename:
476 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineTrend
477 | itemType:
478 | $ref: "#/components/schemas/ContentItemType" # TimelineTrend
479 | social_context:
480 | $ref: "#/components/schemas/SocialContextUnion"
481 | is_ai_trend:
482 | type: boolean
483 | name:
484 | type: string
485 | trend_url:
486 | $ref: "#/components/schemas/SocialContextLandingUrl"
487 | trend_metadata:
488 | $ref: "#/components/schemas/TrendMetadata"
489 | thumbnail_image:
490 | $ref: "#/components/schemas/ThumbnailImage"
491 | images:
492 | type: array
493 | items:
494 | $ref: "#/components/schemas/TrendImage"
495 |
496 | TrendMetadata:
497 | type: object
498 | properties:
499 | url:
500 | $ref: "#/components/schemas/SocialContextLandingUrl"
501 |
502 | ThumbnailImage:
503 | type: object
504 | properties:
505 | original_img_url:
506 | type: string
507 | format: uri
508 | original_img_width:
509 | type: integer
510 | original_img_height:
511 | type: integer
512 |
513 | TrendImage:
514 | type: object
515 | properties:
516 | url:
517 | type: string
518 | format: uri
519 |
520 | TimelineNotification:
521 | required:
522 | - "__typename"
523 | - "itemType"
524 | - "id"
525 | - "notification_icon"
526 | - "rich_message"
527 | - "notification_url"
528 | - "template"
529 | - "timestamp_ms"
530 | properties:
531 | __typename:
532 | $ref: "./typename.yaml#/components/schemas/TypeName" # TimelineNotification
533 | itemType:
534 | $ref: "#/components/schemas/ContentItemType" # TimelineNotification
535 | id:
536 | type: string
537 | notification_icon:
538 | type: string # enum milestone_icon
539 | rich_message:
540 | $ref: "#/components/schemas/RichMessage"
541 | notification_url:
542 | $ref: "#/components/schemas/SocialContextLandingUrl"
543 | template:
544 | $ref: "#/components/schemas/NotificationTemplate"
545 | timestamp_ms:
546 | type: string # 2025-05-05T01:18:21.657Z
547 |
548 | RichMessage:
549 | type: object
550 | properties:
551 | rtl:
552 | type: boolean
553 | text:
554 | type: string
555 |
556 | NotificationTemplate:
557 | type: object
558 | properties:
559 | __typename:
560 | $ref: "./typename.yaml#/components/schemas/TypeName" # NotificationTemplate
561 | target_objects:
562 | type: array
563 | items:
564 | type: object
565 | from_users:
566 | type: array
567 | items:
568 | type: object
569 |
--------------------------------------------------------------------------------
/src/openapi/schemas/general.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths: {}
7 | components:
8 | schemas:
9 | TwitterTimeFormat:
10 | type: string
11 | pattern: "^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (0[1-9]|[12][0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3])(: ?)([0-5][0-9])(: ?)([0-5][0-9]) ([+-][0-9]{4}) ([0-9]{4})$"
12 | example: "Sat Dec 31 23:59:59 +0000 2023" # Wed Sep 20 02: 55: 44 +0000 2023
13 |
--------------------------------------------------------------------------------
/src/openapi/schemas/instruction.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths: {}
7 | components:
8 | schemas:
9 | InstructionUnion:
10 | oneOf:
11 | - $ref: "#/components/schemas/TimelineAddEntries"
12 | - $ref: "#/components/schemas/TimelineAddToModule"
13 | - $ref: "#/components/schemas/TimelineClearCache"
14 | - $ref: "#/components/schemas/TimelinePinEntry"
15 | - $ref: "#/components/schemas/TimelineReplaceEntry"
16 | - $ref: "#/components/schemas/TimelineShowAlert"
17 | - $ref: "#/components/schemas/TimelineTerminateTimeline"
18 | - $ref: "#/components/schemas/TimelineShowCover"
19 | - $ref: "#/components/schemas/TimelineClearEntriesUnreadState"
20 | - $ref: "#/components/schemas/TimelineMarkEntriesUnreadGreaterThanSortIndex"
21 |
22 | discriminator:
23 | propertyName: type
24 | mapping": # deprecated
25 | TimelineAddEntries: "#/components/schemas/TimelineAddEntries"
26 | TimelineAddToModule: "#/components/schemas/TimelineAddToModule"
27 | TimelineClearCache: "#/components/schemas/TimelineClearCache"
28 | TimelinePinEntry: "#/components/schemas/TimelinePinEntry"
29 | TimelineReplaceEntry: "#/components/schemas/TimelineReplaceEntry"
30 | TimelineShowAlert: "#/components/schemas/TimelineShowAlert"
31 | TimelineTerminateTimeline: "#/components/schemas/TimelineTerminateTimeline"
32 | TimelineShowCover: "#/components/schemas/TimelineShowCover"
33 | TimelineClearEntriesUnreadState: "#/components/schemas/TimelineClearEntriesUnreadState"
34 | TimelineMarkEntriesUnreadGreaterThanSortIndex: "#/components/schemas/TimelineMarkEntriesUnreadGreaterThanSortIndex"
35 |
36 | InstructionType:
37 | type: string
38 | enum:
39 | [
40 | TimelineAddEntries,
41 | TimelineAddToModule,
42 | TimelineClearCache,
43 | TimelinePinEntry,
44 | TimelineReplaceEntry,
45 | TimelineShowAlert,
46 | TimelineTerminateTimeline,
47 | TimelineShowCover,
48 | TimelineClearEntriesUnreadState,
49 | TimelineMarkEntriesUnreadGreaterThanSortIndex,
50 | ]
51 |
52 | TimelineAddEntries:
53 | required:
54 | - type
55 | - entries
56 | properties:
57 | type:
58 | $ref: "#/components/schemas/InstructionType" # TimelineAddEntries
59 | entries:
60 | type: array
61 | items:
62 | $ref: "#/components/schemas/TimelineAddEntry"
63 |
64 | TimelineAddToModule:
65 | required:
66 | - type
67 | - moduleItems
68 | - moduleEntryId
69 | properties:
70 | type:
71 | $ref: "#/components/schemas/InstructionType" # TimelineAddToModule
72 | moduleItems:
73 | type: array
74 | items:
75 | $ref: "./content.yaml#/components/schemas/ModuleItem"
76 | moduleEntryId:
77 | type: string
78 | prepend:
79 | type: boolean
80 |
81 | TimelineClearCache:
82 | required:
83 | - type
84 | properties:
85 | type:
86 | $ref: "#/components/schemas/InstructionType" # TimelineClearCache
87 |
88 | TimelinePinEntry:
89 | required:
90 | - type
91 | - entry
92 | properties:
93 | type:
94 | $ref: "#/components/schemas/InstructionType" # TimelinePinEntry
95 | entry:
96 | $ref: "#/components/schemas/TimelineAddEntry"
97 |
98 | TimelineReplaceEntry:
99 | required:
100 | - type
101 | - entry_id_to_replace
102 | - entry
103 | properties:
104 | type:
105 | $ref: "#/components/schemas/InstructionType" # TimelineReplaceEntry
106 | entry_id_to_replace:
107 | type: string
108 | entry:
109 | $ref: "#/components/schemas/TimelineAddEntry"
110 |
111 | TimelineShowAlert:
112 | required:
113 | - type
114 | - usersResults
115 | - richText
116 | properties:
117 | type:
118 | $ref: "#/components/schemas/InstructionType" # TimelineShowAlert
119 | alertType:
120 | type: string
121 | enum: [NewTweets] # which else?
122 | triggerDelayMs:
123 | type: integer
124 | displayDurationMs:
125 | type: integer
126 | usersResults:
127 | type: array
128 | items:
129 | $ref: "./user.yaml#/components/schemas/UserResults"
130 | richText:
131 | type: object
132 | properties:
133 | text:
134 | type: string
135 | entities:
136 | type: array
137 | items:
138 | type: object
139 | additionalProperties: true # todo
140 | iconDisplayInfo:
141 | type: object
142 | additionalProperties: true # todo
143 | colorConfig:
144 | type: object
145 | additionalProperties: true # todo
146 | displayLocation:
147 | type: string
148 | enum: [Top] # which else?
149 |
150 | TimelineTerminateTimeline:
151 | required:
152 | - type
153 | - direction
154 | properties:
155 | type:
156 | $ref: "#/components/schemas/InstructionType" # TimelineTerminateTimeline
157 | direction:
158 | type: string
159 | enum: [Top, Bottom, TopAndBottom]
160 |
161 | TimelineAddEntry:
162 | required:
163 | - "content"
164 | - "entryId"
165 | - "sortIndex"
166 | properties:
167 | content:
168 | $ref: "./content.yaml#/components/schemas/ContentUnion"
169 | entryId:
170 | type: string
171 | pattern: "^(([a-zA-Z]+|[0-9]+|[0-9a-f]+)(-|$))+"
172 | sortIndex:
173 | type: string
174 | pattern: "[0-9]+$"
175 |
176 | TimelineShowCover:
177 | required:
178 | - type
179 | - clientEventInfo
180 | - cover
181 | properties:
182 | type:
183 | $ref: "#/components/schemas/InstructionType" # TimelineShowCover
184 | clientEventInfo:
185 | $ref: "./content.yaml#/components/schemas/ClientEventInfo"
186 | cover:
187 | $ref: "#/components/schemas/TimelineHalfCover"
188 |
189 | TimelineHalfCover:
190 | required:
191 | - type
192 | - halfCoverDisplayType
193 | - primaryText
194 | - primaryCoverCta
195 | - secondaryText
196 | - impressionCallbacks
197 | - dismissible
198 | properties:
199 | type:
200 | type: string
201 | enum: [TimelineHalfCover]
202 | halfCoverDisplayType:
203 | type: string
204 | enum: [Cover]
205 | primaryText:
206 | $ref: "#/components/schemas/Text"
207 | secondaryText:
208 | $ref: "#/components/schemas/Text"
209 | primaryCoverCta:
210 | $ref: "#/components/schemas/CoverCta"
211 | impressionCallbacks:
212 | type: array
213 | items:
214 | $ref: "#/components/schemas/Callback"
215 | dismissible:
216 | type: boolean
217 |
218 | Text:
219 | required:
220 | - text
221 | - entities
222 | properties:
223 | text:
224 | type: string
225 | entities:
226 | type: array
227 | items:
228 | $ref: "#/components/schemas/TextEntity"
229 |
230 | TextEntity:
231 | required:
232 | - fromIndex
233 | - toIndex
234 | - ref
235 | properties:
236 | fromIndex:
237 | type: integer
238 | toIndex:
239 | type: integer
240 | ref:
241 | $ref: "#/components/schemas/TextEntityRef"
242 |
243 | TextEntityRef:
244 | required:
245 | - type
246 | - url
247 | - urlType
248 | properties:
249 | type:
250 | type: string
251 | enum: [TimelineUrl]
252 | url:
253 | type: string
254 | format: uri
255 | urlType:
256 | type: string
257 | enum: [ExternalUrl]
258 |
259 | CoverCta:
260 | required:
261 | - text
262 | - ctaBehavior
263 | - callbacks
264 | - clientEventInfo
265 | properties:
266 | Text:
267 | type: string
268 | ctaBehavior:
269 | $ref: "#/components/schemas/TimelineCoverBehavior"
270 | callbacks:
271 | type: array
272 | items:
273 | $ref: "#/components/schemas/Callback"
274 | clientEventInfo:
275 | $ref: "#/components/schemas/CtaClientEventInfo"
276 | buttonStyle:
277 | type: string
278 | enum: ["Primary"]
279 |
280 | TimelineCoverBehavior:
281 | required:
282 | - type
283 | properties:
284 | type:
285 | type: string
286 | enum: [TimelineCoverBehaviorDismiss, TimelineCoverBehaviorNavigate]
287 | url:
288 | $ref: "#/components/schemas/TimelineCoverBehaviorUrl"
289 |
290 | TimelineCoverBehaviorUrl:
291 | required:
292 | - "url"
293 | - "url_type"
294 | properties:
295 | url:
296 | type: string
297 | format: uri
298 | url_type:
299 | type: string
300 | enum: ["ExternalUrl"]
301 |
302 | Callback:
303 | required:
304 | - endpoint
305 | properties:
306 | endpoint:
307 | type: string
308 | format: uri
309 | # pattern: '^/1\.1/[a-z]+/[a-z]+\.json\?[a-z_]+=[a-z0-9-]+(&[a-z_]+=[a-z0-9-]+)+?$' #/1.1/onboarding/fatigue.json?{params}
310 |
311 | CtaClientEventInfo:
312 | required:
313 | - action
314 | properties:
315 | action:
316 | type: string
317 | enum: [primary_cta]
318 |
319 | TimelineClearEntriesUnreadState:
320 | required:
321 | - type
322 | properties:
323 | type:
324 | $ref: "#/components/schemas/InstructionType" # TimelineClearEntriesUnreadState
325 |
326 | TimelineMarkEntriesUnreadGreaterThanSortIndex:
327 | required:
328 | - type
329 | properties:
330 | type:
331 | $ref: "#/components/schemas/InstructionType" # TimelineMarkEntriesUnreadGreaterThanSortIndex
332 | sort_index:
333 | type: string
334 | pattern: "[0-9]+$"
335 |
--------------------------------------------------------------------------------
/src/openapi/schemas/timeline.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 |
6 | paths: {}
7 | components:
8 | schemas:
9 | TimelineResult:
10 | properties:
11 | id:
12 | type: string
13 | timeline:
14 | $ref: "#/components/schemas/Timeline"
15 |
16 | Timeline:
17 | required:
18 | - "instructions"
19 | properties:
20 | instructions:
21 | type: array
22 | items:
23 | $ref: "./../schemas/instruction.yaml#/components/schemas/InstructionUnion"
24 | metadata:
25 | type: object
26 | additionalProperties: true # todo
27 | responseObjects:
28 | type: object
29 | additionalProperties: true # todo
30 |
--------------------------------------------------------------------------------
/src/openapi/schemas/typename.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 | paths: {}
6 | components:
7 | schemas:
8 | TypeName:
9 | type: string
10 | enum:
11 | [
12 | TimelineTweet,
13 | TimelineTimelineItem,
14 | TimelineUser,
15 | TimelineTimelineCursor,
16 | TweetWithVisibilityResults,
17 | ContextualTweetInterstitial,
18 | TimelineTimelineModule,
19 | TweetTombstone,
20 | TimelinePrompt,
21 | TimelineMessagePrompt,
22 | TimelineCommunity,
23 | TimelineTombstone,
24 | TimelineTrend,
25 | TimelineNotification,
26 | TimelineNotificationAggregateUserActions,
27 | TweetUnavailable,
28 | TweetPreviewDisplay,
29 | Tweet,
30 | User,
31 | UserUnavailable,
32 | Community,
33 | CommunityDeleteActionUnavailable,
34 | CommunityJoinAction,
35 | CommunityJoinActionUnavailable,
36 | CommunityLeaveActionUnavailable,
37 | CommunityTweetPinActionUnavailable,
38 | CommunityTweetUnpinActionUnavailable,
39 | CommunityInvitesUnavailable,
40 | CommunityJoinRequestsUnavailable,
41 | ApiImage,
42 | ]
43 |
--------------------------------------------------------------------------------
/src/openapi/schemas/user.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Twitter OpenAPI
4 | version: 0.0.1
5 | paths: {}
6 | components:
7 | schemas:
8 | UserResultCore:
9 | required:
10 | - "user_results"
11 | properties:
12 | user_results:
13 | $ref: "#/components/schemas/UserResults"
14 |
15 | UserResults:
16 | properties:
17 | result:
18 | $ref: "#/components/schemas/UserUnion"
19 |
20 | UserUnion:
21 | oneOf:
22 | - $ref: "#/components/schemas/User"
23 | - $ref: "#/components/schemas/UserUnavailable"
24 | discriminator:
25 | propertyName: __typename
26 | mapping": # deprecated
27 | User: "#/components/schemas/User"
28 | UserUnavailable: "#/components/schemas/UserUnavailable"
29 |
30 | User:
31 | required:
32 | - "__typename"
33 | - "id"
34 | - "is_blue_verified"
35 | - "legacy"
36 | - "rest_id"
37 | - "profile_image_shape"
38 |
39 | properties:
40 | __typename:
41 | $ref: "./typename.yaml#/components/schemas/TypeName" # User
42 | affiliates_highlighted_label:
43 | type: object
44 | additionalProperties: true # todo
45 | has_graduated_access:
46 | type: boolean
47 | has_nft_avatar:
48 | type: boolean
49 | id:
50 | type: string
51 | pattern: "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$" # base64
52 | is_blue_verified:
53 | type: boolean
54 | legacy:
55 | $ref: "#/components/schemas/UserLegacy"
56 | rest_id:
57 | type: string
58 | pattern: "^[0-9]+$"
59 | business_account:
60 | type: object
61 | additionalProperties: true # todo
62 | super_follow_eligible:
63 | type: boolean
64 | super_followed_by:
65 | type: boolean
66 | super_following:
67 | type: boolean
68 | profile_image_shape:
69 | type: string
70 | enum:
71 | - "Circle"
72 | - "Square"
73 | - "Hexagon"
74 | professional:
75 | $ref: "#/components/schemas/UserProfessional"
76 | user_seed_tweet_count:
77 | type: integer
78 | highlights_info:
79 | $ref: "#/components/schemas/UserHighlightsInfo"
80 | creator_subscriptions_count:
81 | type: integer
82 | verification_info:
83 | $ref: "#/components/schemas/UserVerificationInfo"
84 | is_profile_translatable:
85 | type: boolean
86 | tipjar_settings:
87 | $ref: "#/components/schemas/UserTipJarSettings"
88 | legacy_extended_profile:
89 | $ref: "#/components/schemas/UserLegacyExtendedProfile"
90 | has_hidden_likes_on_profile:
91 | type: boolean
92 | premium_gifting_eligible:
93 | type: boolean
94 | has_hidden_subscriptions_on_profile:
95 | type: boolean
96 | parody_commentary_fan_label:
97 | type: string
98 | enum: ["None", "Parody", "Commentary"]
99 |
100 | UserProfessional:
101 | required:
102 | - "rest_id"
103 | - "professional_type"
104 | - "category"
105 | properties:
106 | rest_id:
107 | type: string
108 | pattern: "^[0-9]+$"
109 | professional_type:
110 | type: string
111 | enum: ["Business", "Creator"]
112 | category:
113 | type: array
114 | items:
115 | $ref: "#/components/schemas/UserProfessionalCategory"
116 |
117 | UserProfessionalCategory:
118 | required:
119 | - "id"
120 | - "name"
121 | - "icon_name"
122 | properties:
123 | id:
124 | type: integer
125 | name:
126 | type: string # enum
127 | icon_name:
128 | type: string # IconBriefcaseStroke ?
129 |
130 | UserHighlightsInfo:
131 | required:
132 | - "can_highlight_tweets"
133 | - "highlighted_tweets"
134 | properties:
135 | can_highlight_tweets:
136 | type: boolean
137 | highlighted_tweets:
138 | type: string
139 |
140 | UserVerificationInfo:
141 | required:
142 | - "is_identity_verified"
143 | properties:
144 | is_identity_verified:
145 | type: boolean
146 | reason:
147 | $ref: "#/components/schemas/UserVerificationInfoReason"
148 |
149 | UserVerificationInfoReason:
150 | required:
151 | - "description"
152 | - "verified_since_msec"
153 | properties:
154 | description:
155 | $ref: "#/components/schemas/UserVerificationInfoReasonDescription"
156 | verified_since_msec:
157 | type: string
158 | pattern: "^-?[0-9]+$"
159 | override_verified_year:
160 | type: integer
161 |
162 | UserVerificationInfoReasonDescription:
163 | required:
164 | - "text"
165 | - "entities"
166 | properties:
167 | text:
168 | type: string
169 | entities:
170 | type: array
171 | items:
172 | $ref: "#/components/schemas/UserVerificationInfoReasonDescriptionEntities"
173 |
174 | UserVerificationInfoReasonDescriptionEntities:
175 | required:
176 | - "from_index"
177 | - "to_index"
178 | - "ref"
179 | properties:
180 | from_index:
181 | type: integer
182 | to_index:
183 | type: integer
184 | ref:
185 | $ref: "#/components/schemas/UserVerificationInfoReasonDescriptionEntitiesRef"
186 |
187 | UserVerificationInfoReasonDescriptionEntitiesRef:
188 | required:
189 | - "url"
190 | - "url_type"
191 | properties:
192 | url:
193 | type: string
194 | format: uri
195 | url_type:
196 | type: string
197 | enum: ["ExternalUrl"]
198 |
199 | UserTipJarSettings:
200 | properties:
201 | is_enabled:
202 | type: boolean
203 | patreon_handle:
204 | type: string
205 | bitcoin_handle:
206 | type: string
207 | ethereum_handle:
208 | type: string
209 | cash_app_handle:
210 | type: string
211 | venmo_handle:
212 | type: string
213 | gofundme_handle:
214 | type: string # uri
215 | bandcamp_handle:
216 | type: string # uri
217 | pay_pal_handle:
218 | type: string
219 |
220 | UserLegacyExtendedProfile:
221 | properties:
222 | birthdate:
223 | $ref: "#/components/schemas/UserLegacyExtendedProfileBirthdate"
224 |
225 | UserLegacyExtendedProfileBirthdate:
226 | required:
227 | - "day"
228 | - "month"
229 | - "visibility"
230 | - "year_visibility"
231 | properties:
232 | day:
233 | type: integer
234 | month:
235 | type: integer
236 | year:
237 | type: integer
238 | visibility:
239 | type: string
240 | enum: ["Self", "Public", "MutualFollow", "Followers", "Following"]
241 | year_visibility:
242 | type: string
243 | enum: ["Self", "Public", "MutualFollow", "Followers", "Following"]
244 |
245 | UserLegacy:
246 | required:
247 | - "created_at"
248 | - "default_profile"
249 | - "default_profile_image"
250 | - "description"
251 | - "entities"
252 | - "fast_followers_count"
253 | - "favourites_count"
254 | - "followers_count"
255 | - "friends_count"
256 | - "has_custom_timelines"
257 | - "is_translator"
258 | - "listed_count"
259 | - "location"
260 | - "media_count"
261 | - "name"
262 | - "normal_followers_count"
263 | - "pinned_tweet_ids_str"
264 | - "possibly_sensitive"
265 | - "profile_image_url_https"
266 | - "profile_interstitial_type"
267 | - "screen_name"
268 | - "status"
269 | - "statuses_count"
270 | - "translator_type"
271 | - "verified"
272 | properties:
273 | blocked_by:
274 | type: boolean
275 | blocking:
276 | type: boolean
277 | can_dm:
278 | type: boolean
279 | can_media_tag:
280 | type: boolean
281 | created_at:
282 | $ref: "./general.yaml#/components/schemas/TwitterTimeFormat"
283 | default_profile:
284 | type: boolean
285 | default_profile_image:
286 | type: boolean
287 | description:
288 | type: string
289 | entities:
290 | type: object
291 | additionalProperties: true # todo
292 | fast_followers_count:
293 | type: integer
294 | favourites_count:
295 | type: integer
296 | follow_request_sent:
297 | type: boolean
298 | followed_by:
299 | type: boolean
300 | followers_count:
301 | type: integer
302 | following:
303 | type: boolean
304 | friends_count:
305 | type: integer
306 | has_custom_timelines:
307 | type: boolean
308 | is_translator:
309 | type: boolean
310 | listed_count:
311 | type: integer
312 | location:
313 | type: string
314 | media_count:
315 | type: integer
316 | muting:
317 | type: boolean
318 | name:
319 | type: string
320 | normal_followers_count:
321 | type: integer
322 | notifications:
323 | type: boolean
324 | pinned_tweet_ids_str:
325 | type: array
326 | items:
327 | type: string
328 | possibly_sensitive:
329 | type: boolean
330 | profile_banner_extensions:
331 | type: object
332 | profile_banner_url:
333 | type: string
334 | format: uri
335 | profile_image_extensions:
336 | type: object
337 | profile_image_url_https:
338 | type: string
339 | format: uri
340 | profile_interstitial_type:
341 | type: string
342 | protected:
343 | type: boolean
344 | screen_name:
345 | type: string
346 | statuses_count:
347 | type: integer
348 | translator_type:
349 | type: string
350 | url:
351 | type: string
352 | verified:
353 | type: boolean
354 | want_retweets:
355 | type: boolean
356 | verified_type:
357 | type: string
358 | enum: ["Business", "Government"]
359 | withheld_in_countries:
360 | type: array
361 | items:
362 | type: string # enum DE
363 |
364 | UserUnavailable:
365 | required:
366 | - "__typename"
367 | - "reason"
368 | properties:
369 | __typename:
370 | $ref: "./typename.yaml#/components/schemas/TypeName" # UserUnavailable
371 | reason:
372 | type: string
373 | message:
374 | type: string
375 |
--------------------------------------------------------------------------------
/test/dart-dio/openapi-generator-config.yaml:
--------------------------------------------------------------------------------
1 | inputSpec: dist/compatible/openapi-3.0.yaml
2 | outputDir: dart_dio_generated
3 |
--------------------------------------------------------------------------------
/test/local.sh:
--------------------------------------------------------------------------------
1 |
2 |
3 | python3 tools/build.py
4 | java -jar openapi-generator-cli.jar generate -c test/python/openapi-generator-config.yaml -g python
5 | python3 -m pip install ./python_generated
6 | python3 test/python/test_serialize.py
--------------------------------------------------------------------------------
/test/python/openapi-generator-config.yaml:
--------------------------------------------------------------------------------
1 | inputSpec: dist/compatible/openapi-3.0.yaml
2 | outputDir: python_generated
3 |
4 | useOneOfDiscriminatorLookup: true
5 |
--------------------------------------------------------------------------------
/test/python/test_serialize.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import concurrent.futures
3 | import glob
4 | import inspect
5 | import json
6 | import logging
7 | import os
8 | import time
9 | import traceback
10 | import warnings
11 | from enum import Enum
12 | from pathlib import Path
13 | from typing import Any
14 |
15 | import bs4
16 | import openapi_client as pt
17 | import requests
18 | import urllib3
19 | from x_client_transaction import ClientTransaction
20 | from x_client_transaction.utils import (
21 | generate_headers,
22 | get_ondemand_file_url,
23 | handle_x_migration,
24 | )
25 |
26 | warnings.filterwarnings("ignore")
27 | logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(message)s")
28 | logger = logging.getLogger("test_serialize")
29 |
30 | TWITTER_SESSION = os.environ.get("TWITTER_SESSION", None)
31 | ERROR_UNCATCHED = os.environ.get("ERROR_UNCATCHED", "false").lower() == "true"
32 | SLEEP_TIME = float(os.environ.get("SLEEP", "0"))
33 | CUESOR_TEST_COUNT = int(os.environ.get("CUESOR_TEST_COUNT", "3"))
34 | STRICT_MODE = os.environ.get("STRICT_MODE", "false").lower() == "true"
35 | MULTI_THREAD = os.environ.get("MULTI_THREAD", "true").lower() == "true"
36 |
37 |
38 | def get_key(snake_str):
39 | components = snake_str.split("_")
40 | return "".join(x.title() for x in components[1:])
41 |
42 |
43 | def get_cursor(obj, fn):
44 | res = []
45 | if isinstance(obj, dict):
46 | callback = fn(obj)
47 | if callback is not None:
48 | res.extend(callback)
49 | else:
50 | for v in obj.values():
51 | res.extend(get_cursor(v, fn))
52 | elif isinstance(obj, list):
53 | for v in obj:
54 | res.extend(get_cursor(v, fn))
55 | return res
56 |
57 |
58 | def find_cursor(x):
59 | if x.get("__typename") is pt.TypeName.TIMELINETIMELINECURSOR:
60 | return [x["value"]]
61 |
62 |
63 | def find_name(x):
64 | if x.get("name") is not None:
65 | return [x["name"]]
66 |
67 |
68 | def get_transaction_base():
69 | session = requests.Session()
70 | session.headers = generate_headers()
71 | home_page_response = handle_x_migration(session=session)
72 | home_page = session.get(url="https://x.com")
73 | home_page_response = bs4.BeautifulSoup(home_page.content, "html.parser")
74 | ondemand_file_url = get_ondemand_file_url(response=home_page_response)
75 | ondemand_file = session.get(url=ondemand_file_url)
76 | ondemand_file_response = bs4.BeautifulSoup(ondemand_file.content, "html.parser")
77 | ct = ClientTransaction(home_page_response, ondemand_file_response)
78 | return ct
79 |
80 |
81 | def get_transaction_id(key, ct=get_transaction_base()):
82 | return ct.generate_transaction_id(
83 | method=placeholder[key]["@method"], path=placeholder[key]["@path"]
84 | )
85 |
86 |
87 | def get_kwargs(key, additional):
88 | kwargs = {"path_query_id": placeholder[key]["queryId"], "_headers": {}}
89 | if placeholder[key].get("variables") is not None:
90 | kwargs["variables"] = json.dumps(placeholder[key]["variables"] | additional)
91 | if placeholder[key].get("features") is not None:
92 | kwargs["features"] = json.dumps(placeholder[key]["features"])
93 | if placeholder[key].get("fieldToggles") is not None:
94 | kwargs["field_toggles"] = json.dumps(placeholder[key]["fieldToggles"])
95 | if placeholder[key].get("@path") is not None:
96 | kwargs["_headers"]["x-client-transaction-id"] = get_transaction_id(key)
97 | return kwargs
98 |
99 |
100 | def match_rate_zero(a, b, base, key):
101 | def get(obj, key):
102 | if isinstance(obj, list):
103 | return get(obj[key[0]], key[1:])
104 | if obj.__dict__.get("actual_instance") is not None:
105 | return get(obj.actual_instance, key)
106 | if len(key) == 0:
107 | return obj
108 | return get(super_get(obj.__dict__, key[0]), key[1:])
109 |
110 | if STRICT_MODE:
111 | obj_name = type(get(base, key[:-1]))
112 | obj_key = f"{obj_name.__name__}.{key[-1]}"
113 | raise Exception(f"Not defined: {obj_key}\nContents: {b}")
114 |
115 | return 0
116 |
117 |
118 | def match_rate(a, b, base, key=""):
119 | if isinstance(a, Enum):
120 | a = a.value
121 | if isinstance(b, Enum):
122 | b = b.value
123 | if a is None and b is False:
124 | return 1
125 | if a is False and b is None:
126 | return 1
127 | if a is None and isinstance(b, list) and len(b) == 0:
128 | return 1
129 | if isinstance(a, list) and b is None and len(a) == 0:
130 | return 1
131 | if a is None and isinstance(b, dict) and len(b) == 0:
132 | return 1
133 | if isinstance(a, dict) and b is None and len(a) == 0:
134 | return 1
135 | if isinstance(a, dict) and isinstance(b, dict):
136 | if len(a) == 0 and len(b) == 0:
137 | return 1
138 | marge_key = set(a.keys()) | set(b.keys())
139 | data = [match_rate(a.get(k), b.get(k), base, [*key, k]) for k in marge_key]
140 | return sum(data) / len(b)
141 | if isinstance(a, list) and isinstance(b, list):
142 | if len(a) == 0 and len(b) == 0:
143 | return 1
144 | if len(a) != len(b):
145 | return match_rate_zero(a, b, base, key)
146 | data = [match_rate(a[i], b[i], base, [*key, i]) for i in range(len(a))]
147 | return sum(data) / len(a)
148 | if a == b:
149 | return 1
150 | return match_rate_zero(a, b, base, key)
151 |
152 |
153 | def save_cache(data):
154 | rand = time.time_ns()
155 | os.makedirs("cache", exist_ok=True)
156 | with open(f"cache/{rand}.json", "w+") as f:
157 | json.dump(data, f, indent=4)
158 |
159 |
160 | def super_get(obj: dict, key: str):
161 | keys = [
162 | key,
163 | "".join(["_" + c.lower() if c.isupper() else c for c in key]).lstrip("_"),
164 | ]
165 |
166 | for k in keys:
167 | if obj.get(k) is not None:
168 | return obj[k]
169 | raise KeyError(key)
170 |
171 |
172 | def task_callback(file, thread=True):
173 | try:
174 | with open(file, "r") as f:
175 | cache = json.load(f)
176 | data = pt.__dict__[cache["type"]].from_json(cache["raw"])
177 |
178 | rate = match_rate(
179 | data.to_dict(),
180 | json.loads(cache["raw"]),
181 | base=data,
182 | )
183 | return rate, file
184 | except Exception:
185 | if thread:
186 | return 0, file
187 | else:
188 | raise
189 |
190 |
191 | def kebab_to_upper_camel(text: dict[str, Any]) -> dict[str, Any]:
192 | res = {}
193 | for key, value in text.items():
194 | new_key = "".join([x.capitalize() for x in remove_prefix(key).split("-")])
195 | res[new_key] = value
196 | return res
197 |
198 |
199 | def get_header(data: dict, name: str):
200 | ignore = ["host", "connection"]
201 | return {key: value for key, value in data[name].items() if key not in ignore}
202 |
203 |
204 | def remove_prefix(text: str) -> str:
205 | if text.startswith("x-twitter-"):
206 | return text[10:]
207 | if text.startswith("x-"):
208 | return text[2:]
209 | return text
210 |
211 |
212 | def error_dump(e):
213 | if ERROR_UNCATCHED:
214 | raise
215 |
216 | logger.error("==========[STACK TRACE]==========")
217 | for trace in traceback.format_exc().split("\n"):
218 | logger.error(trace)
219 | logger.info("================================")
220 |
221 |
222 | if __name__ == "__main__":
223 | if Path("cookie.json").exists():
224 | with open("cookie.json", "r") as f:
225 | cookies = json.load(f)
226 | elif TWITTER_SESSION is not None:
227 | data = base64.b64decode(TWITTER_SESSION).decode("utf-8")
228 | cookies = json.loads(data)
229 | else:
230 | commands = ["python -m pip install tweepy_authlib", "python tools/login.py"]
231 | raise Exception(
232 | f"cookie.json not found. Please run `{'; '.join(commands)}` first."
233 | )
234 |
235 | if isinstance(cookies, list):
236 | cookies = {k["name"]: k["value"] for k in cookies}
237 | cookies_str = "; ".join([f"{k}={v}" for k, v in cookies.items()])
238 |
239 | with open("src/config/placeholder.json", "r") as f:
240 | placeholder = json.load(f)
241 |
242 | fail = []
243 | files = glob.glob("cache/*.json")
244 | if MULTI_THREAD:
245 | with concurrent.futures.ProcessPoolExecutor() as executor:
246 | tasks = [executor.submit(task_callback, x) for x in files]
247 | for task in concurrent.futures.as_completed(tasks):
248 | rate, file = task.result()
249 | if rate < 1:
250 | fail.append(file)
251 | logger.info(f"Match rate: {rate}")
252 | else:
253 | for file in files:
254 | rate, file = task_callback(file, thread=False)
255 | if rate < 1:
256 | fail.append(file)
257 | logger.info(f"Match rate: {rate}")
258 |
259 | logger.info(f"Fail: {len(fail)} / {len(glob.glob('cache/*.json'))}")
260 |
261 | for file in fail:
262 | task_callback(file, thread=False)
263 | logger.info(f"Match rate: {rate}")
264 |
265 | for file in glob.glob("other/**/*.json", recursive=True):
266 | with open(file, "r") as f:
267 | data = json.load(f)
268 |
269 | try:
270 | _ = pt.TweetDetailResponse.from_dict(data)
271 | except Exception as e:
272 | error_dump(e)
273 | access_token = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
274 | twitter_url = "https://x.com/home"
275 | latest_user_agent_res = urllib3.PoolManager().request(
276 | "GET",
277 | "https://raw.githubusercontent.com/fa0311/latest-user-agent/refs/heads/main/header.json",
278 | )
279 | latest_user_agent = json.loads(latest_user_agent_res.data.decode("utf-8"))
280 | api_conf = pt.Configuration(
281 | api_key=kebab_to_upper_camel(
282 | {
283 | **get_header(latest_user_agent, "chrome-fetch"),
284 | "sec-ch-ua-platform": '"Windows"',
285 | "accept-encoding": "identity",
286 | "referer": twitter_url,
287 | "priority": "u=1, i",
288 | "authorization": f"Bearer {access_token}",
289 | "x-twitter-auth-type": "OAuth2Session",
290 | "x-twitter-client-language": "en",
291 | "x-twitter-active-user": "yes",
292 | "x-csrf-token": cookies["ct0"],
293 | },
294 | ),
295 | )
296 | api_conf.access_token = access_token
297 | api_client = pt.ApiClient(configuration=api_conf, cookie=cookies_str)
298 | api_client.user_agent = get_header(latest_user_agent, "chrome-fetch")["user-agent"]
299 |
300 | session = requests.Session()
301 | session.headers = get_header(latest_user_agent, "chrome")
302 |
303 | error_count = 0
304 |
305 | for x in [pt.DefaultApi, pt.TweetApi, pt.UserApi, pt.UsersApi, pt.UserListApi]:
306 | for props, fn in inspect.getmembers(x):
307 | if not callable(fn):
308 | continue
309 | if props.startswith("__") or not props.endswith("_with_http_info"):
310 | continue
311 |
312 | key = get_key(props[:-15])
313 | cursor_list = set([None])
314 | cursor_history = set()
315 |
316 | try:
317 | for _ in range(CUESOR_TEST_COUNT):
318 | cursor = cursor_list.pop()
319 | cursor_history.add(cursor)
320 | logger.info(f"Try: {key} {cursor}")
321 |
322 | kwargs = get_kwargs(
323 | key, {} if cursor is None else {"cursor": cursor}
324 | )
325 | res: pt.ApiResponse = getattr(x(api_client), props)(**kwargs)
326 | data = res.data.to_dict()
327 |
328 | save_cache(
329 | {
330 | "raw": res.raw_data.decode("utf-8"),
331 | "type": res.data.__class__.__name__,
332 | }
333 | )
334 |
335 | new_cursor = set(get_cursor(data, find_cursor)) - cursor_history
336 | cursor_list.update(new_cursor)
337 |
338 | rate = match_rate(
339 | data,
340 | json.loads(res.raw_data),
341 | res.data,
342 | )
343 | logger.info(f"Match rate: {rate}")
344 |
345 | if data.get("errors") is not None:
346 | logger.error(data)
347 | error_count += 1
348 |
349 | if len(cursor_list) == 0:
350 | break
351 | time.sleep(SLEEP_TIME)
352 |
353 | except Exception as e:
354 | error_dump(e)
355 | error_count += 1
356 |
357 | try:
358 | logger.info("Try: Self UserByScreenName Test")
359 | kwargs = get_kwargs("UserByScreenName", {"screen_name": "ptcpz3"})
360 | res = pt.UserApi(api_client).get_user_by_screen_name_with_http_info(**kwargs)
361 | data = res.data.to_dict()
362 |
363 | rate = match_rate(
364 | data,
365 | json.loads(res.raw_data),
366 | res.data,
367 | )
368 | logger.info(f"Match rate: {rate}")
369 | screen_name = data["data"]["user"]["result"]["legacy"]["screen_name"]
370 | if not screen_name == "ptcpz3":
371 | raise Exception("UserByScreenName failed")
372 | except Exception as e:
373 | error_dump(e)
374 | error_count += 1
375 |
376 | ids = [
377 | # "1180389371481976833", banned
378 | "900282258736545792",
379 | "1212617657003859968",
380 | "2455740283",
381 | "2326837940",
382 | "1788224200639160320",
383 | "1500128450186985472",
384 | "25073877",
385 | ]
386 | for id in ids:
387 | try:
388 | logger.info("Try: Self UserTweets Test")
389 | kwargs = get_kwargs("UserTweets", {"userId": id})
390 | res = pt.TweetApi(api_client).get_user_tweets_with_http_info(**kwargs)
391 | data = res.data.to_dict()
392 |
393 | rate = match_rate(
394 | data,
395 | json.loads(res.raw_data),
396 | res.data,
397 | )
398 | logger.info(f"Match rate: {rate}")
399 |
400 | except Exception as e:
401 | error_dump(e)
402 | error_count += 1
403 |
404 | ids = [
405 | "1720975693524377759",
406 | "1721006592303251551",
407 | "1739194269477331076",
408 | # "1697450269259522256",
409 | # "1697450278742884799",
410 | "1749500209061663043",
411 | "1759056048764469303",
412 | "1349129669258448897",
413 | "1810188416812019999",
414 | "1851981523207299417",
415 | "1853879226987901408",
416 | "1866022435549757577",
417 | "1866103697148887145",
418 | "1866036470303309863",
419 | "1862405433639804958",
420 | "1869983867521904840",
421 | "1875050002046726519",
422 | "1848219562136801480",
423 | "1881993128288399684",
424 | "1899104692577489182",
425 | ]
426 | for id in ids:
427 | try:
428 | logger.info(f"Try: Self TweetDetail {id} Test")
429 | kwargs = get_kwargs("TweetDetail", {"focalTweetId": id})
430 | res = pt.TweetApi(api_client).get_tweet_detail_with_http_info(**kwargs)
431 | data = res.data.to_dict()
432 |
433 | save_cache(
434 | {
435 | "raw": res.raw_data.decode("utf-8"),
436 | "type": res.data.__class__.__name__,
437 | }
438 | )
439 |
440 | rate = match_rate(
441 | data,
442 | json.loads(res.raw_data),
443 | res.data,
444 | )
445 | logger.info(f"Match rate: {rate}")
446 | except Exception as e:
447 | error_dump(e)
448 | error_count += 1
449 |
450 | if error_count > 0:
451 | exit(1)
452 |
--------------------------------------------------------------------------------
/test/python/test_serialize_guest.py:
--------------------------------------------------------------------------------
1 | import json
2 | import re
3 | from urllib.parse import urlencode, urlparse
4 |
5 | import openapi_client as pt
6 | import requests
7 | import urllib3
8 | from x_client_transaction import ClientTransaction
9 | from x_client_transaction.utils import handle_x_migration
10 |
11 |
12 | def get_kwargs(key, additional):
13 | kwargs = {"path_query_id": placeholder[key]["queryId"], "_headers": {}}
14 | if placeholder[key].get("variables") is not None:
15 | kwargs["variables"] = json.dumps(placeholder[key]["variables"] | additional)
16 | if placeholder[key].get("features") is not None:
17 | kwargs["features"] = json.dumps(placeholder[key]["features"])
18 | if placeholder[key].get("fieldToggles") is not None:
19 | kwargs["field_toggles"] = json.dumps(placeholder[key]["fieldToggles"])
20 | if placeholder[key].get("@path") is not None:
21 | kwargs["_headers"]["x-client-transaction-id"] = ct.generate_transaction_id(
22 | method=placeholder[key]["@method"], path=placeholder[key]["@path"]
23 | )
24 | return kwargs
25 |
26 |
27 | class SessionManager:
28 | def __init__(self) -> None:
29 | header = "https://raw.githubusercontent.com/fa0311/latest-user-agent/refs/heads/main/header.json"
30 | self.http = urllib3.PoolManager()
31 | self.chorome_header = json.loads(self.http.request("GET", header).data)
32 |
33 | def child(self):
34 | return SessionManagerChild(self.http, self.chorome_header)
35 |
36 |
37 | class SessionManagerChild:
38 | def __init__(self, http, chorome_header) -> None:
39 | self.http = http
40 | self.chorome_header = chorome_header
41 | self.session = {}
42 |
43 | def cookie_normalize(self, cookie: list[str]) -> dict[str, str]:
44 | value = {
45 | x.split("; ")[0].split("=")[0]: x.split("; ")[0].split("=")[1]
46 | for x in cookie
47 | }
48 | return {key: value[key] for key in value if len(value[key]) > 0}
49 |
50 | def cookie_to_str(self, cookie: dict[str, str]) -> str:
51 | return "; ".join([f"{key}={value}" for key, value in cookie.items()])
52 |
53 | def getHader(self, additionals={}) -> dict[str, str]:
54 | ignore = ["host", "connection"]
55 | base = {
56 | key: value
57 | for key, value in self.chorome_header["chrome"].items()
58 | if key not in ignore
59 | }
60 | return base | {"cookie": self.cookie_to_str(self.session)} | additionals
61 |
62 | def update_normalize(self, cookie: list[str]):
63 | self.update(self.cookie_normalize(cookie))
64 |
65 | def update(self, cookie: dict[str, str]):
66 | self.session.update(cookie)
67 |
68 | def pop(self, key: str):
69 | self.session.pop(key)
70 |
71 | def get(self, key: str):
72 | return self.session.get(key)
73 |
74 | def to_str(self):
75 | return self.cookie_to_str(self.session)
76 |
77 |
78 | def get_guest_token():
79 | twitter_url = "https://x.com/elonmusk"
80 | http = urllib3.PoolManager()
81 | chrome = SessionManager()
82 | x = chrome.child()
83 | twitter = chrome.child()
84 |
85 | def regex(str: str, **kwargs) -> str:
86 | return str.format(
87 | quote=r"[\'\"]",
88 | space=r"\s*",
89 | dot=r"\.",
90 | any=r".*?",
91 | target=r"([\s\S]*?)",
92 | **kwargs,
93 | )
94 |
95 | def redirect(
96 | method: str, url: str, body: str = None, headers: dict[str, str] = {}
97 | ) -> urllib3.HTTPResponse:
98 | for _ in range(10):
99 | if urlparse(url).netloc == "x.com":
100 | res = http.request(
101 | method, url, headers=x.getHader(headers), body=body, redirect=False
102 | )
103 | x.update_normalize(res.headers._container["set-cookie"][1:])
104 | elif urlparse(url).netloc == "twitter.com":
105 | res = http.request(
106 | method,
107 | url,
108 | headers=twitter.getHader(headers),
109 | body=body,
110 | redirect=False,
111 | )
112 | twitter.update_normalize(res.headers._container["set-cookie"][1:])
113 | else:
114 | raise Exception("Invalid domain")
115 |
116 | method = "GET"
117 | body = None
118 | headers = {}
119 | location = "document{dot}location{space}={space}{quote}{target}{quote}"
120 | submit = "document{dot}forms{dot}{target}{dot}submit"
121 | form = ""
122 | input = ""
123 |
124 | if res.status >= 300 and res.status < 400:
125 | new_path = res.headers._container["location"][1]
126 | if new_path.startswith("/"):
127 | domain = f"{urlparse(url).scheme}://{urlparse(url).netloc}"
128 | url = f"{domain}{new_path}"
129 | else:
130 | url = new_path
131 |
132 | elif re.findall(regex(location), res.data.decode()):
133 | url = re.findall(regex(location), res.data.decode())[0]
134 | elif re.findall(regex(submit), res.data.decode()):
135 | name = re.findall(regex(submit), res.data.decode())
136 | form_html = re.findall(regex(form, name=name[0]), res.data.decode())
137 | input_html = re.findall(regex(input), form_html[0][1])
138 | method = "POST"
139 | url = form_html[0][0]
140 | body = urlencode({k: v for k, v in input_html})
141 | headers = {"content-type": "application/x-www-form-urlencoded"}
142 | elif res.status == 200:
143 | return res
144 | else:
145 | raise Exception("Failed to redirect")
146 | else:
147 | raise Exception("Failed to redirect")
148 |
149 | res = redirect("GET", twitter_url)
150 | reg = "document{dot}cookie{space}={space}{quote}{target}{quote}"
151 | if re.findall(regex(reg), res.data.decode()):
152 | find = re.findall(regex(reg), res.data.decode())
153 | x.update_normalize(find)
154 |
155 | if x.get("gt") is None:
156 | raise Exception("Failed to get guest token")
157 |
158 | return x
159 |
160 |
161 | if __name__ == "__main__":
162 | cookies = get_guest_token()
163 | cookies_str = cookies.to_str()
164 |
165 | with open("src/config/placeholder.json", "r") as f:
166 | placeholder = json.load(f)
167 |
168 | api_conf = pt.Configuration(
169 | api_key={
170 | "ClientLanguage": "en",
171 | "ActiveUser": "yes",
172 | "GuestToken": cookies.get("gt"),
173 | },
174 | )
175 |
176 | latest_user_agent_res = urllib3.PoolManager().request(
177 | "GET",
178 | "https://raw.githubusercontent.com/fa0311/latest-user-agent/main/output.json",
179 | )
180 |
181 | latest_user_agent = json.loads(latest_user_agent_res.data.decode("utf-8"))
182 |
183 | api_conf.access_token = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
184 | api_client = pt.ApiClient(configuration=api_conf, cookie=cookies_str)
185 | api_client.user_agent = latest_user_agent["chrome-fetch"]
186 |
187 | session = requests.Session()
188 | session.headers = latest_user_agent["chrome"]
189 | ct = ClientTransaction(handle_x_migration(session))
190 |
191 | res = (
192 | pt.TweetApi(api_client)
193 | .get_user_tweets_with_http_info(
194 | **get_kwargs("UserTweets", {}),
195 | )
196 | .model_dump_json()
197 | )
198 | res = (
199 | pt.TweetApi(api_client)
200 | .get_user_highlights_tweets_with_http_info(
201 | **get_kwargs("UserHighlightsTweets", {}),
202 | )
203 | .model_dump_json()
204 | )
205 | res = (
206 | pt.DefaultApi(api_client)
207 | .get_tweet_result_by_rest_id_with_http_info(
208 | **get_kwargs("TweetResultByRestId", {}),
209 | )
210 | .model_dump_json()
211 | )
212 | res = (
213 | pt.UserApi(api_client)
214 | .get_user_by_screen_name_with_http_info(**get_kwargs("UserByScreenName", {}))
215 | .model_dump_json()
216 | )
217 |
--------------------------------------------------------------------------------
/test/typescript-fetch/openapi-generator-config.yaml:
--------------------------------------------------------------------------------
1 | inputSpec: dist/compatible/openapi-3.0.yaml
2 | outputDir: typescript_fetch_generated
3 |
4 | legacyDiscriminatorBehavior: false
5 |
--------------------------------------------------------------------------------
/tools/build.py:
--------------------------------------------------------------------------------
1 | import os
2 | import glob
3 | import yaml
4 | import shutil
5 | from build_config import Config
6 | from hooks import OpenapiHookBase, RequestHookBase, SchemasHookBase, OtherHookBase
7 | from tqdm import tqdm
8 |
9 |
10 | def replace_ref(x):
11 | if isinstance(x, dict):
12 | return {
13 | key: "#" + value.split("#")[-1] if key == "$ref" else replace_ref(value)
14 | for key, value in x.items()
15 | }
16 | elif isinstance(x, list):
17 | return [replace_ref(value) for value in x]
18 | return x
19 |
20 |
21 | print("=== Build Start ===")
22 |
23 |
24 | config = Config()
25 |
26 |
27 | shutil.rmtree("dist", ignore_errors=True)
28 |
29 |
30 | for lang, profile in tqdm(config.main().items(), leave=False):
31 | paths = {}
32 | schemas = {}
33 | files = glob.glob(os.path.join(config.INPUT_DIR, "**/*.yaml"))
34 | for file in tqdm(files, leave=False):
35 | file = file.replace(os.path.sep, "/")
36 | with open(file, mode="r", encoding="utf-8") as f:
37 | load = yaml.safe_load(f)
38 |
39 | # RequestHookBase hook
40 | for path in list(load["paths"]):
41 | for method in list(load["paths"][path]):
42 | key, value = path, load["paths"][path][method]
43 | for tag in list(load["paths"][path][method].get("tags", ["default"])):
44 | for hook in profile["request"][tag]:
45 | hook: RequestHookBase
46 | key, value = hook.hook(key, value)
47 | paths.update({key: {}})
48 | paths[key].update({method: value})
49 |
50 | # SchemasHookBase hook
51 | for name in list(load.get("components", {}).get("schemas", {})):
52 | value = load["components"]["schemas"][name]
53 | for hook in profile["schemas"]:
54 | hook: SchemasHookBase
55 | value = hook.hook(value)
56 | schemas.update({name: value})
57 |
58 | # OtherHookBase hook
59 | if file == "src/openapi/paths/other.yaml":
60 | for hook in profile["other"]:
61 | hook: OtherHookBase
62 | key, value = hook.hook()
63 | schemas["OtherResponse"]["properties"].append({key: value})
64 |
65 | file = "src/openapi/openapi-3.0.yaml"
66 |
67 | with open(file, mode="r", encoding="utf-8") as f:
68 | openapi = yaml.safe_load(f)
69 |
70 | # OpenapiHookBase hook
71 | for hook in profile["openapi"]:
72 | hook: OpenapiHookBase
73 | openapi = hook.hook(openapi)
74 |
75 | openapi["paths"] = replace_ref(paths)
76 | openapi["components"]["schemas"] = replace_ref(schemas)
77 | output = config.OUTPUT_DIR.format(lang)
78 | os.makedirs(output, exist_ok=True)
79 |
80 | with open(output + "/openapi-3.0.yaml", mode="w+", encoding="utf-8") as f:
81 | f.write(yaml.dump(openapi))
82 |
83 | print("=== Build End ===")
84 |
--------------------------------------------------------------------------------
/tools/build_config.py:
--------------------------------------------------------------------------------
1 | from hooks import (
2 | AddParametersOnBody,
3 | AddParametersOnContent,
4 | AddParametersOnParameters,
5 | AddPathQueryIdOnParameters,
6 | SetResponsesHeader,
7 | )
8 |
9 |
10 | class Config:
11 | OUTPUT_DIR = "dist/{0}"
12 | INPUT_DIR = "src/openapi"
13 |
14 | def hooks_generator(self, queryParameterJson=True):
15 | # https://stackoverflow.com/questions/34820064/defining-an-api-with-swagger-get-call-that-uses-json-in-parameters/45223964
16 | if queryParameterJson:
17 | # ["parameters"][0]["content"]["application/json"]["schema"]
18 | getParamHook = AddParametersOnContent(
19 | split=-1,
20 | contentType="application/json",
21 | ignoreKeys=["queryId", "@path", "@method"],
22 | )
23 | else:
24 | # ["parameters"][0]["schema"]
25 | getParamHook = AddParametersOnParameters(
26 | split=-1,
27 | schemaType="string",
28 | ignoreKeys=["queryId", "@path", "@method"],
29 | )
30 |
31 | return {
32 | "openapi": [],
33 | "schemas": [],
34 | "other": [],
35 | "request": {
36 | key: [
37 | SetResponsesHeader(),
38 | AddPathQueryIdOnParameters(split=-1),
39 | getParamHook,
40 | ]
41 | for key in ["default", "user", "users", "user-list", "tweet"]
42 | }
43 | | {
44 | key: [
45 | SetResponsesHeader(),
46 | AddPathQueryIdOnParameters(split=-1),
47 | AddParametersOnBody(
48 | split=-1,
49 | schemaType=None,
50 | contentType="application/json",
51 | ignoreKeys=["@path", "@method"],
52 | ),
53 | ]
54 | for key in ["post"]
55 | }
56 | | {
57 | key: [
58 | SetResponsesHeader(),
59 | AddParametersOnParameters(split=2, schemaType=None),
60 | ]
61 | for key in ["v1.1-get", "v2.0-get"]
62 | }
63 | | {
64 | key: [
65 | SetResponsesHeader(),
66 | AddParametersOnBody(
67 | split=2,
68 | schemaType=None,
69 | contentType="application/x-www-form-urlencoded",
70 | ignoreKeys=["@path", "@method"],
71 | ),
72 | ]
73 | for key in ["v1.1-post", "v2.0-post"]
74 | }
75 | | {"other": []},
76 | }
77 |
78 | def main(self):
79 | return {
80 | "docs": self.hooks_generator(),
81 | "compatible": self.hooks_generator(
82 | queryParameterJson=False,
83 | ),
84 | }
85 |
--------------------------------------------------------------------------------
/tools/generater.py:
--------------------------------------------------------------------------------
1 | # https://github.com/tsukumijima/KonomiTV/blob/master/server/misc/TwitterAPIQueryGenerator.py
2 | # https://github.com/tsukumijima/KonomiTV/blob/master/License.txt
3 |
4 | #!/usr/bin/env python3
5 |
6 | # Usage: poetry run python -m misc.TwitterAPIQueryGenerator
7 |
8 | import json
9 | import re
10 | import urllib.parse
11 |
12 | from rich import print
13 | from rich.rule import Rule
14 |
15 |
16 | def main():
17 | print(Rule(characters="="))
18 | print(
19 | "Chrome DevTools の Network タブで検索窓に「graphql」と入力し「表示されているものをすべてfetch としてコピー」したコードを`input.js`に貼り付けてください。"
20 | )
21 | print("Enter を押すと続行します。")
22 | print(Rule(characters="="))
23 | input()
24 |
25 | with open("./tools/input.js", "r") as f:
26 | fetch_code_raw = f.read()
27 |
28 | print(Rule(characters="="))
29 |
30 | splited = fetch_code_raw.split("\n")
31 | fetch_code_list = []
32 | code = ""
33 | for line in splited:
34 | if line.startswith("fetch("):
35 | if code:
36 | fetch_code_list.append(code)
37 | code = line
38 | else:
39 | code += line + "\n"
40 | fetch_code_list.append(code)
41 |
42 | for fetch_code in fetch_code_list:
43 | # query_idとendpointを抽出
44 | query_id_match = re.search(r'/i/api/graphql/([^/]+)/([^"?]+)', fetch_code)
45 | if not query_id_match:
46 | print("query_id と endpoint の抽出に失敗しました。")
47 | print(Rule(characters="="))
48 | return
49 | query_id = query_id_match.group(1)
50 | endpoint = query_id_match.group(2)
51 |
52 | # リクエストメソッドを判定
53 | method_match = re.search(r'"method"\s*:\s*"(GET|POST)"', fetch_code)
54 | if not method_match:
55 | print("リクエストメソッドの判定に失敗しました。")
56 | print(Rule(characters="="))
57 | return
58 | method = method_match.group(1)
59 |
60 | if method == "POST":
61 | # POST リクエストの場合、fetch() コードの第二引数にある {} で囲まれたオブジェクトを正規表現で抽出したものを JSON としてパース
62 | body_match = re.search(r'"body"\s*:\s*"({.*})"', fetch_code, re.DOTALL)
63 | if not body_match:
64 | print("body の抽出に失敗しました。")
65 | print(Rule(characters="="))
66 | return
67 | body_json_str = body_match.group(1).replace("\\", "")
68 | body_json = json.loads(body_json_str)
69 | features = body_json.get("features", None)
70 | variables = body_json.get("variables", None)
71 | fieldToggles = body_json.get("fieldToggles", None)
72 | else:
73 | # GET リクエストの場合、まず URL を抽出
74 | url_match = re.search(r'"(https?://[^"]+)"', fetch_code)
75 | if not url_match:
76 | print("URL の抽出に失敗しました。")
77 | print(Rule(characters="="))
78 | return
79 | url = url_match.group(1)
80 |
81 | # URL をパースして query string を取得
82 | parsed_url = urllib.parse.urlparse(url)
83 | query_string = parsed_url.query
84 |
85 | # query string を dict 形式にパース
86 | query_dict = urllib.parse.parse_qs(query_string)
87 |
88 | # features を取得
89 | features_json_str = query_dict.get("features", [None])[0]
90 | if features_json_str is None:
91 | features = None
92 | else:
93 | try:
94 | features = json.loads(features_json_str)
95 | except json.JSONDecodeError:
96 | features = None
97 | print(
98 | "features の JSON パースに失敗しました。features は None として続行します。"
99 | )
100 |
101 | variables_json_str = query_dict.get("variables", [None])[0]
102 | if variables_json_str is None:
103 | variables = None
104 | else:
105 | try:
106 | variables = json.loads(variables_json_str)
107 | except json.JSONDecodeError:
108 | variables = None
109 | print(
110 | "variables の JSON パースに失敗しました。variables は None として続行します。"
111 | )
112 |
113 | fieldToggles_json_str = query_dict.get("fieldToggles", [None])[0]
114 | if fieldToggles_json_str is None:
115 | fieldToggles = None
116 | else:
117 | try:
118 | fieldToggles = json.loads(fieldToggles_json_str)
119 | except json.JSONDecodeError:
120 | fieldToggles = None
121 | print(
122 | "fieldToggles の JSON パースに失敗しました。fieldToggles は None として続行します。"
123 | )
124 |
125 | with open("./src/config/placeholder.json", "r") as f:
126 | placeholder = json.load(f)
127 |
128 | def check(a, b, msg):
129 | if isinstance(a, dict) and isinstance(b, dict):
130 | for k in {*a.keys(), *b.keys()}:
131 | if k not in b:
132 | print(f"{msg} key: {k} が存在しません。")
133 | elif k not in a:
134 | print(f"{msg} key: {k} が存在しません。")
135 | else:
136 | check(a[k], b[k], msg)
137 |
138 | check(
139 | variables,
140 | placeholder.get(endpoint, {}).get("variables", {}),
141 | f"{endpoint} の variables が不一致です。",
142 | )
143 |
144 | with open("./src/config/placeholder.json", "w") as f:
145 | placeholder[endpoint] = placeholder.get(endpoint, {})
146 | placeholder[endpoint]["@path"] = f"/i/api/graphql/{query_id}/{endpoint}"
147 | placeholder[endpoint]["@method"] = method
148 | placeholder[endpoint]["queryId"] = query_id
149 | if features:
150 | placeholder[endpoint]["features"] = features
151 | if fieldToggles:
152 | placeholder[endpoint]["fieldToggles"] = fieldToggles
153 | json.dump(placeholder, f, indent=4)
154 |
155 |
156 | if __name__ == "__main__":
157 | main()
158 |
--------------------------------------------------------------------------------
/tools/hooks.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import yaml
4 |
5 |
6 | class HookBase:
7 | PLACEHOLDER: dict
8 |
9 | def __init__(self):
10 | with open("src/config/placeholder.json", mode="r", encoding="utf-8") as f:
11 | self.PLACEHOLDER = json.load(f)
12 |
13 | def placeholder_to_yaml(self, obj, default=False, example=False) -> dict:
14 | def mine(x):
15 | return self.placeholder_to_yaml(x, default=default, example=example)
16 |
17 | def fn(x: str):
18 | return x[:-1] if x.endswith("?") else x
19 |
20 | if isinstance(obj, dict):
21 | ignore_req = [k[:-1] for k in obj.keys() if k.endswith("?")]
22 | req = [k for k in obj.keys() if fn(k) not in ignore_req]
23 | properties = {fn(k): mine(v) for k, v in obj.items()}
24 | value = {
25 | "type": "object",
26 | "properties": properties,
27 | }
28 | value.update({"required": [i for i in req]} if len(req) > 0 else {})
29 | value.update({"default": properties} if default else {})
30 | value.update({"example": properties} if example else {})
31 | return value
32 | elif isinstance(obj, list) and len(obj) > 0:
33 | properties = mine(obj[0])
34 | value = {
35 | "type": "array",
36 | "items": properties,
37 | }
38 | value.update({"default": [properties]} if default else {})
39 | value.update({"example": [properties]} if example else {})
40 | return value
41 | elif isinstance(obj, list) and len(obj) == 0:
42 | value = {
43 | "type": "array",
44 | "items": {"type": "object"},
45 | }
46 | value.update({"default": []} if default else {})
47 | value.update({"example": []} if example else {})
48 | return value
49 | elif isinstance(obj, str):
50 | return {"type": "string", "example": obj, "default": obj}
51 | elif isinstance(obj, bool):
52 | return {"type": "boolean", "example": obj, "default": obj}
53 | elif isinstance(obj, int):
54 | return {"type": "integer", "example": obj, "default": obj}
55 |
56 | def load_component(self, name: str) -> dict:
57 | with open(f"src/config/component/{name}.yaml", mode="r", encoding="utf-8") as f:
58 | return yaml.safe_load(f)
59 |
60 | def load_placeholder(self) -> dict:
61 | with open("src/config/placeholder.json", mode="r", encoding="utf-8") as f:
62 | return yaml.safe_load(f)
63 |
64 | # def load_user_agent(self) -> str:
65 | # user_agent = urllib3.PoolManager().request(
66 | # "GET",
67 | # "https://raw.githubusercontent.com/fa0311/latest-user-agent/main/output.json",
68 | # )
69 | # return json.loads(user_agent.data)["chrome-fetch"]
70 |
71 |
72 | # HookBase extends
73 |
74 |
75 | class OpenapiHookBase(HookBase):
76 | def hook(self, value: dict) -> dict:
77 | return value
78 |
79 |
80 | class OtherHookBase(HookBase):
81 | def hook(self) -> tuple[str, dict]:
82 | return "", {}
83 |
84 |
85 | class SchemasHookBase(HookBase):
86 | def hook(self, value: dict) -> dict:
87 | return value
88 |
89 |
90 | class RequestHookBase(HookBase):
91 | split: int
92 | path_name: str
93 |
94 | def __init__(self, split=1):
95 | super().__init__()
96 | self.split = split
97 |
98 | def hook(self, path: str, value: dict) -> tuple[str, dict]:
99 | value["parameters"] = value.get("parameters", [])
100 | self.path_name = "/".join(path.split("/")[self.split :])
101 | return path, value
102 |
103 |
104 | # OpenapiHookBase extends
105 |
106 |
107 | # class AddSecuritySchemesOnSecuritySchemes(OpenapiHookBase):
108 | # def hook(self, value: dict):
109 | # value = super().hook(value)
110 | # component = self.load_component("security_schemes")
111 | # param = component["components"]["securitySchemes"]
112 | # value["components"]["securitySchemes"].update(param)
113 | # value["security"].extend(component["security"])
114 | # return value
115 |
116 |
117 | # class SetUserAgentOnSecuritySchemes(OpenapiHookBase):
118 | # def hook(self, value: dict):
119 | # value = super().hook(value)
120 | # param = value["components"]["securitySchemes"]
121 | # param["UserAgent"]["description"] = self.load_user_agent()
122 | # return value
123 |
124 |
125 | # SchemasHookBase extends
126 |
127 |
128 | # class RemoveDiscriminator(SchemasHookBase):
129 | # def hook(self, value: dict):
130 | # if value.get("discriminator") is not None:
131 | # del value["discriminator"]
132 | # return value
133 |
134 |
135 | class SchemasCheck(SchemasHookBase):
136 | def hook(self, value: dict):
137 | if value.get("allOf") is not None:
138 | print("allOf is used")
139 | if value.get("type") is None:
140 | print("Type is None")
141 | return value
142 |
143 |
144 | class RequiredCheck(SchemasHookBase):
145 | def hook(self, value: dict):
146 | required = value.get("required", [])
147 |
148 | for key, property in value.get("properties", {}).items():
149 | if key in required and property.get("default") is not None:
150 | print(f"{key} is required and has default value")
151 | d = property.get("default") is None and property.get("nullable", False)
152 | if property not in required and d:
153 | print(f"{key} is not required and has no default value")
154 |
155 | return value
156 |
157 |
158 | # RequestHookBase extends
159 |
160 |
161 | # class AddSecuritySchemesOnHeader(RequestHookBase):
162 | # def hook(self, path: str, value: dict):
163 | # path, value = super().hook(path, value)
164 | # component = self.load_component("security_schemes")
165 | # param = component["paths"]["/parameters"]["get"]["parameters"]
166 | # value["parameters"].extend(param)
167 | # return path, value
168 |
169 |
170 | # class SetUserAgentOnHeader(RequestHookBase):
171 | # def hook(self, path: str, value: dict):
172 | # path, value = super().hook(path, value)
173 | # component = self.load_component("security_schemes")
174 | # param = component["paths"]["/parameters"]["get"]["parameters"]
175 | # value["parameters"].extend(param)
176 | # return path, value
177 |
178 |
179 | class ReplaceQueryIdPlaceholder(RequestHookBase):
180 | def hook(self, path: str, value: dict):
181 | path, value = super().hook(path, value)
182 | new = self.PLACEHOLDER[self.path_name]["queryId"]
183 | return path.replace(r"{pathQueryId}", new), value
184 |
185 |
186 | class SetResponsesHeader(RequestHookBase):
187 | suffix: str
188 |
189 | def __init__(self):
190 | super().__init__()
191 |
192 | def hook(self, path: str, value: dict):
193 | path, value = super().hook(path, value)
194 | component = self.load_component("response_header")
195 | value["responses"]["200"]["headers"] = component["components"]["headers"]
196 | return path, value
197 |
198 |
199 | class AddPathQueryIdOnParameters(RequestHookBase):
200 | def __init__(self, split: str = 1):
201 | super().__init__(split=split)
202 |
203 | def hook(self, path: str, value: dict):
204 | path, value = super().hook(path, value)
205 | data = self.PLACEHOLDER[self.path_name]
206 | value["parameters"].append(
207 | {
208 | "in": "path",
209 | "name": "pathQueryId",
210 | "required": True,
211 | "schema": {
212 | "type": "string",
213 | "default": data["queryId"],
214 | "example": data["queryId"],
215 | },
216 | }
217 | )
218 | return path, value
219 |
220 |
221 | # class RemoveErrorHandle(RequestHookBase):
222 | # def hook(self, path: str, value: dict):
223 | # content = value["responses"]["200"]["content"]["application/json"]
224 | # content["schema"] = content["schema"]["oneOf"][0]
225 | # return path, value
226 |
227 |
228 | # OnParameters
229 |
230 |
231 | class AddParametersOnParameters(RequestHookBase):
232 | schemaType: str | None
233 | ignoreKeys: list[str]
234 |
235 | def __init__(
236 | self,
237 | split: str = 1,
238 | schemaType: str | None = None,
239 | ignoreKeys: list[str] | None = None,
240 | ):
241 | super().__init__(split=split)
242 | self.schemaType = schemaType
243 | self.ignoreKeys = ignoreKeys or []
244 |
245 | def hook(self, path: str, value: dict):
246 | path, value = super().hook(path, value)
247 | data = self.PLACEHOLDER[self.path_name]
248 | data = {key: data[key] for key in data.keys() if key not in self.ignoreKeys}
249 |
250 | for key in data.keys():
251 | if self.schemaType == "string":
252 | example = (
253 | data[key] if isinstance(data[key], str) else json.dumps(data[key])
254 | )
255 | schema = {
256 | "type": "string",
257 | "default": example,
258 | "example": example,
259 | }
260 | elif self.schemaType == "object":
261 | example = (
262 | data[key] if isinstance(data[key], str) else json.dumps(data[key])
263 | )
264 | schema = {
265 | "type": "object",
266 | "default": example,
267 | "example": example,
268 | }
269 | else:
270 | schema = self.placeholder_to_yaml(data[key])
271 | value["parameters"].append(
272 | {
273 | "name": key,
274 | "in": "query",
275 | "required": True,
276 | "schema": schema,
277 | }
278 | )
279 | return path, value
280 |
281 |
282 | # OnBody
283 |
284 |
285 | class AddParametersOnBody(RequestHookBase):
286 | schemaType: str | None
287 | contentType: str | None
288 | ignoreKeys: list[str]
289 |
290 | def __init__(
291 | self,
292 | split: str = 1,
293 | contentType: str = "application/json",
294 | schemaType: str | None = None,
295 | ignoreKeys: list[str] | None = None,
296 | ):
297 | super().__init__(split)
298 | self.schemaType = schemaType
299 | self.contentType = contentType
300 | self.ignoreKeys = ignoreKeys or []
301 |
302 | def hook(self, path: str, value: dict):
303 | path, value = super().hook(path, value)
304 | data: dict[str, dict] = self.PLACEHOLDER[self.path_name]
305 | data = {k: v for k, v in data.items() if k not in self.ignoreKeys}
306 |
307 | if self.schemaType == "string":
308 | example = data if isinstance(data, str) else json.dumps(data)
309 | schema = {
310 | "type": "string",
311 | "default": example,
312 | "example": example,
313 | }
314 | elif self.schemaType == "object":
315 | example = data if isinstance(data, str) else json.dumps(data)
316 | schema = {
317 | "type": "object",
318 | "default": example,
319 | "example": example,
320 | }
321 | else:
322 | schema = {
323 | "properties": {k: self.placeholder_to_yaml(v) for k, v in data.items()},
324 | "required": [k for k in data.keys()],
325 | }
326 | value["requestBody"] = {
327 | "description": "body",
328 | "required": True,
329 | "content": {
330 | self.contentType: {
331 | "schema": schema,
332 | }
333 | },
334 | }
335 | return path, value
336 |
337 |
338 | # OnContent
339 |
340 |
341 | class AddParametersOnContent(RequestHookBase):
342 | contentType: str
343 | ignoreKeys: list[str]
344 |
345 | def __init__(
346 | self,
347 | split: str = 1,
348 | contentType: str = "application/json",
349 | ignoreKeys: list[str] | None = None,
350 | ):
351 | super().__init__(split=split)
352 | self.contentType = contentType
353 | self.ignoreKeys = ignoreKeys or []
354 |
355 | def hook(self, path: str, value: dict):
356 | path, value = super().hook(path, value)
357 | data = self.PLACEHOLDER[self.path_name]
358 | data = {key: data[key] for key in data.keys() if key not in self.ignoreKeys}
359 |
360 | for key in data.keys():
361 | value["parameters"].append(
362 | {
363 | "name": key,
364 | "in": "query",
365 | "required": True,
366 | "content": {
367 | self.contentType: {
368 | "schema": self.placeholder_to_yaml(data[key]),
369 | }
370 | },
371 | }
372 | )
373 | return path, value
374 |
--------------------------------------------------------------------------------
/tools/login.py:
--------------------------------------------------------------------------------
1 | import json
2 | import base64
3 | from tweepy_authlib import CookieSessionUserHandler
4 |
5 | auth_handler = CookieSessionUserHandler(
6 | screen_name=input("screen_name: "),
7 | password=input("password: "),
8 | )
9 |
10 |
11 | cookies = auth_handler.get_cookies()
12 |
13 | data = json.dumps(cookies.get_dict())
14 | print(base64.b64encode(data.encode("utf-8")).decode("utf-8"))
15 |
16 | with open("cookie.json", "w") as f:
17 | f.write(data)
18 |
--------------------------------------------------------------------------------