|
|
|
import os
|
|
|
|
import asyncio
|
|
|
|
import signal
|
|
|
|
import sys
|
|
|
|
import threading
|
|
|
|
import traceback
|
Add Dockerfile + concept of a DATA_DIR
- Add a Dockerfile so people can run this bot in a docker container
- Stuck with recommendation of running with python3.9 for now
- Will later test with 3.11 + supply fixes if I get this working ...
- Added a DATA_DIR env param to use to choose the directory to write data we want persistent across docker container restarts to be written to
- We default to CWD like the code does today - we just explicitly pass it to functions / classes
Test:
- `docker build -t gpt3discord .`
```
crl-m1:GPT3Discord cooper$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
gpt3discord latest 6d2832af2450 69 seconds ago 356MB
```
- Try run it ... I would guess if I had correct tokens things would work ...
- To do so I plan to bind mount over /bin/.env on my docker container when I run this ...
```
crl-m1:GPT3Discord cooper$ docker run gpt3discord
None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
Downloading: 100%|██████████| 1.04M/1.04M [00:02<00:00, 516kB/s]
Downloading: 100%|██████████| 456k/456k [00:01<00:00, 319kB/s]
Downloading: 100%|██████████| 1.36M/1.36M [00:03<00:00, 443kB/s]
Downloading: 100%|██████████| 665/665 [00:00<00:00, 740kB/s]
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 413, in static_login
data = await self.request(Route("GET", "/users/@me"))
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 366, in request
raise HTTPException(response, data)
discord.errors.HTTPException: 401 Unauthorized (error code: 0): 401: Unauthorized
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/bin/gpt3discord", line 79, in <module>
asyncio.get_event_loop().run_until_complete(main())
File "/usr/local/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
return future.result()
File "/bin/gpt3discord", line 63, in main
await bot.start(os.getenv("DISCORD_TOKEN"))
File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 658, in start
await self.login(token)
File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 514, in login
data = await self.http.static_login(token.strip())
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 417, in static_login
raise LoginFailure("Improper token has been passed.") from exc
discord.errors.LoginFailure: Improper token has been passed.
Wrote PID to file the file bot.pid
The debug channel and guild IDs are 755420092027633774 and 907974109084942396
Improper token has been passed.
Removing PID file
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0xffff721a2dc0>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0xffff718fe0a0>, 170230.336548951)]']
connector: <aiohttp.connector.TCPConnector object at 0xffff721a2fd0>
```
2 years ago
|
|
|
from pathlib import Path
|
|
|
|
from platform import system
|
|
|
|
|
|
|
|
import discord
|
|
|
|
import pinecone
|
|
|
|
from pycord.multicog import apply_multicog
|
|
|
|
|
|
|
|
from cogs.search_service_cog import SearchService
|
|
|
|
from cogs.text_service_cog import GPT3ComCon
|
|
|
|
from cogs.image_service_cog import DrawDallEService
|
|
|
|
from cogs.prompt_optimizer_cog import ImgPromptOptimizer
|
|
|
|
from cogs.moderations_service_cog import ModerationsService
|
|
|
|
from cogs.commands import Commands
|
|
|
|
from cogs.transcription_service_cog import TranscribeService
|
|
|
|
from cogs.translation_service_cog import TranslationService
|
|
|
|
from cogs.index_service_cog import IndexService
|
|
|
|
from models.deepl_model import TranslationModel
|
|
|
|
from services.health_service import HealthService
|
|
|
|
from services.pickle_service import Pickler
|
|
|
|
|
|
|
|
from services.pinecone_service import PineconeService
|
|
|
|
from services.deletion_service import Deletion
|
|
|
|
from services.message_queue_service import Message
|
|
|
|
from services.usage_service import UsageService
|
|
|
|
from services.environment_service import EnvService
|
|
|
|
|
|
|
|
from models.openai_model import Model
|
|
|
|
|
|
|
|
|
|
|
|
__version__ = "11.1.5"
|
|
|
|
|
|
|
|
|
|
|
|
PID_FILE = Path("bot.pid")
|
|
|
|
PROCESS = None
|
|
|
|
|
|
|
|
if sys.platform == "win32":
|
|
|
|
separator = "\\"
|
|
|
|
else:
|
|
|
|
separator = "/"
|
|
|
|
|
|
|
|
#
|
|
|
|
# The pinecone service is used to store and retrieve conversation embeddings.
|
|
|
|
#
|
|
|
|
|
|
|
|
try:
|
|
|
|
PINECONE_TOKEN = os.getenv("PINECONE_TOKEN")
|
|
|
|
except Exception:
|
|
|
|
PINECONE_TOKEN = None
|
|
|
|
|
|
|
|
pinecone_service = None
|
|
|
|
if PINECONE_TOKEN:
|
|
|
|
pinecone.init(api_key=PINECONE_TOKEN, environment=EnvService.get_pinecone_region())
|
|
|
|
PINECONE_INDEX = "conversation-embeddings"
|
|
|
|
if PINECONE_INDEX not in pinecone.list_indexes():
|
|
|
|
print("Creating pinecone index. Please wait...")
|
|
|
|
pinecone.create_index(
|
|
|
|
PINECONE_INDEX,
|
|
|
|
dimension=1536,
|
|
|
|
metric="dotproduct",
|
|
|
|
pod_type="s1",
|
|
|
|
)
|
|
|
|
|
|
|
|
pinecone_service = PineconeService(pinecone.Index(PINECONE_INDEX))
|
|
|
|
print("Got the pinecone service")
|
|
|
|
|
|
|
|
#
|
|
|
|
# Message queueing for the debug service, defer debug messages to be sent later so we don't hit rate limits.
|
|
|
|
#
|
|
|
|
message_queue = asyncio.Queue()
|
|
|
|
deletion_queue = asyncio.Queue()
|
|
|
|
asyncio.ensure_future(Message.process_message_queue(message_queue, 1.5, 5))
|
|
|
|
asyncio.ensure_future(Deletion.process_deletion_queue(deletion_queue, 1, 1))
|
|
|
|
|
|
|
|
# Pickling service for conversation persistence
|
|
|
|
try:
|
|
|
|
Path(EnvService.save_path() / "pickles").mkdir(exist_ok=True)
|
|
|
|
except Exception:
|
|
|
|
traceback.print_exc()
|
|
|
|
print(
|
|
|
|
"Could not start pickle service. Conversation history will not be persistent across restarts."
|
|
|
|
)
|
|
|
|
pickle_queue = asyncio.Queue()
|
|
|
|
asyncio.ensure_future(Pickler.process_pickle_queue(pickle_queue, 5, 1))
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Settings for the bot
|
|
|
|
#
|
|
|
|
activity = discord.Activity(
|
|
|
|
type=discord.ActivityType.watching, name="for /help /gpt, and more!"
|
|
|
|
)
|
|
|
|
bot = discord.Bot(intents=discord.Intents.all(), command_prefix="!", activity=activity)
|
Add Dockerfile + concept of a DATA_DIR
- Add a Dockerfile so people can run this bot in a docker container
- Stuck with recommendation of running with python3.9 for now
- Will later test with 3.11 + supply fixes if I get this working ...
- Added a DATA_DIR env param to use to choose the directory to write data we want persistent across docker container restarts to be written to
- We default to CWD like the code does today - we just explicitly pass it to functions / classes
Test:
- `docker build -t gpt3discord .`
```
crl-m1:GPT3Discord cooper$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
gpt3discord latest 6d2832af2450 69 seconds ago 356MB
```
- Try run it ... I would guess if I had correct tokens things would work ...
- To do so I plan to bind mount over /bin/.env on my docker container when I run this ...
```
crl-m1:GPT3Discord cooper$ docker run gpt3discord
None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
Downloading: 100%|██████████| 1.04M/1.04M [00:02<00:00, 516kB/s]
Downloading: 100%|██████████| 456k/456k [00:01<00:00, 319kB/s]
Downloading: 100%|██████████| 1.36M/1.36M [00:03<00:00, 443kB/s]
Downloading: 100%|██████████| 665/665 [00:00<00:00, 740kB/s]
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 413, in static_login
data = await self.request(Route("GET", "/users/@me"))
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 366, in request
raise HTTPException(response, data)
discord.errors.HTTPException: 401 Unauthorized (error code: 0): 401: Unauthorized
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/bin/gpt3discord", line 79, in <module>
asyncio.get_event_loop().run_until_complete(main())
File "/usr/local/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
return future.result()
File "/bin/gpt3discord", line 63, in main
await bot.start(os.getenv("DISCORD_TOKEN"))
File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 658, in start
await self.login(token)
File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 514, in login
data = await self.http.static_login(token.strip())
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 417, in static_login
raise LoginFailure("Improper token has been passed.") from exc
discord.errors.LoginFailure: Improper token has been passed.
Wrote PID to file the file bot.pid
The debug channel and guild IDs are 755420092027633774 and 907974109084942396
Improper token has been passed.
Removing PID file
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0xffff721a2dc0>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0xffff718fe0a0>, 170230.336548951)]']
connector: <aiohttp.connector.TCPConnector object at 0xffff721a2fd0>
```
2 years ago
|
|
|
usage_service = UsageService(Path(os.environ.get("DATA_DIR", os.getcwd())))
|
|
|
|
model = Model(usage_service)
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# An encapsulating wrapper for the discord.py client. This uses the old re-write without cogs, but it gets the job done!
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
@bot.event # Using self gives u
|
|
|
|
async def on_ready(): # I can make self optional by
|
|
|
|
print("We have logged in as {0.user}".format(bot))
|
|
|
|
|
|
|
|
|
|
|
|
@bot.event
|
|
|
|
async def on_application_command_error(
|
|
|
|
ctx: discord.ApplicationContext, error: discord.DiscordException
|
|
|
|
):
|
|
|
|
if isinstance(error, discord.CheckFailure):
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
raise error
|
|
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
data_path = EnvService.environment_path_with_fallback("DATA_DIR")
|
|
|
|
debug_guild = int(os.getenv("DEBUG_GUILD"))
|
|
|
|
debug_channel = int(os.getenv("DEBUG_CHANNEL"))
|
|
|
|
|
Add Dockerfile + concept of a DATA_DIR
- Add a Dockerfile so people can run this bot in a docker container
- Stuck with recommendation of running with python3.9 for now
- Will later test with 3.11 + supply fixes if I get this working ...
- Added a DATA_DIR env param to use to choose the directory to write data we want persistent across docker container restarts to be written to
- We default to CWD like the code does today - we just explicitly pass it to functions / classes
Test:
- `docker build -t gpt3discord .`
```
crl-m1:GPT3Discord cooper$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
gpt3discord latest 6d2832af2450 69 seconds ago 356MB
```
- Try run it ... I would guess if I had correct tokens things would work ...
- To do so I plan to bind mount over /bin/.env on my docker container when I run this ...
```
crl-m1:GPT3Discord cooper$ docker run gpt3discord
None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
Downloading: 100%|██████████| 1.04M/1.04M [00:02<00:00, 516kB/s]
Downloading: 100%|██████████| 456k/456k [00:01<00:00, 319kB/s]
Downloading: 100%|██████████| 1.36M/1.36M [00:03<00:00, 443kB/s]
Downloading: 100%|██████████| 665/665 [00:00<00:00, 740kB/s]
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 413, in static_login
data = await self.request(Route("GET", "/users/@me"))
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 366, in request
raise HTTPException(response, data)
discord.errors.HTTPException: 401 Unauthorized (error code: 0): 401: Unauthorized
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/bin/gpt3discord", line 79, in <module>
asyncio.get_event_loop().run_until_complete(main())
File "/usr/local/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
return future.result()
File "/bin/gpt3discord", line 63, in main
await bot.start(os.getenv("DISCORD_TOKEN"))
File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 658, in start
await self.login(token)
File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 514, in login
data = await self.http.static_login(token.strip())
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 417, in static_login
raise LoginFailure("Improper token has been passed.") from exc
discord.errors.LoginFailure: Improper token has been passed.
Wrote PID to file the file bot.pid
The debug channel and guild IDs are 755420092027633774 and 907974109084942396
Improper token has been passed.
Removing PID file
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0xffff721a2dc0>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0xffff718fe0a0>, 170230.336548951)]']
connector: <aiohttp.connector.TCPConnector object at 0xffff721a2fd0>
```
2 years ago
|
|
|
if not data_path.exists():
|
|
|
|
raise OSError(f"Data path: {data_path} does not exist ... create it?")
|
|
|
|
|
|
|
|
# Load the cog for the moderations service
|
|
|
|
bot.add_cog(ModerationsService(bot, usage_service, model))
|
|
|
|
|
|
|
|
# Load the main GPT3 Bot service
|
|
|
|
bot.add_cog(
|
|
|
|
GPT3ComCon(
|
|
|
|
bot,
|
|
|
|
usage_service,
|
|
|
|
model,
|
|
|
|
message_queue,
|
|
|
|
deletion_queue,
|
|
|
|
debug_guild,
|
|
|
|
debug_channel,
|
Add Dockerfile + concept of a DATA_DIR
- Add a Dockerfile so people can run this bot in a docker container
- Stuck with recommendation of running with python3.9 for now
- Will later test with 3.11 + supply fixes if I get this working ...
- Added a DATA_DIR env param to use to choose the directory to write data we want persistent across docker container restarts to be written to
- We default to CWD like the code does today - we just explicitly pass it to functions / classes
Test:
- `docker build -t gpt3discord .`
```
crl-m1:GPT3Discord cooper$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
gpt3discord latest 6d2832af2450 69 seconds ago 356MB
```
- Try run it ... I would guess if I had correct tokens things would work ...
- To do so I plan to bind mount over /bin/.env on my docker container when I run this ...
```
crl-m1:GPT3Discord cooper$ docker run gpt3discord
None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
Downloading: 100%|██████████| 1.04M/1.04M [00:02<00:00, 516kB/s]
Downloading: 100%|██████████| 456k/456k [00:01<00:00, 319kB/s]
Downloading: 100%|██████████| 1.36M/1.36M [00:03<00:00, 443kB/s]
Downloading: 100%|██████████| 665/665 [00:00<00:00, 740kB/s]
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 413, in static_login
data = await self.request(Route("GET", "/users/@me"))
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 366, in request
raise HTTPException(response, data)
discord.errors.HTTPException: 401 Unauthorized (error code: 0): 401: Unauthorized
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/bin/gpt3discord", line 79, in <module>
asyncio.get_event_loop().run_until_complete(main())
File "/usr/local/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
return future.result()
File "/bin/gpt3discord", line 63, in main
await bot.start(os.getenv("DISCORD_TOKEN"))
File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 658, in start
await self.login(token)
File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 514, in login
data = await self.http.static_login(token.strip())
File "/usr/local/lib/python3.9/site-packages/discord/http.py", line 417, in static_login
raise LoginFailure("Improper token has been passed.") from exc
discord.errors.LoginFailure: Improper token has been passed.
Wrote PID to file the file bot.pid
The debug channel and guild IDs are 755420092027633774 and 907974109084942396
Improper token has been passed.
Removing PID file
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0xffff721a2dc0>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0xffff718fe0a0>, 170230.336548951)]']
connector: <aiohttp.connector.TCPConnector object at 0xffff721a2fd0>
```
2 years ago
|
|
|
data_path,
|
|
|
|
pinecone_service=pinecone_service,
|
|
|
|
pickle_queue=pickle_queue,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
bot.add_cog(
|
|
|
|
DrawDallEService(
|
|
|
|
bot,
|
|
|
|
usage_service,
|
|
|
|
model,
|
|
|
|
message_queue,
|
|
|
|
deletion_queue,
|
|
|
|
bot.get_cog("GPT3ComCon"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
bot.add_cog(
|
|
|
|
ImgPromptOptimizer(
|
|
|
|
bot,
|
|
|
|
usage_service,
|
|
|
|
model,
|
|
|
|
message_queue,
|
|
|
|
deletion_queue,
|
|
|
|
bot.get_cog("GPT3ComCon"),
|
|
|
|
bot.get_cog("DrawDallEService"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
bot.add_cog(
|
|
|
|
IndexService(
|
|
|
|
bot,
|
|
|
|
usage_service,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
if EnvService.get_deepl_token():
|
|
|
|
bot.add_cog(TranslationService(bot, TranslationModel()))
|
|
|
|
print("The translation service is enabled.")
|
|
|
|
|
|
|
|
if (
|
|
|
|
EnvService.get_google_search_api_key()
|
|
|
|
and EnvService.get_google_search_engine_id()
|
|
|
|
):
|
|
|
|
bot.add_cog(SearchService(bot, model, usage_service))
|
|
|
|
print("The Search service is enabled.")
|
|
|
|
|
|
|
|
bot.add_cog(
|
|
|
|
TranscribeService(
|
|
|
|
bot,
|
|
|
|
model,
|
|
|
|
usage_service,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
bot.add_cog(
|
|
|
|
Commands(
|
|
|
|
bot,
|
|
|
|
usage_service,
|
|
|
|
model,
|
|
|
|
message_queue,
|
|
|
|
deletion_queue,
|
|
|
|
bot.get_cog("GPT3ComCon"),
|
|
|
|
bot.get_cog("DrawDallEService"),
|
|
|
|
bot.get_cog("ImgPromptOptimizer"),
|
|
|
|
bot.get_cog("ModerationsService"),
|
|
|
|
bot.get_cog("IndexService"),
|
|
|
|
bot.get_cog("TranslationService"),
|
|
|
|
bot.get_cog("SearchService"),
|
|
|
|
bot.get_cog("TranscribeService"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
apply_multicog(bot)
|
|
|
|
|
|
|
|
await bot.start(os.getenv("DISCORD_TOKEN"))
|
|
|
|
|
|
|
|
|
|
|
|
def check_process_file(pid_file: Path) -> bool:
|
|
|
|
"""Check the pid file exists and if the Process ID is actually running"""
|
|
|
|
if not pid_file.exists():
|
|
|
|
return False
|
|
|
|
if system() == "Linux":
|
|
|
|
with pid_file.open("r") as pfp:
|
|
|
|
try:
|
|
|
|
proc_pid_path = Path("/proc") / "{int(pfp.read().strip())}"
|
|
|
|
print("Checking if PID proc path {proc_pid_path} exists")
|
|
|
|
except ValueError:
|
|
|
|
# We don't have a valid int in the PID File^M
|
|
|
|
pid_file.unlink()
|
|
|
|
return False
|
|
|
|
return proc_pid_path.exists()
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def cleanup_pid_file(signum, frame):
|
|
|
|
# Kill all threads
|
|
|
|
if PROCESS:
|
|
|
|
print("Killing all subprocesses")
|
|
|
|
PROCESS.terminate()
|
|
|
|
print("Killed all subprocesses")
|
|
|
|
# Always cleanup PID File if it exists
|
|
|
|
if PID_FILE.exists():
|
|
|
|
print(f"Removing PID file {PID_FILE}", flush=True)
|
|
|
|
PID_FILE.unlink()
|
|
|
|
|
|
|
|
|
|
|
|
# Run the bot with a token taken from an environment file.
|
|
|
|
def init():
|
|
|
|
global PROCESS
|
|
|
|
# Handle SIGTERM cleanly - Docker sends this ...
|
|
|
|
signal.signal(signal.SIGTERM, cleanup_pid_file)
|
|
|
|
|
|
|
|
if check_process_file(PID_FILE):
|
|
|
|
print(
|
|
|
|
"Process ID file already exists. Remove the file if you're sure another instance isn't running with the command: rm bot.pid"
|
|
|
|
)
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
with PID_FILE.open("w") as f:
|
|
|
|
f.write(str(os.getpid()))
|
|
|
|
print(f"Wrote PID to file {PID_FILE}")
|
|
|
|
f.close()
|
|
|
|
try:
|
|
|
|
if EnvService.get_health_service_enabled():
|
|
|
|
try:
|
|
|
|
PROCESS = HealthService().get_process()
|
|
|
|
except:
|
|
|
|
traceback.print_exc()
|
|
|
|
print("The health service failed to start.")
|
|
|
|
|
|
|
|
asyncio.get_event_loop().run_until_complete(main())
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print("Caught keyboard interrupt, killing and removing PID")
|
|
|
|
except Exception as e:
|
|
|
|
traceback.print_exc()
|
|
|
|
print(str(e))
|
|
|
|
print("Removing PID file")
|
|
|
|
finally:
|
|
|
|
cleanup_pid_file(None, None)
|
|
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
sys.exit(init())
|