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 +21 -0
- gixenpy-0.2.0/PKG-INFO +327 -0
- gixenpy-0.2.0/README.md +296 -0
- gixenpy-0.2.0/pyproject.toml +48 -0
- gixenpy-0.2.0/setup.cfg +4 -0
- gixenpy-0.2.0/src/gixenpy/__init__.py +20 -0
- gixenpy-0.2.0/src/gixenpy/cli.py +206 -0
- gixenpy-0.2.0/src/gixenpy/client.py +786 -0
- gixenpy-0.2.0/src/gixenpy/forms.py +88 -0
- gixenpy-0.2.0/src/gixenpy/py.typed +0 -0
- gixenpy-0.2.0/src/gixenpy.egg-info/PKG-INFO +327 -0
- gixenpy-0.2.0/src/gixenpy.egg-info/SOURCES.txt +16 -0
- gixenpy-0.2.0/src/gixenpy.egg-info/dependency_links.txt +1 -0
- gixenpy-0.2.0/src/gixenpy.egg-info/entry_points.txt +2 -0
- gixenpy-0.2.0/src/gixenpy.egg-info/requires.txt +5 -0
- gixenpy-0.2.0/src/gixenpy.egg-info/top_level.txt +1 -0
- gixenpy-0.2.0/tests/test_cli.py +188 -0
- gixenpy-0.2.0/tests/test_client.py +820 -0
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
|
+
·
|
|
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/
|
gixenpy-0.2.0/README.md
ADDED
|
@@ -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
|
+
·
|
|
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/
|