managebetcodes 1.0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Manage Bet Codes
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.
@@ -0,0 +1,270 @@
1
+ Metadata-Version: 2.4
2
+ Name: managebetcodes
3
+ Version: 1.0.0
4
+ Summary: Official Python SDK for the Manage Bet Codes public API v1
5
+ Author: Manage Bet Codes
6
+ License: MIT
7
+ Project-URL: Documentation, https://managebetcodes.app/developers/sdk/python
8
+ Project-URL: Source, https://github.com/kayceepeece/Viewbetbot
9
+ Keywords: betting,bet-codes,sportybet,1xbet
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=7; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # Manage Bet Codes Python SDK
22
+
23
+ The official Python SDK for the Manage Bet Codes public API v1.
24
+
25
+ - Python 3.9 or later.
26
+ - No runtime dependencies. The SDK uses the Python standard library.
27
+ - Synchronous client.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install managebetcodes
33
+ ```
34
+
35
+ ## Command line
36
+
37
+ The package installs an `mbc` command with the same client.
38
+
39
+ ```bash
40
+ export MBC_API_KEY=mbc_live_your_key
41
+ mbc convert 7B9A2F --from sportybet --to 1xng
42
+ mbc view 7B9A2F --bookmaker sportybet
43
+ mbc usage
44
+ ```
45
+
46
+ ## Quick start
47
+
48
+ ```python
49
+ from managebetcodes import Client
50
+
51
+ client = Client("mbc_live_your_key")
52
+
53
+ result = client.convert("7B9A2F", "sportybet", "1xng")
54
+ print(result["target"]["code"])
55
+ ```
56
+
57
+ The client uses this base URL by default:
58
+
59
+ ```text
60
+ https://api.managebetcodes.app/v1
61
+ ```
62
+
63
+ Change the base URL for a test server:
64
+
65
+ ```python
66
+ client = Client("mbc_live_your_key", base_url="http://127.0.0.1:8000/v1")
67
+ ```
68
+
69
+ ## Authentication
70
+
71
+ Pass the API key to the constructor.
72
+ The SDK sends the key in the `X-API-Key` header.
73
+ Keys start with `mbc_live_`.
74
+ Keep the key on the server.
75
+ Do not put the key in browser code.
76
+
77
+ ## Client options
78
+
79
+ ```python
80
+ Client(
81
+ api_key,
82
+ base_url="https://api.managebetcodes.app/v1",
83
+ timeout=30,
84
+ max_retries=3,
85
+ )
86
+ ```
87
+
88
+ - `timeout` is the socket timeout in seconds.
89
+ - `max_retries` is the number of extra attempts.
90
+
91
+ ## Methods
92
+
93
+ | Method | Call | Price |
94
+ |---|---|---|
95
+ | `convert(code, source, target)` | `POST /convert` | 2 credits per target |
96
+ | `convert_to_all(code, source, to=...)` | `POST /convert/all` | 2 credits per target |
97
+ | `convert_bulk(codes, to=...)` | `POST /convert/bulk` | 2 credits per pair |
98
+ | `view(code, bookmaker)` | `GET /view` | 1 credit |
99
+ | `view_image(code, bookmaker)` | `GET /view/image` | 2 credits |
100
+ | `split(code, bookmaker, count=2, ...)` | `POST /split` | 2 credits plus 1 per extra part |
101
+ | `merge(pairs, target=None)` | `POST /merge` | 2 credits plus 1 per extra code |
102
+ | `book(bookmaker, plan_id=..., picks=...)` | `POST /book` | 2 credits per code |
103
+ | `events_search(bookmaker, queries)` | `POST /events/search` | 0 credits |
104
+ | `events(q=...)` | `GET /events` | 0 credits |
105
+ | `event_markets(event_id)` | `GET /events/{id}/markets` | 0 credits |
106
+ | `resolve(bookmaker, picks)` | `POST /resolve` | 0 credits |
107
+ | `markets()` | `GET /markets` | 0 credits |
108
+ | `books(limit=..., offset=...)` | `GET /books` | 0 credits |
109
+ | `capabilities()` | `GET /capabilities` | 0 credits |
110
+ | `usage()` | `GET /usage` | 0 credits |
111
+ | `history(limit=..., offset=...)` | `GET /history` | 0 credits |
112
+ | `jobs_create(payload)` | `POST /jobs` | Job price |
113
+ | `job(job_id)` | `GET /jobs/{id}` | 0 credits |
114
+ | `jobs()` | `GET /jobs` | 0 credits |
115
+ | `cancel_job(job_id)` | `DELETE /jobs/{id}` | 0 credits |
116
+ | `track_start(code, bookmaker)` | `POST /track` | 1 credit |
117
+ | `track(track_id)` | `GET /track/{id}` | 0 credits |
118
+ | `tracks()` | `GET /tracks` | 0 credits |
119
+ | `stop_track(track_id)` | `DELETE /track/{id}` | 0 credits |
120
+
121
+ The methods return the JSON response as a `dict`.
122
+ `view_image` returns `bytes`.
123
+
124
+ ## Convert
125
+
126
+ Convert one code to one target:
127
+
128
+ ```python
129
+ client.convert("7B9A2F", "sportybet", "1xng")
130
+ ```
131
+
132
+ Convert one code to a batch:
133
+
134
+ ```python
135
+ client.convert("7B9A2F", "sportybet", ["1xng", "msport", "bet9ja"])
136
+ ```
137
+
138
+ A batch accepts 2 to 6 targets.
139
+ Use a job for more targets.
140
+
141
+ ## Book
142
+
143
+ Book from a resolve plan:
144
+
145
+ ```python
146
+ client.book("sportybet", plan_id="plan_123")
147
+ ```
148
+
149
+ Book from direct picks:
150
+
151
+ ```python
152
+ client.book(
153
+ "sportybet",
154
+ picks=[{"event_id": "sr:match:123", "market_id": "18",
155
+ "outcome_id": "12", "odds": "1.90"}],
156
+ )
157
+ ```
158
+
159
+ ## Jobs
160
+
161
+ `jobs_create` accepts a job body:
162
+
163
+ ```python
164
+ job = client.jobs_create({
165
+ "kind": "convert",
166
+ "code": "7B9A2F",
167
+ "from": "sportybet",
168
+ "to": ["1xng", "msport", "bet9ja", "melbet"],
169
+ })
170
+ client.job(job["id"])
171
+ ```
172
+
173
+ ## Image
174
+
175
+ ```python
176
+ png = client.view_image("7B9A2F", "sportybet")
177
+ with open("slip.png", "wb") as file:
178
+ file.write(png)
179
+ ```
180
+
181
+ The method returns the image bytes.
182
+ It raises a typed error when the API returns an error envelope.
183
+
184
+ ## Idempotency
185
+
186
+ The SDK adds an `Idempotency-Key` header to every POST call.
187
+ Pass your own key when you need a stable value:
188
+
189
+ ```python
190
+ client.convert("7B9A2F", "sportybet", "1xng", idempotency_key="order-123")
191
+ ```
192
+
193
+ The SDK sends the same key on a retry.
194
+
195
+ ## Retries
196
+
197
+ The SDK retries on these status codes:
198
+
199
+ - `429`
200
+ - `502`
201
+ - `503`
202
+ - `504`
203
+
204
+ The SDK reads the `Retry-After` header.
205
+ If the header is absent, the SDK uses exponential backoff.
206
+
207
+ ## Errors
208
+
209
+ All errors inherit from `APIError`:
210
+
211
+ | Error | Status | Meaning |
212
+ |---|---|---|
213
+ | `AuthenticationError` | 401, 403 | The key is missing, bad, or not allowed |
214
+ | `QuotaError` | 429 | The plan credits or search cap are finished |
215
+ | `RateLimitError` | 429 | The request rate is too high |
216
+ | `ValidationError` | 400, 422 | A parameter or the body is invalid |
217
+ | `APIError` | Other | Any other API error |
218
+
219
+ Each error has these attributes:
220
+
221
+ - `status`
222
+ - `code`
223
+ - `message`
224
+ - `request_id`
225
+ - `docs`
226
+ - `details`
227
+
228
+ `RateLimitError` also has `retry_after`.
229
+
230
+ ```python
231
+ from managebetcodes import RateLimitError, ValidationError
232
+
233
+ try:
234
+ client.convert("BAD", "sportybet", "1xng")
235
+ except ValidationError as error:
236
+ print(error.code, error.details)
237
+ except RateLimitError as error:
238
+ print("Retry after", error.retry_after)
239
+ ```
240
+
241
+ ## Webhooks
242
+
243
+ Verify the raw request body before you trust a webhook:
244
+
245
+ ```python
246
+ from managebetcodes import verify_webhook
247
+
248
+ if not verify_webhook(secret, raw_body, request.headers["X-MBC-Signature"]):
249
+ return "invalid signature", 400
250
+ ```
251
+
252
+ `raw_body` must be `bytes`.
253
+ Do not parse the body before the check.
254
+ The function returns `True` for a valid signature.
255
+ It returns `False` for a bad signature.
256
+
257
+ ## Pagination
258
+
259
+ Pass `limit` and `offset` to `history`, `jobs`, `books`, and `events`.
260
+ The response returns `next_offset` when another page exists.
261
+
262
+ ```python
263
+ page = client.history(limit=20, offset=0)
264
+ for item in page["items"]:
265
+ print(item["id"])
266
+ ```
267
+
268
+ ## License
269
+
270
+ MIT
@@ -0,0 +1,250 @@
1
+ # Manage Bet Codes Python SDK
2
+
3
+ The official Python SDK for the Manage Bet Codes public API v1.
4
+
5
+ - Python 3.9 or later.
6
+ - No runtime dependencies. The SDK uses the Python standard library.
7
+ - Synchronous client.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install managebetcodes
13
+ ```
14
+
15
+ ## Command line
16
+
17
+ The package installs an `mbc` command with the same client.
18
+
19
+ ```bash
20
+ export MBC_API_KEY=mbc_live_your_key
21
+ mbc convert 7B9A2F --from sportybet --to 1xng
22
+ mbc view 7B9A2F --bookmaker sportybet
23
+ mbc usage
24
+ ```
25
+
26
+ ## Quick start
27
+
28
+ ```python
29
+ from managebetcodes import Client
30
+
31
+ client = Client("mbc_live_your_key")
32
+
33
+ result = client.convert("7B9A2F", "sportybet", "1xng")
34
+ print(result["target"]["code"])
35
+ ```
36
+
37
+ The client uses this base URL by default:
38
+
39
+ ```text
40
+ https://api.managebetcodes.app/v1
41
+ ```
42
+
43
+ Change the base URL for a test server:
44
+
45
+ ```python
46
+ client = Client("mbc_live_your_key", base_url="http://127.0.0.1:8000/v1")
47
+ ```
48
+
49
+ ## Authentication
50
+
51
+ Pass the API key to the constructor.
52
+ The SDK sends the key in the `X-API-Key` header.
53
+ Keys start with `mbc_live_`.
54
+ Keep the key on the server.
55
+ Do not put the key in browser code.
56
+
57
+ ## Client options
58
+
59
+ ```python
60
+ Client(
61
+ api_key,
62
+ base_url="https://api.managebetcodes.app/v1",
63
+ timeout=30,
64
+ max_retries=3,
65
+ )
66
+ ```
67
+
68
+ - `timeout` is the socket timeout in seconds.
69
+ - `max_retries` is the number of extra attempts.
70
+
71
+ ## Methods
72
+
73
+ | Method | Call | Price |
74
+ |---|---|---|
75
+ | `convert(code, source, target)` | `POST /convert` | 2 credits per target |
76
+ | `convert_to_all(code, source, to=...)` | `POST /convert/all` | 2 credits per target |
77
+ | `convert_bulk(codes, to=...)` | `POST /convert/bulk` | 2 credits per pair |
78
+ | `view(code, bookmaker)` | `GET /view` | 1 credit |
79
+ | `view_image(code, bookmaker)` | `GET /view/image` | 2 credits |
80
+ | `split(code, bookmaker, count=2, ...)` | `POST /split` | 2 credits plus 1 per extra part |
81
+ | `merge(pairs, target=None)` | `POST /merge` | 2 credits plus 1 per extra code |
82
+ | `book(bookmaker, plan_id=..., picks=...)` | `POST /book` | 2 credits per code |
83
+ | `events_search(bookmaker, queries)` | `POST /events/search` | 0 credits |
84
+ | `events(q=...)` | `GET /events` | 0 credits |
85
+ | `event_markets(event_id)` | `GET /events/{id}/markets` | 0 credits |
86
+ | `resolve(bookmaker, picks)` | `POST /resolve` | 0 credits |
87
+ | `markets()` | `GET /markets` | 0 credits |
88
+ | `books(limit=..., offset=...)` | `GET /books` | 0 credits |
89
+ | `capabilities()` | `GET /capabilities` | 0 credits |
90
+ | `usage()` | `GET /usage` | 0 credits |
91
+ | `history(limit=..., offset=...)` | `GET /history` | 0 credits |
92
+ | `jobs_create(payload)` | `POST /jobs` | Job price |
93
+ | `job(job_id)` | `GET /jobs/{id}` | 0 credits |
94
+ | `jobs()` | `GET /jobs` | 0 credits |
95
+ | `cancel_job(job_id)` | `DELETE /jobs/{id}` | 0 credits |
96
+ | `track_start(code, bookmaker)` | `POST /track` | 1 credit |
97
+ | `track(track_id)` | `GET /track/{id}` | 0 credits |
98
+ | `tracks()` | `GET /tracks` | 0 credits |
99
+ | `stop_track(track_id)` | `DELETE /track/{id}` | 0 credits |
100
+
101
+ The methods return the JSON response as a `dict`.
102
+ `view_image` returns `bytes`.
103
+
104
+ ## Convert
105
+
106
+ Convert one code to one target:
107
+
108
+ ```python
109
+ client.convert("7B9A2F", "sportybet", "1xng")
110
+ ```
111
+
112
+ Convert one code to a batch:
113
+
114
+ ```python
115
+ client.convert("7B9A2F", "sportybet", ["1xng", "msport", "bet9ja"])
116
+ ```
117
+
118
+ A batch accepts 2 to 6 targets.
119
+ Use a job for more targets.
120
+
121
+ ## Book
122
+
123
+ Book from a resolve plan:
124
+
125
+ ```python
126
+ client.book("sportybet", plan_id="plan_123")
127
+ ```
128
+
129
+ Book from direct picks:
130
+
131
+ ```python
132
+ client.book(
133
+ "sportybet",
134
+ picks=[{"event_id": "sr:match:123", "market_id": "18",
135
+ "outcome_id": "12", "odds": "1.90"}],
136
+ )
137
+ ```
138
+
139
+ ## Jobs
140
+
141
+ `jobs_create` accepts a job body:
142
+
143
+ ```python
144
+ job = client.jobs_create({
145
+ "kind": "convert",
146
+ "code": "7B9A2F",
147
+ "from": "sportybet",
148
+ "to": ["1xng", "msport", "bet9ja", "melbet"],
149
+ })
150
+ client.job(job["id"])
151
+ ```
152
+
153
+ ## Image
154
+
155
+ ```python
156
+ png = client.view_image("7B9A2F", "sportybet")
157
+ with open("slip.png", "wb") as file:
158
+ file.write(png)
159
+ ```
160
+
161
+ The method returns the image bytes.
162
+ It raises a typed error when the API returns an error envelope.
163
+
164
+ ## Idempotency
165
+
166
+ The SDK adds an `Idempotency-Key` header to every POST call.
167
+ Pass your own key when you need a stable value:
168
+
169
+ ```python
170
+ client.convert("7B9A2F", "sportybet", "1xng", idempotency_key="order-123")
171
+ ```
172
+
173
+ The SDK sends the same key on a retry.
174
+
175
+ ## Retries
176
+
177
+ The SDK retries on these status codes:
178
+
179
+ - `429`
180
+ - `502`
181
+ - `503`
182
+ - `504`
183
+
184
+ The SDK reads the `Retry-After` header.
185
+ If the header is absent, the SDK uses exponential backoff.
186
+
187
+ ## Errors
188
+
189
+ All errors inherit from `APIError`:
190
+
191
+ | Error | Status | Meaning |
192
+ |---|---|---|
193
+ | `AuthenticationError` | 401, 403 | The key is missing, bad, or not allowed |
194
+ | `QuotaError` | 429 | The plan credits or search cap are finished |
195
+ | `RateLimitError` | 429 | The request rate is too high |
196
+ | `ValidationError` | 400, 422 | A parameter or the body is invalid |
197
+ | `APIError` | Other | Any other API error |
198
+
199
+ Each error has these attributes:
200
+
201
+ - `status`
202
+ - `code`
203
+ - `message`
204
+ - `request_id`
205
+ - `docs`
206
+ - `details`
207
+
208
+ `RateLimitError` also has `retry_after`.
209
+
210
+ ```python
211
+ from managebetcodes import RateLimitError, ValidationError
212
+
213
+ try:
214
+ client.convert("BAD", "sportybet", "1xng")
215
+ except ValidationError as error:
216
+ print(error.code, error.details)
217
+ except RateLimitError as error:
218
+ print("Retry after", error.retry_after)
219
+ ```
220
+
221
+ ## Webhooks
222
+
223
+ Verify the raw request body before you trust a webhook:
224
+
225
+ ```python
226
+ from managebetcodes import verify_webhook
227
+
228
+ if not verify_webhook(secret, raw_body, request.headers["X-MBC-Signature"]):
229
+ return "invalid signature", 400
230
+ ```
231
+
232
+ `raw_body` must be `bytes`.
233
+ Do not parse the body before the check.
234
+ The function returns `True` for a valid signature.
235
+ It returns `False` for a bad signature.
236
+
237
+ ## Pagination
238
+
239
+ Pass `limit` and `offset` to `history`, `jobs`, `books`, and `events`.
240
+ The response returns `next_offset` when another page exists.
241
+
242
+ ```python
243
+ page = client.history(limit=20, offset=0)
244
+ for item in page["items"]:
245
+ print(item["id"])
246
+ ```
247
+
248
+ ## License
249
+
250
+ MIT
@@ -0,0 +1,22 @@
1
+ """Official SDK for the Manage Bet Codes public API v1."""
2
+
3
+ from .client import Client, __version__
4
+ from .errors import (
5
+ APIError,
6
+ AuthenticationError,
7
+ QuotaError,
8
+ RateLimitError,
9
+ ValidationError,
10
+ )
11
+ from .webhooks import verify_webhook
12
+
13
+ __all__ = [
14
+ "APIError",
15
+ "AuthenticationError",
16
+ "Client",
17
+ "QuotaError",
18
+ "RateLimitError",
19
+ "ValidationError",
20
+ "verify_webhook",
21
+ "__version__",
22
+ ]
@@ -0,0 +1,121 @@
1
+ """The `mbc` command: convert and view codes from a terminal."""
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import sys
7
+ import urllib.error
8
+ import urllib.request
9
+ from typing import Any, Dict, List, Optional, Sequence
10
+
11
+ from .client import BASE_URL, Client, __version__
12
+ from .errors import APIError
13
+
14
+
15
+ def build_parser() -> argparse.ArgumentParser:
16
+ """Build the `mbc` argument parser."""
17
+
18
+ common = argparse.ArgumentParser(add_help=False)
19
+ common.add_argument(
20
+ "--api-key",
21
+ default=os.environ.get("MBC_API_KEY"),
22
+ help="API key (or set MBC_API_KEY). Not needed for capabilities.",
23
+ )
24
+ common.add_argument(
25
+ "--base-url",
26
+ default=os.environ.get("MBC_BASE_URL", BASE_URL),
27
+ help="API base URL (or set MBC_BASE_URL).",
28
+ )
29
+ parser = argparse.ArgumentParser(
30
+ prog="mbc",
31
+ description="Manage Bet Codes public API v1",
32
+ parents=[common],
33
+ )
34
+ parser.add_argument(
35
+ "--version",
36
+ action="version",
37
+ version=f"%(prog)s {__version__}",
38
+ )
39
+ sub = parser.add_subparsers(dest="command", required=True, metavar="command")
40
+
41
+ convert = sub.add_parser(
42
+ "convert", parents=[common], help="Convert a code to one or more targets."
43
+ )
44
+ convert.add_argument("code", help="Source booking code.")
45
+ convert.add_argument("--from", dest="source", required=True, help="Source bookmaker.")
46
+ convert.add_argument(
47
+ "--to",
48
+ dest="targets",
49
+ required=True,
50
+ help="Comma-separated target list, e.g. 1xng or 1xng,msport.",
51
+ )
52
+
53
+ view = sub.add_parser("view", parents=[common], help="View a slip as text.")
54
+ view.add_argument("code", help="Booking code.")
55
+ view.add_argument("--bookmaker", required=True, help="Bookmaker of the code.")
56
+
57
+ sub.add_parser("capabilities", parents=[common], help="Show the API manifest.")
58
+ sub.add_parser("usage", parents=[common], help="Show plan, credits, and limits.")
59
+ return parser
60
+
61
+
62
+ def _targets(raw: str) -> List[str]:
63
+ return [part.strip() for part in raw.split(",") if part.strip()]
64
+
65
+
66
+ def _public_capabilities(base_url: str) -> int:
67
+ """Read the public manifest without an API key."""
68
+
69
+ url = base_url.rstrip("/") + "/capabilities"
70
+ request = urllib.request.Request(url, headers={"Accept": "application/json"})
71
+ try:
72
+ with urllib.request.urlopen(request, timeout=30) as response:
73
+ print(json.dumps(json.loads(response.read().decode("utf-8")), indent=2))
74
+ return 0
75
+ except urllib.error.HTTPError as exc:
76
+ print(f"mbc: HTTP {exc.code} [http_{exc.code}]: the manifest read failed.",
77
+ file=sys.stderr)
78
+ return 1
79
+ except (urllib.error.URLError, TimeoutError) as exc:
80
+ print(f"mbc: the request to the API failed: {exc}", file=sys.stderr)
81
+ return 1
82
+
83
+
84
+ def _print(payload: Dict[str, Any]) -> int:
85
+ print(json.dumps(payload, indent=2))
86
+ return 0
87
+
88
+
89
+ def main(argv: Optional[Sequence[str]] = None) -> int:
90
+ """Run the `mbc` command. Returns the process exit code."""
91
+
92
+ args = build_parser().parse_args(argv)
93
+ if args.command == "capabilities":
94
+ return _public_capabilities(args.base_url)
95
+ if not args.api_key:
96
+ print("mbc: set --api-key or MBC_API_KEY.", file=sys.stderr)
97
+ return 2
98
+ client = Client(args.api_key, base_url=args.base_url)
99
+ try:
100
+ if args.command == "convert":
101
+ targets = _targets(args.targets)
102
+ if not targets:
103
+ print("mbc: --to needs at least one target.", file=sys.stderr)
104
+ return 2
105
+ target = targets[0] if len(targets) == 1 else targets
106
+ return _print(client.convert(args.code, args.source, target))
107
+ if args.command == "view":
108
+ return _print(client.view(args.code, args.bookmaker))
109
+ if args.command == "capabilities":
110
+ return _print(client.capabilities())
111
+ if args.command == "usage":
112
+ return _print(client.usage())
113
+ except APIError as error:
114
+ print(f"mbc: {error}", file=sys.stderr)
115
+ return 1
116
+ print(f"mbc: unknown command {args.command}.", file=sys.stderr)
117
+ return 2
118
+
119
+
120
+ if __name__ == "__main__":
121
+ raise SystemExit(main())