gixenpy 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gixenpy-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 figurophobia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gixenpy-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,327 @@
1
+ Metadata-Version: 2.4
2
+ Name: gixenpy
3
+ Version: 0.2.0
4
+ Summary: Unofficial Python client for Gixen.com (eBay sniping) — automates its website since there's no public API.
5
+ Author: figurophobia
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/figurophobia/gixenpy
8
+ Project-URL: Repository, https://github.com/figurophobia/gixenpy
9
+ Project-URL: Issues, https://github.com/figurophobia/gixenpy/issues
10
+ Keywords: gixen,ebay,sniping,auction,scraping,bidding
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: requests>=2.32
27
+ Requires-Dist: click>=8.1
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ <a id="readme-top"></a>
33
+
34
+ [![Contributors][contributors-shield]][contributors-url]
35
+ [![Forks][forks-shield]][forks-url]
36
+ [![Stargazers][stars-shield]][stars-url]
37
+ [![Issues][issues-shield]][issues-url]
38
+ [![MIT License][license-shield]][license-url]
39
+
40
+ <br />
41
+ <div align="center">
42
+ <h3 align="center">gixenpy</h3>
43
+
44
+ <p align="center">
45
+ Unofficial Python client for Gixen.com (eBay sniping)
46
+ <br />
47
+ <a href="#usage"><strong>See usage examples »</strong></a>
48
+ <br />
49
+ <br />
50
+ <a href="https://github.com/figurophobia/gixenpy/issues/new?labels=bug">Report a bug</a>
51
+ &middot;
52
+ <a href="https://github.com/figurophobia/gixenpy/issues/new?labels=enhancement">Request a feature</a>
53
+ </p>
54
+ </div>
55
+
56
+ <details>
57
+ <summary>Table of Contents</summary>
58
+ <ol>
59
+ <li><a href="#about-the-project">About The Project</a>
60
+ <ul><li><a href="#built-with">Built With</a></li></ul>
61
+ </li>
62
+ <li><a href="#getting-started">Getting Started</a>
63
+ <ul>
64
+ <li><a href="#prerequisites">Prerequisites</a></li>
65
+ <li><a href="#installation">Installation</a></li>
66
+ </ul>
67
+ </li>
68
+ <li><a href="#usage">Usage</a>
69
+ <ul>
70
+ <li><a href="#as-a-library">As a library</a></li>
71
+ <li><a href="#as-a-cli">As a CLI</a></li>
72
+ </ul>
73
+ </li>
74
+ <li><a href="#security">Security</a></li>
75
+ <li><a href="#tests">Tests</a></li>
76
+ <li><a href="#publishing-to-pypi">Publishing to PyPI</a></li>
77
+ <li><a href="#roadmap">Roadmap</a></li>
78
+ <li><a href="#contributing">Contributing</a></li>
79
+ <li><a href="#license">License</a></li>
80
+ <li><a href="#contact">Contact</a></li>
81
+ <li><a href="#acknowledgments">Acknowledgments</a></li>
82
+ </ol>
83
+ </details>
84
+
85
+ ## About The Project
86
+
87
+ [Gixen](https://www.gixen.com/) used to offer a public API for scheduling
88
+ snipes (last-second eBay bids); that API is now **restricted to regular
89
+ users**. gixenpy solves that by automating Gixen's web form with `requests`,
90
+ just like a browser with JavaScript disabled would — without depending on an
91
+ API that's no longer available.
92
+
93
+ > Not a Gixen product, not affiliated with Gixen.com. Checked against their
94
+ > [terms of use](https://www.gixen.com/main/terms.php) and
95
+ > [FAQ](https://www.gixen.com/main/faq.php): they don't prohibit automated
96
+ > access, but use it at your own risk — Gixen may block traffic it doesn't
97
+ > recognize at its discretion.
98
+
99
+ What you can do with it:
100
+
101
+ - Schedule (`add_snipe`), list (`list_snipes`), edit (`update_snipe`), delete
102
+ (`delete_snipe`) and purge ended snipes (`purge_completed`).
103
+ - Bid, offset (seconds before the close) and bid group, all configurable; in
104
+ `update_snipe` each one is independent and optional (whatever isn't passed
105
+ is kept as-is).
106
+ - Tell active snipes apart from ended ones (`Snipe.status`).
107
+ - Dynamically locate Gixen's forms by parsing the HTML, instead of assuming
108
+ fixed field names: if Gixen changes their site, it keeps working as long
109
+ as the form structure doesn't change too much.
110
+ - **Dry-run** mode (the default for `add_snipe` unless told otherwise):
111
+ sends nothing, just reports what it would send.
112
+ - A full CLI: `gixenpy list/add/edit/remove/purge/group`.
113
+
114
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
115
+
116
+ ### Built With
117
+
118
+ * [![Python][Python.org]][Python-url]
119
+ * [![Requests][Requests.badge]][Requests-url]
120
+ * [![Click][Click.badge]][Click-url]
121
+
122
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
123
+
124
+ ## Getting Started
125
+
126
+ ### Prerequisites
127
+
128
+ * Python 3.10 or newer
129
+ * A [Gixen](https://www.gixen.com/) account (credentials are Gixen's, not
130
+ eBay's)
131
+
132
+ ### Installation
133
+
134
+ ```bash
135
+ pip install git+https://github.com/figurophobia/gixenpy.git
136
+ ```
137
+
138
+ For development (with tests):
139
+
140
+ ```bash
141
+ git clone https://github.com/figurophobia/gixenpy.git
142
+ cd gixenpy
143
+ pip install -e ".[dev]"
144
+ ```
145
+
146
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
147
+
148
+ ## Usage
149
+
150
+ ### As a library
151
+
152
+ ```python
153
+ from gixenpy import GixenClient
154
+
155
+ client = GixenClient(username="your_gixen_username", password="your_password", dry_run=True)
156
+
157
+ # Schedule a snipe (dry_run=True by default: doesn't bid for real).
158
+ result = client.add_snipe(item_id="123456789012", max_bid=42.50)
159
+ print(result.message)
160
+
161
+ # When you want to bid for real:
162
+ result = client.add_snipe(item_id="123456789012", max_bid=42.50, dry_run=False)
163
+
164
+ # Manage existing snipes.
165
+ for snipe in client.list_snipes():
166
+ print(snipe.item_id, snipe.max_bid, snipe.status) # "active" | "ended" | "unknown"
167
+
168
+ client.update_snipe(item_id="123456789012", new_max=55)
169
+ client.delete_snipe(item_id="123456789012")
170
+
171
+ # Offset (seconds before the close) and bid group, optional:
172
+ client.add_snipe(item_id="123456789012", max_bid=42.50, offset=9, group=1, dry_run=False)
173
+ client.update_snipe(item_id="123456789012", offset=12) # change only the offset
174
+ client.update_snipe(item_id="123456789012", group=2) # change only the group
175
+
176
+ client.purge_completed() # remove already-ended snipes from the history
177
+ ```
178
+
179
+ Credentials are your **Gixen** account's (not eBay's). Never logged or
180
+ printed.
181
+
182
+ ### As a CLI
183
+
184
+ ```bash
185
+ export GIXEN_USERNAME=your_username
186
+ export GIXEN_PASSWORD=your_password
187
+ # or create a .env with those two lines (see .env.example)
188
+
189
+ gixenpy --help # list of commands
190
+ gixenpy --version
191
+
192
+ gixenpy list # active and ended snipes
193
+
194
+ gixenpy add 123456789012 42.50 # schedules a snipe FOR REAL
195
+ gixenpy add 123456789012 42.50 --offset 9 --group 1 # with offset/group
196
+ gixenpy add 123456789012 42.50 --dry-run # just shows what would be sent
197
+
198
+ gixenpy edit 123456789012 55 # change the max bid
199
+ gixenpy edit 123456789012 --offset 12 # change only the offset (keeps the bid)
200
+ gixenpy edit 123456789012 --group 2 # change only the group
201
+
202
+ gixenpy remove 123456789012 # delete the snipe
203
+ gixenpy purge # purge ended snipes from the history
204
+
205
+ gixenpy group 3 123456789012 987654321098 # assign group 3 to several items
206
+ ```
207
+
208
+ Every command exits with code `0` if Gixen confirmed the operation, or `1`
209
+ if it failed/errored (message on stderr, prefixed with `ERROR:`).
210
+
211
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
212
+
213
+ ## Security
214
+
215
+ - A small, fixed number of requests per action (1 GET + 1 POST), no
216
+ aggressive polling.
217
+ - Reuses the existing session instead of relogging on every action
218
+ (avoiding unnecessarily kicking out other sessions — Gixen only allows one
219
+ active session per account).
220
+ - The max bid amount is always decided by whoever calls the library; it
221
+ never picks a value on its own.
222
+ - Any form URL extracted from Gixen's HTML is validated against
223
+ `gixen.com` (and HTTPS only) before sending anything; a tampered response
224
+ can't make the client send the payload to a different host.
225
+ - Configurable connect/read timeouts (`GixenClient(timeout=...)`, default
226
+ `(5, 25)` seconds) to fail fast on network issues.
227
+ - A single retry with backoff (`GixenClient(retry_backoff=...)`, 2 seconds
228
+ by default) if Gixen accepts a write but doesn't end up applying it —
229
+ never on an explicit rejection.
230
+
231
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
232
+
233
+ ## Tests
234
+
235
+ ```bash
236
+ pip install -e ".[dev]"
237
+ pytest -q
238
+ ```
239
+
240
+ Neither suite makes any network requests: `tests/test_client.py` replaces
241
+ Gixen's logged-in page with test HTML and verifies form parsing and the
242
+ payload built; `tests/test_cli.py` exercises the CLI with
243
+ `click.testing.CliRunner`, injecting a fake client.
244
+
245
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
246
+
247
+ ## Publishing to PyPI
248
+
249
+ The package is ready to publish (the `gixenpy` name is free on PyPI,
250
+ `py.typed`, classifiers, `python -m build` + `twine check` pass, the wheel
251
+ was verified by installing it into a clean venv). Steps left (manual,
252
+ require your own PyPI account):
253
+
254
+ ```bash
255
+ pip install -e ".[dev]" build twine
256
+ python -m build # generates dist/*.tar.gz and dist/*.whl
257
+ twine check dist/* # validates the package
258
+ twine upload -r testpypi dist/* # optional: try it on TestPyPI first
259
+ twine upload dist/* # real publication
260
+ ```
261
+
262
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
263
+
264
+ ## Roadmap
265
+
266
+ - [ ] Publish `gixenpy` on PyPI
267
+ - [ ] Persistent session across processes (today every new `GixenClient`
268
+ logs in if needed; there's no on-disk session cache)
269
+ - [ ] Revisit the active/ended status heuristic if Gixen changes the
270
+ text/order of "Status (main): ..." in their HTML
271
+
272
+ See the [open issues](https://github.com/figurophobia/gixenpy/issues) for
273
+ the full list of proposed features (and known issues).
274
+
275
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
276
+
277
+ ## Contributing
278
+
279
+ Contributions are welcome. If you have an idea that would make this better,
280
+ fork the repo and open a pull request, or simply open an issue with the
281
+ "enhancement" tag.
282
+
283
+ 1. Fork the Project
284
+ 2. Create your Feature Branch (`git checkout -b feature/my-feature`)
285
+ 3. Commit your Changes (`git commit -m 'Add my-feature'`)
286
+ 4. Push to the Branch (`git push origin feature/my-feature`)
287
+ 5. Open a Pull Request
288
+
289
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
290
+
291
+ ## License
292
+
293
+ Distributed under the MIT License. See [`LICENSE`](LICENSE) for more
294
+ information.
295
+
296
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
297
+
298
+ ## Contact
299
+
300
+ Project issues: [github.com/figurophobia/gixenpy/issues](https://github.com/figurophobia/gixenpy/issues)
301
+
302
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
303
+
304
+ ## Acknowledgments
305
+
306
+ * [hsukenooi/gixen-cli](https://github.com/hsukenooi/comic-pipeline/tree/main/packages/gixen-cli) — another independent implementation of the same problem, used as a reference for several improvements to this library
307
+ * [Best-README-Template](https://github.com/othneildrew/Best-README-Template) — the template used for this README
308
+ * [Gixen.com](https://www.gixen.com/) for the sniping service itself
309
+
310
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
311
+
312
+ [contributors-shield]: https://img.shields.io/github/contributors/figurophobia/gixenpy.svg?style=for-the-badge
313
+ [contributors-url]: https://github.com/figurophobia/gixenpy/graphs/contributors
314
+ [forks-shield]: https://img.shields.io/github/forks/figurophobia/gixenpy.svg?style=for-the-badge
315
+ [forks-url]: https://github.com/figurophobia/gixenpy/network/members
316
+ [stars-shield]: https://img.shields.io/github/stars/figurophobia/gixenpy.svg?style=for-the-badge
317
+ [stars-url]: https://github.com/figurophobia/gixenpy/stargazers
318
+ [issues-shield]: https://img.shields.io/github/issues/figurophobia/gixenpy.svg?style=for-the-badge
319
+ [issues-url]: https://github.com/figurophobia/gixenpy/issues
320
+ [license-shield]: https://img.shields.io/github/license/figurophobia/gixenpy.svg?style=for-the-badge
321
+ [license-url]: https://github.com/figurophobia/gixenpy/blob/master/LICENSE
322
+ [Python.org]: https://img.shields.io/badge/Python-3.10%2B-3776AB?style=for-the-badge&logo=python&logoColor=white
323
+ [Python-url]: https://www.python.org/
324
+ [Requests.badge]: https://img.shields.io/badge/Requests-2.32%2B-000000?style=for-the-badge&logo=python&logoColor=white
325
+ [Requests-url]: https://requests.readthedocs.io/
326
+ [Click.badge]: https://img.shields.io/badge/Click-8.1%2B-000000?style=for-the-badge&logo=python&logoColor=white
327
+ [Click-url]: https://click.palletsprojects.com/
@@ -0,0 +1,296 @@
1
+ <a id="readme-top"></a>
2
+
3
+ [![Contributors][contributors-shield]][contributors-url]
4
+ [![Forks][forks-shield]][forks-url]
5
+ [![Stargazers][stars-shield]][stars-url]
6
+ [![Issues][issues-shield]][issues-url]
7
+ [![MIT License][license-shield]][license-url]
8
+
9
+ <br />
10
+ <div align="center">
11
+ <h3 align="center">gixenpy</h3>
12
+
13
+ <p align="center">
14
+ Unofficial Python client for Gixen.com (eBay sniping)
15
+ <br />
16
+ <a href="#usage"><strong>See usage examples »</strong></a>
17
+ <br />
18
+ <br />
19
+ <a href="https://github.com/figurophobia/gixenpy/issues/new?labels=bug">Report a bug</a>
20
+ &middot;
21
+ <a href="https://github.com/figurophobia/gixenpy/issues/new?labels=enhancement">Request a feature</a>
22
+ </p>
23
+ </div>
24
+
25
+ <details>
26
+ <summary>Table of Contents</summary>
27
+ <ol>
28
+ <li><a href="#about-the-project">About The Project</a>
29
+ <ul><li><a href="#built-with">Built With</a></li></ul>
30
+ </li>
31
+ <li><a href="#getting-started">Getting Started</a>
32
+ <ul>
33
+ <li><a href="#prerequisites">Prerequisites</a></li>
34
+ <li><a href="#installation">Installation</a></li>
35
+ </ul>
36
+ </li>
37
+ <li><a href="#usage">Usage</a>
38
+ <ul>
39
+ <li><a href="#as-a-library">As a library</a></li>
40
+ <li><a href="#as-a-cli">As a CLI</a></li>
41
+ </ul>
42
+ </li>
43
+ <li><a href="#security">Security</a></li>
44
+ <li><a href="#tests">Tests</a></li>
45
+ <li><a href="#publishing-to-pypi">Publishing to PyPI</a></li>
46
+ <li><a href="#roadmap">Roadmap</a></li>
47
+ <li><a href="#contributing">Contributing</a></li>
48
+ <li><a href="#license">License</a></li>
49
+ <li><a href="#contact">Contact</a></li>
50
+ <li><a href="#acknowledgments">Acknowledgments</a></li>
51
+ </ol>
52
+ </details>
53
+
54
+ ## About The Project
55
+
56
+ [Gixen](https://www.gixen.com/) used to offer a public API for scheduling
57
+ snipes (last-second eBay bids); that API is now **restricted to regular
58
+ users**. gixenpy solves that by automating Gixen's web form with `requests`,
59
+ just like a browser with JavaScript disabled would — without depending on an
60
+ API that's no longer available.
61
+
62
+ > Not a Gixen product, not affiliated with Gixen.com. Checked against their
63
+ > [terms of use](https://www.gixen.com/main/terms.php) and
64
+ > [FAQ](https://www.gixen.com/main/faq.php): they don't prohibit automated
65
+ > access, but use it at your own risk — Gixen may block traffic it doesn't
66
+ > recognize at its discretion.
67
+
68
+ What you can do with it:
69
+
70
+ - Schedule (`add_snipe`), list (`list_snipes`), edit (`update_snipe`), delete
71
+ (`delete_snipe`) and purge ended snipes (`purge_completed`).
72
+ - Bid, offset (seconds before the close) and bid group, all configurable; in
73
+ `update_snipe` each one is independent and optional (whatever isn't passed
74
+ is kept as-is).
75
+ - Tell active snipes apart from ended ones (`Snipe.status`).
76
+ - Dynamically locate Gixen's forms by parsing the HTML, instead of assuming
77
+ fixed field names: if Gixen changes their site, it keeps working as long
78
+ as the form structure doesn't change too much.
79
+ - **Dry-run** mode (the default for `add_snipe` unless told otherwise):
80
+ sends nothing, just reports what it would send.
81
+ - A full CLI: `gixenpy list/add/edit/remove/purge/group`.
82
+
83
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
84
+
85
+ ### Built With
86
+
87
+ * [![Python][Python.org]][Python-url]
88
+ * [![Requests][Requests.badge]][Requests-url]
89
+ * [![Click][Click.badge]][Click-url]
90
+
91
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
92
+
93
+ ## Getting Started
94
+
95
+ ### Prerequisites
96
+
97
+ * Python 3.10 or newer
98
+ * A [Gixen](https://www.gixen.com/) account (credentials are Gixen's, not
99
+ eBay's)
100
+
101
+ ### Installation
102
+
103
+ ```bash
104
+ pip install git+https://github.com/figurophobia/gixenpy.git
105
+ ```
106
+
107
+ For development (with tests):
108
+
109
+ ```bash
110
+ git clone https://github.com/figurophobia/gixenpy.git
111
+ cd gixenpy
112
+ pip install -e ".[dev]"
113
+ ```
114
+
115
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
116
+
117
+ ## Usage
118
+
119
+ ### As a library
120
+
121
+ ```python
122
+ from gixenpy import GixenClient
123
+
124
+ client = GixenClient(username="your_gixen_username", password="your_password", dry_run=True)
125
+
126
+ # Schedule a snipe (dry_run=True by default: doesn't bid for real).
127
+ result = client.add_snipe(item_id="123456789012", max_bid=42.50)
128
+ print(result.message)
129
+
130
+ # When you want to bid for real:
131
+ result = client.add_snipe(item_id="123456789012", max_bid=42.50, dry_run=False)
132
+
133
+ # Manage existing snipes.
134
+ for snipe in client.list_snipes():
135
+ print(snipe.item_id, snipe.max_bid, snipe.status) # "active" | "ended" | "unknown"
136
+
137
+ client.update_snipe(item_id="123456789012", new_max=55)
138
+ client.delete_snipe(item_id="123456789012")
139
+
140
+ # Offset (seconds before the close) and bid group, optional:
141
+ client.add_snipe(item_id="123456789012", max_bid=42.50, offset=9, group=1, dry_run=False)
142
+ client.update_snipe(item_id="123456789012", offset=12) # change only the offset
143
+ client.update_snipe(item_id="123456789012", group=2) # change only the group
144
+
145
+ client.purge_completed() # remove already-ended snipes from the history
146
+ ```
147
+
148
+ Credentials are your **Gixen** account's (not eBay's). Never logged or
149
+ printed.
150
+
151
+ ### As a CLI
152
+
153
+ ```bash
154
+ export GIXEN_USERNAME=your_username
155
+ export GIXEN_PASSWORD=your_password
156
+ # or create a .env with those two lines (see .env.example)
157
+
158
+ gixenpy --help # list of commands
159
+ gixenpy --version
160
+
161
+ gixenpy list # active and ended snipes
162
+
163
+ gixenpy add 123456789012 42.50 # schedules a snipe FOR REAL
164
+ gixenpy add 123456789012 42.50 --offset 9 --group 1 # with offset/group
165
+ gixenpy add 123456789012 42.50 --dry-run # just shows what would be sent
166
+
167
+ gixenpy edit 123456789012 55 # change the max bid
168
+ gixenpy edit 123456789012 --offset 12 # change only the offset (keeps the bid)
169
+ gixenpy edit 123456789012 --group 2 # change only the group
170
+
171
+ gixenpy remove 123456789012 # delete the snipe
172
+ gixenpy purge # purge ended snipes from the history
173
+
174
+ gixenpy group 3 123456789012 987654321098 # assign group 3 to several items
175
+ ```
176
+
177
+ Every command exits with code `0` if Gixen confirmed the operation, or `1`
178
+ if it failed/errored (message on stderr, prefixed with `ERROR:`).
179
+
180
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
181
+
182
+ ## Security
183
+
184
+ - A small, fixed number of requests per action (1 GET + 1 POST), no
185
+ aggressive polling.
186
+ - Reuses the existing session instead of relogging on every action
187
+ (avoiding unnecessarily kicking out other sessions — Gixen only allows one
188
+ active session per account).
189
+ - The max bid amount is always decided by whoever calls the library; it
190
+ never picks a value on its own.
191
+ - Any form URL extracted from Gixen's HTML is validated against
192
+ `gixen.com` (and HTTPS only) before sending anything; a tampered response
193
+ can't make the client send the payload to a different host.
194
+ - Configurable connect/read timeouts (`GixenClient(timeout=...)`, default
195
+ `(5, 25)` seconds) to fail fast on network issues.
196
+ - A single retry with backoff (`GixenClient(retry_backoff=...)`, 2 seconds
197
+ by default) if Gixen accepts a write but doesn't end up applying it —
198
+ never on an explicit rejection.
199
+
200
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
201
+
202
+ ## Tests
203
+
204
+ ```bash
205
+ pip install -e ".[dev]"
206
+ pytest -q
207
+ ```
208
+
209
+ Neither suite makes any network requests: `tests/test_client.py` replaces
210
+ Gixen's logged-in page with test HTML and verifies form parsing and the
211
+ payload built; `tests/test_cli.py` exercises the CLI with
212
+ `click.testing.CliRunner`, injecting a fake client.
213
+
214
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
215
+
216
+ ## Publishing to PyPI
217
+
218
+ The package is ready to publish (the `gixenpy` name is free on PyPI,
219
+ `py.typed`, classifiers, `python -m build` + `twine check` pass, the wheel
220
+ was verified by installing it into a clean venv). Steps left (manual,
221
+ require your own PyPI account):
222
+
223
+ ```bash
224
+ pip install -e ".[dev]" build twine
225
+ python -m build # generates dist/*.tar.gz and dist/*.whl
226
+ twine check dist/* # validates the package
227
+ twine upload -r testpypi dist/* # optional: try it on TestPyPI first
228
+ twine upload dist/* # real publication
229
+ ```
230
+
231
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
232
+
233
+ ## Roadmap
234
+
235
+ - [ ] Publish `gixenpy` on PyPI
236
+ - [ ] Persistent session across processes (today every new `GixenClient`
237
+ logs in if needed; there's no on-disk session cache)
238
+ - [ ] Revisit the active/ended status heuristic if Gixen changes the
239
+ text/order of "Status (main): ..." in their HTML
240
+
241
+ See the [open issues](https://github.com/figurophobia/gixenpy/issues) for
242
+ the full list of proposed features (and known issues).
243
+
244
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
245
+
246
+ ## Contributing
247
+
248
+ Contributions are welcome. If you have an idea that would make this better,
249
+ fork the repo and open a pull request, or simply open an issue with the
250
+ "enhancement" tag.
251
+
252
+ 1. Fork the Project
253
+ 2. Create your Feature Branch (`git checkout -b feature/my-feature`)
254
+ 3. Commit your Changes (`git commit -m 'Add my-feature'`)
255
+ 4. Push to the Branch (`git push origin feature/my-feature`)
256
+ 5. Open a Pull Request
257
+
258
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
259
+
260
+ ## License
261
+
262
+ Distributed under the MIT License. See [`LICENSE`](LICENSE) for more
263
+ information.
264
+
265
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
266
+
267
+ ## Contact
268
+
269
+ Project issues: [github.com/figurophobia/gixenpy/issues](https://github.com/figurophobia/gixenpy/issues)
270
+
271
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
272
+
273
+ ## Acknowledgments
274
+
275
+ * [hsukenooi/gixen-cli](https://github.com/hsukenooi/comic-pipeline/tree/main/packages/gixen-cli) — another independent implementation of the same problem, used as a reference for several improvements to this library
276
+ * [Best-README-Template](https://github.com/othneildrew/Best-README-Template) — the template used for this README
277
+ * [Gixen.com](https://www.gixen.com/) for the sniping service itself
278
+
279
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
280
+
281
+ [contributors-shield]: https://img.shields.io/github/contributors/figurophobia/gixenpy.svg?style=for-the-badge
282
+ [contributors-url]: https://github.com/figurophobia/gixenpy/graphs/contributors
283
+ [forks-shield]: https://img.shields.io/github/forks/figurophobia/gixenpy.svg?style=for-the-badge
284
+ [forks-url]: https://github.com/figurophobia/gixenpy/network/members
285
+ [stars-shield]: https://img.shields.io/github/stars/figurophobia/gixenpy.svg?style=for-the-badge
286
+ [stars-url]: https://github.com/figurophobia/gixenpy/stargazers
287
+ [issues-shield]: https://img.shields.io/github/issues/figurophobia/gixenpy.svg?style=for-the-badge
288
+ [issues-url]: https://github.com/figurophobia/gixenpy/issues
289
+ [license-shield]: https://img.shields.io/github/license/figurophobia/gixenpy.svg?style=for-the-badge
290
+ [license-url]: https://github.com/figurophobia/gixenpy/blob/master/LICENSE
291
+ [Python.org]: https://img.shields.io/badge/Python-3.10%2B-3776AB?style=for-the-badge&logo=python&logoColor=white
292
+ [Python-url]: https://www.python.org/
293
+ [Requests.badge]: https://img.shields.io/badge/Requests-2.32%2B-000000?style=for-the-badge&logo=python&logoColor=white
294
+ [Requests-url]: https://requests.readthedocs.io/
295
+ [Click.badge]: https://img.shields.io/badge/Click-8.1%2B-000000?style=for-the-badge&logo=python&logoColor=white
296
+ [Click-url]: https://click.palletsprojects.com/