lbenc 1.1.0__py3-none-any.whl

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,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: lbenc
3
+ Version: 1.1.0
4
+ Summary: Telegram file-processing bot with interactive configuration
5
+ License: MIT
6
+ Keywords: telegram,bot,python,zip,source transformation
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: pyTelegramBotAPI>=4.16.1
11
+ Dynamic: license-file
12
+
13
+ # LBENC
14
+
15
+ A Telegram file-processing bot with a terminal configuration menu.
16
+
17
+ ## Install locally
18
+
19
+ ```bash
20
+ python -m pip install .
21
+ ```
22
+
23
+ ## Run
24
+
25
+ ```bash
26
+ lbenc YOUR_TELEGRAM_BOT_TOKEN
27
+ ```
28
+
29
+ In an interactive terminal, the configuration menu opens first. Select `0` to save and start the bot.
30
+
31
+ To change settings later:
32
+
33
+ ```bash
34
+ lbenc --configure
35
+ ```
36
+
37
+ You can also set `TELEGRAM_BOT_TOKEN` in your environment and run `lbenc`.
38
+
39
+ ## Configuration options
40
+
41
+ 1. Change inline button URLs
42
+ 2. Change `/start` message (enter `/done` on its own line to finish)
43
+ 3. Change welcome image URL
44
+ 4. View configuration
45
+ 5. Reset configuration
46
+ 0. Save and continue to bot
47
+
48
+ Settings are stored in `lbenc_config.json` in the current working directory.
49
+
50
+ ## PyPI publishing
51
+
52
+ The package is prepared for PyPI, but it is not published automatically by this ZIP. First verify the name is available, then build and upload with your PyPI account:
53
+
54
+ ```bash
55
+ python -m pip install build twine
56
+ python -m build
57
+ python -m twine upload dist/*
58
+ ```
59
+
60
+ ## Note about the 30 methods
61
+
62
+ The 30 menu choices are text transformations and representations. Base encodings, compression, ROT13, XOR, and Caesar shifts are not secure encryption. Hash methods are one-way, and Marshal output depends on the Python version. Outputs are delivered as `.txt` because many options do not produce executable Python source.
63
+
64
+ Uploaded Python files are treated as data and are not executed by the bot.
@@ -0,0 +1,7 @@
1
+ lbenc.py,sha256=CDiTRUGdCmxT0W7yF6hozSlKmtT7iCMFBDkeYx0O398,16923
2
+ lbenc-1.1.0.dist-info/licenses/LICENSE,sha256=c35Q9u0AzYFQ3NXGzTkwHpMoL-6ST-JGG-LUvC3sXqE,816
3
+ lbenc-1.1.0.dist-info/METADATA,sha256=ctgFK0mOy77Ts74ZfnQ_jVgjLREYIR09KDGXXD4NwlE,1770
4
+ lbenc-1.1.0.dist-info/WHEEL,sha256=a-udL2m4V_6Yn8b-uKzWb12RjRxwwMo6ohYFM24tpaY,91
5
+ lbenc-1.1.0.dist-info/entry_points.txt,sha256=G5Oz6siJin1NNFKYkQOmN882QZgRQk5Ch-xPSUZcTsQ,37
6
+ lbenc-1.1.0.dist-info/top_level.txt,sha256=bHWiA2dsBUnnN0npzj5llS9TuiwqiUelVJgclwAcOiw,6
7
+ lbenc-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ lbenc = lbenc:main
@@ -0,0 +1,17 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LBENC contributors
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.
@@ -0,0 +1 @@
1
+ lbenc
lbenc.py ADDED
@@ -0,0 +1,501 @@
1
+ #!/usr/bin/env python3
2
+ """LBENC — Telegram file-processing bot with an interactive configuration menu."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import io
8
+ import json
9
+ import os
10
+ import re
11
+ import sys
12
+ import tempfile
13
+ import zipfile
14
+ from pathlib import Path
15
+ from threading import Lock
16
+ from urllib.parse import urlparse
17
+
18
+ try:
19
+ import telebot
20
+ from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
21
+ except ImportError:
22
+ telebot = None
23
+ InlineKeyboardMarkup = InlineKeyboardButton = None
24
+
25
+ APP_NAME = "LBENC"
26
+ APP_VERSION = "1.1.0"
27
+ CONFIG_FILE = Path.cwd() / "lbenc_config.json"
28
+ MAX_FILE_SIZE = 20 * 1024 * 1024
29
+ STATE_LOCK = Lock()
30
+ USER_FILES = {}
31
+
32
+ DEFAULT_CONFIG = {
33
+ "channel_url": "https://t.me/",
34
+ "developer_url": "https://t.me/",
35
+ "start_message": (
36
+ "🛡️ Welcome to LBENC Bot!\\n\\n"
37
+ "Send me a file to process it.\\n"
38
+ "• Python files: choose a text transformation\\n"
39
+ "• Any file: convert it to a ZIP archive"
40
+ ),
41
+ "image_url": "https://iili.io/F9VZX9f.jpg",
42
+ }
43
+
44
+ # These options provide text transformations/representations, not 30 secure
45
+ # encryption algorithms. Hashes are one-way; marshal is Python-version-specific.
46
+ def _b64(text):
47
+ import base64
48
+ return base64.b64encode(text.encode("utf-8")).decode("ascii")
49
+
50
+ def _b32(text):
51
+ import base64
52
+ return base64.b32encode(text.encode("utf-8")).decode("ascii")
53
+
54
+ def _b16(text):
55
+ import base64
56
+ return base64.b16encode(text.encode("utf-8")).decode("ascii")
57
+
58
+ def _zlib(text):
59
+ import base64, zlib
60
+ return base64.b64encode(zlib.compress(text.encode("utf-8"))).decode("ascii")
61
+
62
+ def _marshal(text):
63
+ import base64, marshal
64
+ return base64.b64encode(marshal.dumps(compile(text, "<lbenc>", "exec"))).decode("ascii")
65
+
66
+ def _xor(text, key):
67
+ return bytes(byte ^ key for byte in text.encode("utf-8")).hex()
68
+
69
+ def _caesar(text, shift):
70
+ return "".join(chr((ord(ch) + shift) % 0x110000) for ch in text)
71
+
72
+ def _rot13_b64(text):
73
+ import base64, codecs
74
+ return base64.b64encode(codecs.encode(text, "rot_13").encode("utf-8")).decode("ascii")
75
+
76
+ def _gzip(text):
77
+ import base64, gzip
78
+ return base64.b64encode(gzip.compress(text.encode("utf-8"))).decode("ascii")
79
+
80
+ def _bz2(text):
81
+ import base64, bz2
82
+ return base64.b64encode(bz2.compress(text.encode("utf-8"))).decode("ascii")
83
+
84
+ def _lzma(text):
85
+ import base64, lzma
86
+ return base64.b64encode(lzma.compress(text.encode("utf-8"))).decode("ascii")
87
+
88
+ def _binary(text):
89
+ return " ".join(format(byte, "08b") for byte in text.encode("utf-8"))
90
+
91
+ def _unicode_escape(text):
92
+ return text.encode("unicode_escape").decode("ascii")
93
+
94
+ def _json_string(text):
95
+ return json.dumps(text, ensure_ascii=True)
96
+
97
+ def _sha256(text):
98
+ import hashlib
99
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()
100
+
101
+ def _sha3(text):
102
+ import hashlib
103
+ return hashlib.sha3_256(text.encode("utf-8")).hexdigest()
104
+
105
+ def _blake2(text):
106
+ import hashlib
107
+ return hashlib.blake2b(text.encode("utf-8")).hexdigest()
108
+
109
+ def _base85(text):
110
+ import base64
111
+ return base64.b85encode(text.encode("utf-8")).decode("ascii")
112
+
113
+ def _ascii85(text):
114
+ import base64
115
+ return base64.a85encode(text.encode("utf-8")).decode("ascii")
116
+
117
+ def _url_reverse(text):
118
+ from urllib.parse import quote
119
+ return quote(text, safe="")[::-1]
120
+
121
+ def _token_dump(text):
122
+ import io, tokenize
123
+ rows = []
124
+ for tok in tokenize.generate_tokens(io.StringIO(text).readline):
125
+ rows.append({
126
+ "type": tokenize.tok_name.get(tok.type, str(tok.type)),
127
+ "string": tok.string, "start": tok.start, "end": tok.end
128
+ })
129
+ return json.dumps(rows, ensure_ascii=False, indent=2)
130
+
131
+ def _ast_dump(text):
132
+ import ast
133
+ return ast.dump(ast.parse(text), indent=2, include_attributes=True)
134
+
135
+ def _compile_check(text):
136
+ compile(text, "<lbenc-validation>", "exec")
137
+ return text
138
+
139
+ def _url_encode(text):
140
+ from urllib.parse import quote
141
+ return quote(text, safe="")
142
+
143
+ def _hex(text):
144
+ return text.encode("utf-8").hex()
145
+
146
+ def _reverse_rot13(text):
147
+ import codecs
148
+ return codecs.encode(text[::-1], "rot_13")
149
+
150
+ def _identity(text):
151
+ return text
152
+
153
+ METHODS = [
154
+ ("ROT13", lambda t: __import__("codecs").encode(t, "rot_13")),
155
+ ("Base64", _b64),
156
+ ("Base32", _b32),
157
+ ("Base16", _b16),
158
+ ("Zlib + Base64", _zlib),
159
+ ("Marshal + Base64", _marshal),
160
+ ("Reverse", lambda t: t[::-1]),
161
+ ("URL Encode", _url_encode),
162
+ ("Hex", _hex),
163
+ ("XOR-7", lambda t: _xor(t, 7)),
164
+ ("XOR-5", lambda t: _xor(t, 5)),
165
+ ("Caesar-3", lambda t: _caesar(t, 3)),
166
+ ("Caesar-5", lambda t: _caesar(t, 5)),
167
+ ("ROT13 + Base64", _rot13_b64),
168
+ ("Zlib + Base64 (alias)", _zlib),
169
+ ("Marshal Base64 (alias)", _marshal),
170
+ ("Reverse + ROT13", _reverse_rot13),
171
+ ("URL + Reverse", _url_reverse),
172
+ ("Identity", _identity),
173
+ ("Base85", _base85),
174
+ ("ASCII85", _ascii85),
175
+ ("Gzip + Base64", _gzip),
176
+ ("BZ2 + Base64", _bz2),
177
+ ("LZMA + Base64", _lzma),
178
+ ("Binary", _binary),
179
+ ("Unicode Escape", _unicode_escape),
180
+ ("JSON String", _json_string),
181
+ ("SHA-256 Digest", _sha256),
182
+ ("SHA3-256 Digest", _sha3),
183
+ ("BLAKE2b Digest", _blake2),
184
+ ]
185
+ assert len(METHODS) == 30
186
+
187
+
188
+ def load_config():
189
+ config = DEFAULT_CONFIG.copy()
190
+ try:
191
+ saved = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
192
+ if isinstance(saved, dict):
193
+ for key in config:
194
+ if isinstance(saved.get(key), str) and saved[key].strip():
195
+ config[key] = saved[key].strip()
196
+ except (OSError, json.JSONDecodeError):
197
+ pass
198
+ return config
199
+
200
+
201
+ def save_config(config):
202
+ temp_path = CONFIG_FILE.with_suffix(".tmp")
203
+ temp_path.write_text(
204
+ json.dumps(config, ensure_ascii=False, indent=2) + "\n",
205
+ encoding="utf-8",
206
+ )
207
+ temp_path.replace(CONFIG_FILE)
208
+
209
+
210
+ def _valid_http_url(value):
211
+ parsed = urlparse(value.strip())
212
+ return parsed.scheme in ("http", "https") and bool(parsed.netloc)
213
+
214
+
215
+ def _read_multiline_message():
216
+ print("\nEnter your /start message.")
217
+ print("Type /done on a line by itself when finished.")
218
+ lines = []
219
+ while True:
220
+ try:
221
+ line = input()
222
+ except EOFError:
223
+ break
224
+ if line.strip() == "/done":
225
+ break
226
+ lines.append(line)
227
+ value = "\n".join(lines).strip()
228
+ if not value:
229
+ print("⚠️ Empty message ignored.")
230
+ return None
231
+ return value
232
+
233
+
234
+ def configure_menu():
235
+ config = load_config()
236
+ while True:
237
+ print("\n" + "═" * 52)
238
+ print(" L B E N C")
239
+ print(" PREMIUM CONFIGURATION")
240
+ print("═" * 52)
241
+ print(" 1. Change inline button URLs")
242
+ print(" 2. Change /start message")
243
+ print(" 3. Change welcome image URL")
244
+ print(" 4. View current configuration")
245
+ print(" 5. Reset configuration")
246
+ print(" 0. Save and continue to bot")
247
+ print("─" * 52)
248
+ try:
249
+ choice = input("Choose an option: ").strip()
250
+ except (EOFError, KeyboardInterrupt):
251
+ print("\nConfiguration cancelled.")
252
+ return config
253
+
254
+ if choice == "1":
255
+ channel = input("Please send your channel URL: ").strip()
256
+ if not _valid_http_url(channel):
257
+ print("❌ Invalid URL. Use an http:// or https:// URL.")
258
+ continue
259
+ developer = input("Please send your developer URL: ").strip()
260
+ if not _valid_http_url(developer):
261
+ print("❌ Invalid URL. Use an http:// or https:// URL.")
262
+ continue
263
+ config["channel_url"] = channel
264
+ config["developer_url"] = developer
265
+ save_config(config)
266
+ print("✅ URL changed successfully — changed by @hardhackar009")
267
+ elif choice == "2":
268
+ message = _read_multiline_message()
269
+ if message is not None:
270
+ config["start_message"] = message
271
+ save_config(config)
272
+ print("✅ Start message changed successfully — changed by @hardhackar009")
273
+ elif choice == "3":
274
+ image_url = input("Please send your image URL: ").strip()
275
+ if not _valid_http_url(image_url):
276
+ print("❌ Invalid URL. Use an http:// or https:// URL.")
277
+ continue
278
+ config["image_url"] = image_url
279
+ save_config(config)
280
+ print("✅ Image URL changed successfully — changed by @hardhackar009")
281
+ elif choice == "4":
282
+ print("\nCurrent configuration:")
283
+ print(f"Channel URL : {config['channel_url']}")
284
+ print(f"Developer URL : {config['developer_url']}")
285
+ print(f"Start message :\n{config['start_message']}")
286
+ print(f"Image URL : {config['image_url']}")
287
+ print(f"Config file : {CONFIG_FILE.resolve()}")
288
+ elif choice == "5":
289
+ confirm = input("Reset configuration to defaults? (y/N): ").strip().lower()
290
+ if confirm == "y":
291
+ config = DEFAULT_CONFIG.copy()
292
+ save_config(config)
293
+ print("✅ Configuration reset.")
294
+ else:
295
+ print("Reset cancelled.")
296
+ elif choice == "0":
297
+ save_config(config)
298
+ print("✅ Configuration saved.")
299
+ return config
300
+ else:
301
+ print("❌ Invalid option. Please choose 0–5.")
302
+
303
+
304
+ def _cleanup_user_file(user_id):
305
+ with STATE_LOCK:
306
+ state = USER_FILES.pop(user_id, None)
307
+ if state:
308
+ try:
309
+ os.remove(state["path"])
310
+ except OSError:
311
+ pass
312
+
313
+
314
+ def _safe_filename(name):
315
+ name = os.path.basename(name or "upload.bin")
316
+ name = re.sub(r"[^A-Za-z0-9._ -]", "_", name).strip(" .")
317
+ return (name or "upload.bin")[:160]
318
+
319
+
320
+ def create_bot(token):
321
+ if telebot is None:
322
+ raise RuntimeError(
323
+ "Missing dependency. Install it with: python -m pip install pyTelegramBotAPI"
324
+ )
325
+ bot = telebot.TeleBot(token)
326
+
327
+ def keyboard():
328
+ cfg = load_config()
329
+ markup = InlineKeyboardMarkup()
330
+ markup.row(
331
+ InlineKeyboardButton("📢 Channel", url=cfg["channel_url"]),
332
+ InlineKeyboardButton("👨‍💻 Developer", url=cfg["developer_url"]),
333
+ )
334
+ return markup
335
+
336
+ @bot.message_handler(commands=["start"])
337
+ def start_handler(message):
338
+ cfg = load_config()
339
+ try:
340
+ bot.send_photo(
341
+ message.chat.id,
342
+ cfg["image_url"],
343
+ caption=cfg["start_message"],
344
+ reply_markup=keyboard(),
345
+ )
346
+ except Exception:
347
+ bot.send_message(
348
+ message.chat.id,
349
+ cfg["start_message"],
350
+ reply_markup=keyboard(),
351
+ )
352
+
353
+ @bot.message_handler(content_types=["document"])
354
+ def document_handler(message):
355
+ user_id = message.from_user.id
356
+ doc = message.document
357
+ if doc.file_size and doc.file_size > MAX_FILE_SIZE:
358
+ bot.reply_to(message, "❌ Maximum upload size is 20 MB.")
359
+ return
360
+ filename = _safe_filename(doc.file_name)
361
+ try:
362
+ info = bot.get_file(doc.file_id)
363
+ payload = bot.download_file(info.file_path)
364
+ fd, path = tempfile.mkstemp(prefix="lbenc_")
365
+ with os.fdopen(fd, "wb") as f:
366
+ f.write(payload)
367
+ _cleanup_user_file(user_id)
368
+ with STATE_LOCK:
369
+ USER_FILES[user_id] = {
370
+ "path": path,
371
+ "name": filename,
372
+ "is_python": filename.lower().endswith(".py"),
373
+ }
374
+ markup = InlineKeyboardMarkup(row_width=3)
375
+ if filename.lower().endswith(".py"):
376
+ buttons = [
377
+ InlineKeyboardButton(name, callback_data=f"ENC|{i}")
378
+ for i, (name, _) in enumerate(METHODS)
379
+ ]
380
+ for offset in range(0, len(buttons), 3):
381
+ markup.row(*buttons[offset:offset + 3])
382
+ markup.row(InlineKeyboardButton("📦 Convert to ZIP", callback_data="ZIP"))
383
+ prompt = (
384
+ "Choose one of the 30 processing methods or ZIP:"
385
+ if filename.lower().endswith(".py")
386
+ else "Choose ZIP conversion:"
387
+ )
388
+ bot.send_message(message.chat.id, prompt, reply_markup=markup)
389
+ except Exception as exc:
390
+ bot.reply_to(message, f"❌ Upload failed: {type(exc).__name__}")
391
+
392
+ @bot.callback_query_handler(func=lambda call: call.data.startswith("ENC|"))
393
+ def transform_handler(call):
394
+ user_id = call.from_user.id
395
+ try:
396
+ index = int(call.data.split("|", 1)[1])
397
+ except (ValueError, IndexError):
398
+ bot.answer_callback_query(call.id, "Invalid method.")
399
+ return
400
+ if not 0 <= index < len(METHODS):
401
+ bot.answer_callback_query(call.id, "Unknown method.")
402
+ return
403
+ with STATE_LOCK:
404
+ data = USER_FILES.get(user_id)
405
+ if not data:
406
+ bot.answer_callback_query(call.id, "Please upload the file again.")
407
+ return
408
+ if not data["is_python"]:
409
+ bot.answer_callback_query(call.id, "Python files only.")
410
+ return
411
+ name, method = METHODS[index]
412
+ try:
413
+ text = Path(data["path"]).read_text(encoding="utf-8-sig")
414
+ output = method(text)
415
+ header = (
416
+ f"LBENC method: {name}\n"
417
+ "Note: this is a text transformation/representation, not necessarily "
418
+ "runnable Python. Hash outputs are one-way.\n\n"
419
+ )
420
+ bio = io.BytesIO((header + output).encode("utf-8"))
421
+ bio.name = Path(data["name"]).stem + f"_method_{index + 1:02d}.txt"
422
+ bot.send_document(
423
+ call.message.chat.id, bio, caption=f"✅ Processed: {name}"
424
+ )
425
+ bot.answer_callback_query(call.id, "Processing complete.")
426
+ except Exception as exc:
427
+ bot.send_message(
428
+ call.message.chat.id,
429
+ f"❌ Processing failed: {type(exc).__name__}: {exc}",
430
+ )
431
+
432
+ @bot.callback_query_handler(func=lambda call: call.data == "ZIP")
433
+ def zip_handler(call):
434
+ user_id = call.from_user.id
435
+ with STATE_LOCK:
436
+ data = USER_FILES.get(user_id)
437
+ if not data:
438
+ bot.answer_callback_query(call.id, "Please upload the file again.")
439
+ return
440
+ try:
441
+ out = io.BytesIO()
442
+ with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as archive:
443
+ archive.write(data["path"], arcname=data["name"])
444
+ out.seek(0)
445
+ out.name = Path(data["name"]).stem + ".zip"
446
+ bot.send_document(
447
+ call.message.chat.id, out, caption=f"✅ {data['name']} converted to ZIP"
448
+ )
449
+ bot.answer_callback_query(call.id, "ZIP created.")
450
+ except Exception as exc:
451
+ bot.send_message(
452
+ call.message.chat.id,
453
+ f"❌ ZIP conversion failed: {type(exc).__name__}: {exc}",
454
+ )
455
+
456
+ return bot
457
+
458
+
459
+ def main(argv=None):
460
+ parser = argparse.ArgumentParser(
461
+ prog="lbenc",
462
+ description="LBENC Telegram bot — configure URLs, start text, and image.",
463
+ )
464
+ parser.add_argument("token", nargs="?", help="Telegram bot token")
465
+ parser.add_argument("--configure", action="store_true", help="Open configuration menu")
466
+ parser.add_argument("--version", action="version", version=f"%(prog)s {APP_VERSION}")
467
+ args = parser.parse_args(argv)
468
+
469
+ if args.configure:
470
+ configure_menu()
471
+ return 0
472
+
473
+ token = (args.token or os.environ.get("TELEGRAM_BOT_TOKEN", "")).strip()
474
+ if not token:
475
+ print("Usage: lbenc YOUR_TELEGRAM_BOT_TOKEN")
476
+ print("Or configure settings with: lbenc --configure")
477
+ return 2
478
+
479
+ # Show the interactive menu when launched from a terminal.
480
+ if sys.stdin.isatty():
481
+ configure_menu()
482
+
483
+ try:
484
+ bot = create_bot(token)
485
+ except RuntimeError as exc:
486
+ print(f"❌ {exc}")
487
+ return 2
488
+
489
+ print("╔══════════════════════════════════════════════╗")
490
+ print("║ LBENC BOT IS RUNNING ║")
491
+ print("╚══════════════════════════════════════════════╝")
492
+ print("Press Ctrl+C to stop.")
493
+ try:
494
+ bot.infinity_polling(skip_pending=True, timeout=30, long_polling_timeout=30)
495
+ except KeyboardInterrupt:
496
+ print("\nLBENC stopped.")
497
+ return 0
498
+
499
+
500
+ if __name__ == "__main__":
501
+ raise SystemExit(main())