mirror of
https://github.com/damp11113/PyserSSH.git
synced 2025-04-27 22:48:11 +00:00
4.4
no demo folder but it on library python -m PyserSSH
This commit is contained in:
parent
79353cf668
commit
f33be3bca1
27
README.md
27
README.md
@ -6,6 +6,9 @@ This project is part from [damp11113-library](https://github.com/damp11113/damp1
|
|||||||
|
|
||||||
This Server use port **2222** for default port
|
This Server use port **2222** for default port
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> For use in product please **generate new private key**! If you still use this demo private key maybe your product getting **hacked**! up to 90%. Please don't use this demo private key for real product.
|
||||||
|
|
||||||
# Install
|
# Install
|
||||||
Install from pypi
|
Install from pypi
|
||||||
```bash
|
```bash
|
||||||
@ -40,11 +43,25 @@ If you input `hello` the response is `world`
|
|||||||
# Demo
|
# Demo
|
||||||
https://github.com/damp11113/PyserSSH/assets/64675096/49bef3e2-3b15-4b64-b88e-3ca84a955de7
|
https://github.com/damp11113/PyserSSH/assets/64675096/49bef3e2-3b15-4b64-b88e-3ca84a955de7
|
||||||
|
|
||||||
See [server.py](https://github.com/damp11113/PyserSSH/blob/main/demo/server.py)
|
For run this demo you can use this command
|
||||||
|
```
|
||||||
|
$ python -m PyserSSH
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```
|
||||||
|
Do you want to run demo? (y/n): y
|
||||||
|
```
|
||||||
|
But if no [damp11113-library](https://github.com/damp11113/damp11113-library)
|
||||||
|
```
|
||||||
|
No 'damp11113-library'
|
||||||
|
This demo is require 'damp11113-library' for run
|
||||||
|
```
|
||||||
|
you need to install [damp11113-library](https://github.com/damp11113/damp11113-library) for run this demo by choose `y` or `yes` in lowercase or uppercase
|
||||||
|
```
|
||||||
|
Do you want to install 'damp11113-library'? (y/n): y
|
||||||
|
```
|
||||||
|
For exit demo you can use `ctrl+c` or use `shutdown now` in PyserSSH shell **(not in real terminal)**
|
||||||
|
|
||||||
I intend to leaked private key because that key i generated new. I recommend to generate new key if you want to use on your host because that key is for demo only.
|
I intend to leaked private key because that key i generated new. I recommend to generate new key if you want to use on your host because that key is for demo only.
|
||||||
why i talk about this? because when i push private key into this repo in next 5 min++ i getting new email from GitGuardian. in that email say "
|
why i talk about this? because when i push private key into this repo in next 5 min++ i getting new email from GitGuardian. in that email say "
|
||||||
GitGuardian has detected the following RSA Private Key exposed within your GitHub account" i dont knows what is GitGuardian and i not install this app into my account.
|
GitGuardian has detected the following RSA Private Key exposed within your GitHub account" i dont knows what is GitGuardian and i not install this app into my account.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEogIBAAKCAQEAq7UgQtL4Nv2s8rvaJjQryNxpsKpcSeDIsABnvry6Xkd3KhOi
|
|
||||||
K3c4dkYJjiAb4w4wfPiJ7sFL/PFP3f/slcpNHz18meWZkktia3rBX8uyJQ3soyNw
|
|
||||||
Vbxm6mOPntAqC4JBoPaYS4HABSYxJYY6yPU1i0UufvWg5pNRgeZIM8kQSyie4q1C
|
|
||||||
AEFG1T6sabJ5mOWH8Yw/zu3nTQpz2yIZYSVOsvJxBtaCEHCThhmQk2jPb88Ss0XT
|
|
||||||
a7uzaX/UIRktDz1FN6ooJbFqHsHxOsZJrC6YdZ3lo7DZYJU+jclG4jy95rGe7KpE
|
|
||||||
0p9cMAYNO0ya6toJM6GwFzJEk+HD0BTxdi7dKQIDAQABAoIBAFY1ciUa1xSE+LhG
|
|
||||||
KJjVyMXoJAhXAE73VMtI6M2S499B8kpl4R4BlY+MSm/ZHyc4kI+uGVKOKiCs53SG
|
|
||||||
cboi/+WXcV+zLw+MWbWsxDncg2ynORAPUu840FMN+aW6zeFJXLn8FSqT0lzDeBlm
|
|
||||||
80zCEEgES/viRw59GIcnn0igwlV5EzO6zhWzwdeMpBO4XFFDaiEY5idIBQf4jCEY
|
|
||||||
JcfQOrkPpfPgjLyQmFLyeojyaUVLIOOLUGMsSS8Hk7MJlgEdneEIXX7EhPqLDPyc
|
|
||||||
1f33WsnlTvbHLGWHE7lMG1LbK1ecsNwWWUFfoVQaQzYUQAVSuoqTYYkAYfGnQmV0
|
|
||||||
nnsIUAECgYEA7xR8cqu/knx9gFeinYwLx+/BbUw1MAX0WSK4GOx0O7qgbDb+scb2
|
|
||||||
x4aBCZnDE44KRl1/mbLXxb3wq8GN7W4owIHSud/8gZNqJM0uXbiJS7gMW0XnoJuk
|
|
||||||
D4hr0ADddPn/vGfQyUf0oUOFg2nP+H99GEuyYpHXr39R4Fh4UljXEkECgYEAt9wG
|
|
||||||
GRUhW6BoE3t2/mUcgkpXrljI7W2SDtHGgGzNb8Mlxcu/KUC4b6qdNwe83t0w3SaP
|
|
||||||
+34JHXIqnb2cuvigQj8pCoFxaMT6gH7x1zQWI1cORCA+Vfx/NZ2cCyXpebNOytxu
|
|
||||||
AwtAVo+r/QZlfs4OlG+TVwKBxYz9huCPFaAfQOkCgYA5iViZ0DN+cW9Sn8SG3dlH
|
|
||||||
+K84OoriT8yKVwyvEti2Nye8Y0/QQO3K/te3E8Yawqg+XuoCd0PuVtPAwggCB+zO
|
|
||||||
x2+LRBhkprF4wdhSvcJs8pImtSAVSt+kzVQE7vBc4n1lPibFCggZd0J+acyfJS9Z
|
|
||||||
1X3MswSRO7bcou3yA2dfAQKBgAHz3Dy39Lq8YV6TmRfqivr3Pyci2j9rQnnV0H3c
|
|
||||||
qfHd6LDJESanAU5uSW0kL+VOBA7VMgJBvGcLp1g1g0yZB1qswQrThRjPvrlOn9Lh
|
|
||||||
QrrtWcFvdjoDjHZNTjLwHCKmvNd6r9Bodi51KCZvwvQtzAnXhYEPDcHDVY3xJJPe
|
|
||||||
N3bBAoGADnEc8G2taL/tq7Skcw1G/cZYUp4CZw+ypCLd1xyus7Lnu9Wl6y3U0HEl
|
|
||||||
pjgzBGlwvTRkvC5ewz46WIWE+hlmOdSih81Cro48baXR2T1OD8jKQ2pXmHK5Z/wy
|
|
||||||
0V7t7eHd/k8CcXzIWIk6gmpOYhKkIVvQW5g7ssbwsfsk3qD++Fs=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
2
setup.py
2
setup.py
@ -5,7 +5,7 @@ with open('README.md', 'r', encoding='utf-8') as f:
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='PyserSSH',
|
name='PyserSSH',
|
||||||
version='4.3',
|
version='4.4',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
author='damp11113',
|
author='damp11113',
|
||||||
author_email='damp51252@gmail.com',
|
author_email='damp51252@gmail.com',
|
||||||
|
@ -42,9 +42,10 @@ import logging
|
|||||||
from .interactive import *
|
from .interactive import *
|
||||||
from .server import Server
|
from .server import Server
|
||||||
from .account import AccountManager
|
from .account import AccountManager
|
||||||
from .system.info import system_banner
|
|
||||||
|
|
||||||
|
|
||||||
|
from .system.info import system_banner
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.environ["pyserssh_systemmessage"]
|
os.environ["pyserssh_systemmessage"]
|
||||||
except:
|
except:
|
||||||
@ -66,3 +67,10 @@ if os.environ["pyserssh_log"] == "NO":
|
|||||||
|
|
||||||
if os.environ["pyserssh_systemmessage"] == "YES":
|
if os.environ["pyserssh_systemmessage"] == "YES":
|
||||||
print(system_banner)
|
print(system_banner)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
stadem = input("Do you want to run demo? (y/n): ")
|
||||||
|
if stadem.upper() in ["Y", "YES"]:
|
||||||
|
from .demo import demo1
|
||||||
|
else:
|
||||||
|
exit()
|
@ -60,6 +60,8 @@ class AccountManager:
|
|||||||
self.save("autosave_session.ses")
|
self.save("autosave_session.ses")
|
||||||
self.__autosaveworknexttime = time.time() + self.autosavedelay
|
self.__autosaveworknexttime = time.time() + self.autosavedelay
|
||||||
|
|
||||||
|
time.sleep(1) # fix cpu load
|
||||||
|
|
||||||
def __saveexit(self):
|
def __saveexit(self):
|
||||||
self.__autosavework = False
|
self.__autosavework = False
|
||||||
self.save("autosave_session.ses")
|
self.save("autosave_session.ses")
|
||||||
@ -140,6 +142,15 @@ class AccountManager:
|
|||||||
return self.accounts[username]["sftp_path"]
|
return self.accounts[username]["sftp_path"]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def set_banner(self, username, banner):
|
||||||
|
if username in self.accounts:
|
||||||
|
self.accounts[username]["banner"] = banner
|
||||||
|
|
||||||
|
def get_banner(self, username):
|
||||||
|
if username in self.accounts and "banner" in self.accounts[username]:
|
||||||
|
return self.accounts[username]["banner"]
|
||||||
|
return None
|
||||||
|
|
||||||
def get_user_timeout(self, username):
|
def get_user_timeout(self, username):
|
||||||
if username in self.accounts and "timeout" in self.accounts[username]:
|
if username in self.accounts and "timeout" in self.accounts[username]:
|
||||||
return self.accounts[username]["timeout"]
|
return self.accounts[username]["timeout"]
|
||||||
|
38
src/PyserSSH/demo/__init__.py
Normal file
38
src/PyserSSH/demo/__init__.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"""
|
||||||
|
PyserSSH - A Scriptable SSH server. For more info visit https://github.com/damp11113/PyserSSH
|
||||||
|
Copyright (C) 2023-2024 damp11113 (MIT)
|
||||||
|
|
||||||
|
Visit https://github.com/damp11113/PyserSSH
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
note
|
||||||
|
|
||||||
|
ansi cursor arrow
|
||||||
|
up - \x1b[A
|
||||||
|
down - \x1b[B
|
||||||
|
left - \x1b[D
|
||||||
|
right - \x1b[C
|
||||||
|
|
||||||
|
https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
"""
|
@ -2,68 +2,72 @@ import os
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import shlex
|
import shlex
|
||||||
from damp11113 import TextFormatter, sort_files, allfiles
|
|
||||||
import cv2
|
import cv2
|
||||||
import traceback
|
import traceback
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
import pyfiglet
|
||||||
|
|
||||||
from PyserSSH import Server, AccountManager, Send, wait_input, wait_inputkey, wait_choose, Clear, Title
|
from ..server import Server
|
||||||
from PyserSSH.system.info import system_banner
|
from ..account import AccountManager
|
||||||
from PyserSSH.extensions.processbar import indeterminateStatus, LoadingProgress
|
from ..interactive import Send, Clear, wait_input, wait_inputkey, wait_choose
|
||||||
from PyserSSH.extensions.dialog import MenuDialog, TextDialog, TextInputDialog
|
from ..system.info import system_banner, __version__
|
||||||
from PyserSSH.extensions.moredisplay import clickable_url
|
from ..extensions.processbar import (indeterminateStatus, LoadingProgress)
|
||||||
|
from ..extensions.dialog import MenuDialog, TextDialog, TextInputDialog
|
||||||
|
from ..extensions.moredisplay import clickable_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
from damp11113 import TextFormatter
|
||||||
|
except:
|
||||||
|
print("No 'damp11113-library'")
|
||||||
|
print("This demo is require 'damp11113-library' for run")
|
||||||
|
ins = input("Do you want to install 'damp11113-library'? (y/n): ")
|
||||||
|
if ins.upper() in ["Y", "YES"]:
|
||||||
|
import pip
|
||||||
|
pip.main(["install", "damp11113"])
|
||||||
|
from damp11113 import TextFormatter
|
||||||
|
else:
|
||||||
|
exit()
|
||||||
|
|
||||||
useraccount = AccountManager()
|
useraccount = AccountManager()
|
||||||
useraccount.add_account("admin", "") # create user without password
|
useraccount.add_account("admin", "") # create user without password
|
||||||
useraccount.add_account("test", "test") # create user without password
|
|
||||||
|
|
||||||
ssh = Server(useraccount, system_commands=True, system_message=False)
|
ssh = Server(useraccount, system_commands=True, system_message=False, sftp=False)
|
||||||
|
|
||||||
nonamewarning = """Connection Warning:
|
|
||||||
Unauthorized access or improper use of this system is prohibited.
|
|
||||||
Please ensure you have proper authorization before proceeding."""
|
|
||||||
|
|
||||||
Authorizedmessage = """You have successfully connected to the server.
|
|
||||||
Enjoy your session and remember to follow security protocols."""
|
|
||||||
|
|
||||||
loading = ["PyserSSH", "Extensions"]
|
loading = ["PyserSSH", "Extensions"]
|
||||||
|
|
||||||
|
print("you connect to this demo using 'ssh admin@localhost -p 2222' (no password)")
|
||||||
|
print("command list: passtest, colortest, typing <speed> <text>, renimtest, errortest, inloadtest, loadtest, dialogtest, dialogtest2, dialogtest3, passdialogtest3, choosetest, vieweb <url>, shutdown now")
|
||||||
|
print("Do not you this demo private key for real production")
|
||||||
|
|
||||||
@ssh.on_user("connect")
|
@ssh.on_user("connect")
|
||||||
def connect(client):
|
def connect(client):
|
||||||
Title(client, "PyserSSH")
|
wm = f"""{pyfiglet.figlet_format('PyserSSH', font='usaflag', width=client["windowsize"]["width"])}*********************************************************************************************
|
||||||
#print(client["windowsize"])
|
|
||||||
if client['current_user'] == "":
|
|
||||||
warningmessage = nonamewarning
|
|
||||||
else:
|
|
||||||
warningmessage = Authorizedmessage
|
|
||||||
|
|
||||||
wm = f"""*********************************************************************************************
|
|
||||||
Hello {client['current_user']},
|
Hello {client['current_user']},
|
||||||
|
|
||||||
{warningmessage}
|
This is the testing server of PyserSSH v{__version__}.
|
||||||
|
For use in product please use new private key.
|
||||||
|
|
||||||
Visit: {clickable_url("https://damp11113.xyz", "DPCloudev")}
|
Visit: {clickable_url("https://damp11113.xyz", "DPCloudev")}
|
||||||
|
|
||||||
{system_banner}
|
{system_banner}
|
||||||
*********************************************************************************************"""
|
*********************************************************************************************"""
|
||||||
|
|
||||||
if client['current_user'] != "test":
|
for i in loading:
|
||||||
for i in loading:
|
P = indeterminateStatus(client, f"Starting {i}", f"[ OK ] Started {i}")
|
||||||
P = indeterminateStatus(client, f"Starting {i}", f"[ OK ] Started {i}")
|
P.start()
|
||||||
P.start()
|
|
||||||
|
|
||||||
time.sleep(len(i) / 20)
|
time.sleep(len(i) / 20)
|
||||||
|
|
||||||
P.stop()
|
P.stop()
|
||||||
|
|
||||||
Di1 = TextDialog(client, "PyserSSH Extension", "Welcome!\n to PyserSSH test server")
|
Di1 = TextDialog(client, "PyserSSH Extension", "Welcome!\n to PyserSSH test server")
|
||||||
Di1.render()
|
Di1.render()
|
||||||
|
|
||||||
for char in wm:
|
for char in wm:
|
||||||
Send(client, char, ln=False)
|
Send(client, char, ln=False)
|
||||||
# time.sleep(0.005) # Adjust the delay as needed
|
# time.sleep(0.005) # Adjust the delay as needed
|
||||||
Send(client, '\n') # Send newline after each line
|
Send(client, '\n') # Send newline after each line
|
||||||
|
|
||||||
@ssh.on_user("error")
|
@ssh.on_user("error")
|
||||||
def error(client, error):
|
def error(client, error):
|
||||||
@ -72,7 +76,6 @@ def error(client, error):
|
|||||||
else:
|
else:
|
||||||
Send(client, traceback.format_exc())
|
Send(client, traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
#@ssh.on_user("onrawtype")
|
#@ssh.on_user("onrawtype")
|
||||||
#def onrawtype(client, key):
|
#def onrawtype(client, key):
|
||||||
# print(key)
|
# print(key)
|
||||||
@ -95,11 +98,11 @@ def command(client, command: str):
|
|||||||
Send(client, "")
|
Send(client, "")
|
||||||
Send(client, "TrueColors 24-Bit")
|
Send(client, "TrueColors 24-Bit")
|
||||||
elif command == "keytest":
|
elif command == "keytest":
|
||||||
user = wait_inputkey(client, "press any key", raw=True)
|
user = wait_inputkey(client, "press any key", raw=True, timeout=1)
|
||||||
Send(client, "")
|
Send(client, "")
|
||||||
Send(client, f"key: {user}")
|
Send(client, f"key: {user}")
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
user = wait_inputkey(client, "press any key", raw=True)
|
user = wait_inputkey(client, "press any key", raw=True, timeout=1)
|
||||||
Send(client, "")
|
Send(client, "")
|
||||||
Send(client, f"key: {user}")
|
Send(client, f"key: {user}")
|
||||||
elif command.startswith("typing"):
|
elif command.startswith("typing"):
|
||||||
@ -110,10 +113,9 @@ def command(client, command: str):
|
|||||||
Send(client, w, ln=False)
|
Send(client, w, ln=False)
|
||||||
time.sleep(speed)
|
time.sleep(speed)
|
||||||
Send(client, "")
|
Send(client, "")
|
||||||
elif command.startswith("renimtest"):
|
elif command == "renimtest":
|
||||||
args = shlex.split(command)
|
|
||||||
Clear(client)
|
Clear(client)
|
||||||
image = cv2.imread(f"opensource.png", cv2.IMREAD_COLOR)
|
image = cv2.imread(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'opensource.png'), cv2.IMREAD_COLOR)
|
||||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||||
|
|
||||||
width, height = client['windowsize']["width"]-5, client['windowsize']["height"]-5
|
width, height = client['windowsize']["width"]-5, client['windowsize']["height"]-5
|
||||||
@ -126,7 +128,6 @@ def command(client, command: str):
|
|||||||
for y in range(0, height):
|
for y in range(0, height):
|
||||||
for x in range(0, width):
|
for x in range(0, width):
|
||||||
pixel_color = resized[y, x]
|
pixel_color = resized[y, x]
|
||||||
# PyserSSH.Send(channel, f"Pixel color at ({x}, {y}): {pixel_color}")
|
|
||||||
if pixel_color.tolist() != [0, 0, 0]:
|
if pixel_color.tolist() != [0, 0, 0]:
|
||||||
t += TextFormatter.format_text_truecolor(" ", background=f"{pixel_color[0]};{pixel_color[1]};{pixel_color[2]}")
|
t += TextFormatter.format_text_truecolor(" ", background=f"{pixel_color[0]};{pixel_color[1]};{pixel_color[2]}")
|
||||||
else:
|
else:
|
||||||
@ -192,6 +193,7 @@ def command(client, command: str):
|
|||||||
loading.stop()
|
loading.stop()
|
||||||
Di1 = TextDialog(client, url, text_content)
|
Di1 = TextDialog(client, url, text_content)
|
||||||
Di1.render()
|
Di1.render()
|
||||||
|
elif command == "shutdown now":
|
||||||
|
ssh.stop_server()
|
||||||
|
|
||||||
|
ssh.run(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'private_key.pem'))
|
||||||
ssh.run(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'private_key.pem'))
|
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
27
src/PyserSSH/demo/private_key.pem
Normal file
27
src/PyserSSH/demo/private_key.pem
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEAwNfkia91HNrpyqlHwjYrVKDV5SkDt5P27MxKZDjwOokGBX7E
|
||||||
|
g5cMXb1wxQeCm+zptg680qIXHfSaaOi1E/DAutaTIQa3GI+gDMphlWMxrEWFuZOB
|
||||||
|
ylvTuFAxLB8xKcuBjelQX4TYlcgA1WgyeI6LFPNdJPekVHnzkLCZnW+y05PkT6a0
|
||||||
|
QY1Eoa6DY2TtY8w4NZmnyCy1ZPYV5qLKN/P7aVSU52AD8u25St1WprvxpM4TtZiG
|
||||||
|
2O9X1Unx+wtco2P8G1M4qcuWqPDdITn4n19DcR7rhuACjUo2poFTlnl9lfEsW11R
|
||||||
|
5sDfYlgc3n8a4Iw49Ea4GaLkSEMluOfB9eOLUQIDAQABAoIBAAeZmpVTN7uFjyLg
|
||||||
|
YrEZ6cGXPcbJw9k8zhr3/tM4q+hf/7+WBuWkEtCGR5xO7Ev73XFs3u6IL9QLkKL4
|
||||||
|
z4YefgypqeO/0YB4zJckdLhqpTRZCxOiEhfpCuI1MDLrycgQD/uJSenHIQgKI/+a
|
||||||
|
cH7Ffgq7Kp0V22vu4HVVLcCsJxvlIxFd92xCKFl8zRHBdyKikfvZAEidbMu9xdsW
|
||||||
|
S9DzFCveCGrE8g6HWQyXiCpq2xb4b2C37O+0iZRtYfJQSCrnG99Y/KfWIVbb+3gU
|
||||||
|
5WbIlYm57TKzMGgKc3LWtGCWxfB/NNP5wOxR+4y78oWDzTibrT5OZDsX2S+mbgNB
|
||||||
|
wAo/0U8CgYEAxHAOrlz9Ae2kYfyUgx9JTonElIFlDmDVdYcRW8Go7xpeMZ+XG2sR
|
||||||
|
f/za6t6jiCxI9FSD5gl4nDyOVhx5zRpu2QZvZBHICaWDwEmZC+d3suYtQY/ixR3K
|
||||||
|
3sdDKK6wzOtta+OBVNPQWAW2rmTr/J1JobguflM0NBm+YZC02gQyL9sCgYEA+1DU
|
||||||
|
llDGDaU08WQNTLRgW+1RAbzsBTFd+DhvbYM8+mgmlFzHKHJP3jCpwLZmqdBzLl0R
|
||||||
|
wUZBwpZ5MnkiQV0e9AW4/tnqBw8n9pf+NgNqcssw8MEMXHPbLNwr7OVS/LG8VNOm
|
||||||
|
LbuLjxq8O8wfbS87eBj2D18c1x4voEIw1AWYn0MCgYEAnPBF2moyPMMmjJ5l7Ggn
|
||||||
|
ghaxNlA2c4lLoOz7IkqTdAul65FsARzGS3GxWOnsztNKqeGHy1YPxQrgUM3JReLz
|
||||||
|
YnIwtks6fPJ+Uza5jngr+oLI71NMQl1uAhRChJMkb2M79XE6l5HuJxTRgXzhyN3E
|
||||||
|
wO5MPuKsl19l6b7ZrkCh8/cCgYEAjIL6+TgcI9D0suo/zV0kawFaw1//jj+1zGyx
|
||||||
|
UEeKNm848saUy3ZuVUpb/tV8vQFBBPEgVjGT3toG1UOI9Ya9Ia55anQoNt4wd90v
|
||||||
|
Ur/CKoCU0mb9JEvahVBsdr0ZExPEuqDDTtqHAvHtwHk2MPOxikpaeOmy1EuaUT3w
|
||||||
|
0vp2BMUCgYEAsLL592l8pclhxk2b0lmgvhPLOmZuQ7QkcnMMyYCeUr9Kt95VN40J
|
||||||
|
N/LK9LIbf/l9CUN4eO1JqCJkAiMIW2Gvumw3g+TMj+nqcfsufSHJCG1EZNYMUftG
|
||||||
|
aL7KtccPyFwotMD/P+OaAeJimwuC5247hCep1SSf1A41gbdmutiirM4=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@ -36,12 +36,15 @@ def Send(client, string, ln=True):
|
|||||||
else:
|
else:
|
||||||
channel.send(replace_enter_with_crlf(str(string)))
|
channel.send(replace_enter_with_crlf(str(string)))
|
||||||
|
|
||||||
def Clear(client, oldclear=False):
|
def Clear(client, oldclear=False, keep=False):
|
||||||
sx, sy = client["windowsize"]["width"], client["windowsize"]["height"]
|
sx, sy = client["windowsize"]["width"], client["windowsize"]["height"]
|
||||||
|
|
||||||
if oldclear:
|
if oldclear:
|
||||||
for x in range(sy):
|
for x in range(sy):
|
||||||
Send(client, '\b \b' * sx, ln=False) # Send newline after each line
|
Send(client, '\b \b' * sx, ln=False) # Send newline after each line
|
||||||
|
elif keep:
|
||||||
|
Send(client, "\033[2J", ln=False)
|
||||||
|
Send(client, "\033[H", ln=False)
|
||||||
else:
|
else:
|
||||||
Send(client, "\033[3J", ln=False)
|
Send(client, "\033[3J", ln=False)
|
||||||
Send(client, "\033[1J", ln=False)
|
Send(client, "\033[1J", ln=False)
|
||||||
|
@ -36,23 +36,22 @@ from .system.SFTP import SSHSFTPServer
|
|||||||
from .system.interface import Sinterface
|
from .system.interface import Sinterface
|
||||||
from .interactive import *
|
from .interactive import *
|
||||||
from .system.inputsystem import expect
|
from .system.inputsystem import expect
|
||||||
from .system.info import system_banner, __version__
|
from .system.info import __version__
|
||||||
|
|
||||||
#paramiko.sftp_file.SFTPFile.MAX_REQUEST_SIZE = pow(2, 22)
|
# paramiko.sftp_file.SFTPFile.MAX_REQUEST_SIZE = pow(2, 22)
|
||||||
|
|
||||||
sftpclient = ["WinSCP", "Xplore"]
|
sftpclient = ["WinSCP", "Xplore"]
|
||||||
|
|
||||||
logger = logging.getLogger("PyserSSH")
|
logger = logging.getLogger("PyserSSH")
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
def __init__(self, accounts, system_message=True, disable_scroll_with_arrow=True, sftp=True, sftproot=os.getcwd(), system_commands=True, compression=True, usexternalauth=False, history=True, inputsystem=True, XHandler=None, title=f"PyserSSH v{__version__}", inspeed=32768):
|
def __init__(self, accounts, system_message=True, disable_scroll_with_arrow=True, sftp=False, sftproot=os.getcwd(), system_commands=True, compression=True, usexternalauth=False, history=True, inputsystem=True, XHandler=None, title=f"PyserSSH v{__version__}", inspeed=32768):
|
||||||
"""
|
"""
|
||||||
A simple SSH server
|
A simple SSH server
|
||||||
"""
|
"""
|
||||||
self._event_handlers = {}
|
self._event_handlers = {}
|
||||||
self.sysmess = system_message
|
self.sysmess = system_message
|
||||||
self.client_handlers = {} # Dictionary to store event handlers for each client
|
self.client_handlers = {} # Dictionary to store event handlers for each client
|
||||||
self.current_users = {} # Dictionary to store current_user for each connected client
|
|
||||||
self.accounts = accounts
|
self.accounts = accounts
|
||||||
self.disable_scroll_with_arrow = disable_scroll_with_arrow
|
self.disable_scroll_with_arrow = disable_scroll_with_arrow
|
||||||
self.sftproot = sftproot
|
self.sftproot = sftproot
|
||||||
@ -66,7 +65,9 @@ class Server:
|
|||||||
self.title = title
|
self.title = title
|
||||||
self.inspeed = inspeed
|
self.inspeed = inspeed
|
||||||
|
|
||||||
self.system_banner = system_banner
|
self.__processmode = None
|
||||||
|
self.__serverisrunning = False
|
||||||
|
self.__server_stopped = threading.Event() # Event to signal server stop
|
||||||
|
|
||||||
if self.enasyscom:
|
if self.enasyscom:
|
||||||
print("\033[33m!!Warning!! System commands is enable! \033[0m")
|
print("\033[33m!!Warning!! System commands is enable! \033[0m")
|
||||||
@ -104,7 +105,6 @@ class Server:
|
|||||||
SSHSFTPServer.CLIENTHANDELES = self.client_handlers
|
SSHSFTPServer.CLIENTHANDELES = self.client_handlers
|
||||||
bh_session.set_subsystem_handler('sftp', paramiko.SFTPServer, SSHSFTPServer)
|
bh_session.set_subsystem_handler('sftp', paramiko.SFTPServer, SSHSFTPServer)
|
||||||
|
|
||||||
|
|
||||||
if self.compressena:
|
if self.compressena:
|
||||||
bh_session.use_compression(True)
|
bh_session.use_compression(True)
|
||||||
else:
|
else:
|
||||||
@ -139,13 +139,17 @@ class Server:
|
|||||||
"connecttype": None,
|
"connecttype": None,
|
||||||
"last_login_time": None,
|
"last_login_time": None,
|
||||||
"windowsize": {},
|
"windowsize": {},
|
||||||
"x11": {}
|
"x11": {},
|
||||||
|
"prompt": None,
|
||||||
|
"inputbuffer": None,
|
||||||
|
"peername": peername
|
||||||
}
|
}
|
||||||
client_handler = self.client_handlers[peername]
|
client_handler = self.client_handlers[peername]
|
||||||
client_handler["current_user"] = server.current_user
|
client_handler["current_user"] = server.current_user
|
||||||
client_handler["channel"] = channel # Update the channel attribute for the client handler
|
client_handler["channel"] = channel # Update the channel attribute for the client handler
|
||||||
client_handler["last_activity_time"] = time.time()
|
client_handler["last_activity_time"] = time.time()
|
||||||
client_handler["last_login_time"] = time.time()
|
client_handler["last_login_time"] = time.time()
|
||||||
|
client_handler["prompt"] = self.accounts.get_prompt(server.current_user)
|
||||||
|
|
||||||
self.accounts.set_user_last_login(self.client_handlers[channel.getpeername()]["current_user"], peername[0])
|
self.accounts.set_user_last_login(self.client_handlers[channel.getpeername()]["current_user"], peername[0])
|
||||||
|
|
||||||
@ -154,10 +158,11 @@ class Server:
|
|||||||
while self.client_handlers[channel.getpeername()]["windowsize"] == {}:
|
while self.client_handlers[channel.getpeername()]["windowsize"] == {}:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
channel.send(f"\033]0;{self.title}\007".encode())
|
userbanner = self.accounts.get_banner(self.client_handlers[channel.getpeername()]["current_user"])
|
||||||
|
|
||||||
if self.sysmess:
|
if self.sysmess or userbanner != None:
|
||||||
channel.sendall(replace_enter_with_crlf(self.system_banner))
|
channel.send(f"\033]0;{self.title}\007".encode())
|
||||||
|
channel.sendall(replace_enter_with_crlf(userbanner))
|
||||||
channel.sendall(replace_enter_with_crlf("\n"))
|
channel.sendall(replace_enter_with_crlf("\n"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -172,9 +177,9 @@ class Server:
|
|||||||
channel.setblocking(False)
|
channel.setblocking(False)
|
||||||
channel.settimeout(self.accounts.get_user_timeout(self.client_handlers[channel.getpeername()]["current_user"]))
|
channel.settimeout(self.accounts.get_user_timeout(self.client_handlers[channel.getpeername()]["current_user"]))
|
||||||
|
|
||||||
channel.send(replace_enter_with_crlf(self.accounts.get_prompt(self.client_handlers[channel.getpeername()]["current_user"]) + " ").encode('utf-8'))
|
channel.send(replace_enter_with_crlf(self.client_handlers[channel.getpeername()]["prompt"] + " ").encode('utf-8'))
|
||||||
while True:
|
while True:
|
||||||
expect(self, channel, peername)
|
expect(self, self.client_handlers[channel.getpeername()])
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self._handle_event("disconnected", self.client_handlers[peername]["current_user"])
|
self._handle_event("disconnected", self.client_handlers[peername]["current_user"])
|
||||||
channel.close()
|
channel.close()
|
||||||
@ -206,6 +211,7 @@ class Server:
|
|||||||
channel = client_handler.get("channel")
|
channel = client_handler.get("channel")
|
||||||
if channel:
|
if channel:
|
||||||
channel.close()
|
channel.close()
|
||||||
|
self.__serverisrunning = True
|
||||||
self.server.close()
|
self.server.close()
|
||||||
logger.info("Server stopped.")
|
logger.info("Server stopped.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -213,23 +219,34 @@ class Server:
|
|||||||
|
|
||||||
def _start_listening_thread(self):
|
def _start_listening_thread(self):
|
||||||
try:
|
try:
|
||||||
self.server.listen(10)
|
|
||||||
logger.info("Start Listening for connections...")
|
logger.info("Start Listening for connections...")
|
||||||
while True:
|
while self.__serverisrunning:
|
||||||
client, addr = self.server.accept()
|
client, addr = self.server.accept()
|
||||||
client_thread = threading.Thread(target=self.handle_client, args=(client, addr))
|
if self.__processmode == "thread":
|
||||||
client_thread.start()
|
client_thread = threading.Thread(target=self.handle_client, args=(client, addr))
|
||||||
|
client_thread.start()
|
||||||
|
else:
|
||||||
|
self.handle_client(client, addr)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
def run(self, private_key_path, host="0.0.0.0", port=2222):
|
def run(self, private_key_path, host="0.0.0.0", port=2222, mode="thread", maxuser=0, daemon=False):
|
||||||
|
"""mode: single, thread"""
|
||||||
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
|
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
|
||||||
self.server.bind((host, port))
|
self.server.bind((host, port))
|
||||||
self.private_key = paramiko.RSAKey(filename=private_key_path)
|
self.private_key = paramiko.RSAKey(filename=private_key_path)
|
||||||
|
if maxuser == 0:
|
||||||
|
self.server.listen()
|
||||||
|
else:
|
||||||
|
self.server.listen(maxuser)
|
||||||
|
|
||||||
|
self.__processmode = mode.lower()
|
||||||
|
self.__serverisrunning = True
|
||||||
|
|
||||||
client_thread = threading.Thread(target=self._start_listening_thread)
|
client_thread = threading.Thread(target=self._start_listening_thread)
|
||||||
|
client_thread.daemon = daemon
|
||||||
client_thread.start()
|
client_thread.start()
|
||||||
|
|
||||||
def kickbyusername(self, username, reason=None):
|
def kickbyusername(self, username, reason=None):
|
||||||
@ -304,4 +321,4 @@ class Server:
|
|||||||
logger.error(f"Error occurred while sending message to {username}: {e}")
|
logger.error(f"Error occurred while sending message to {username}: {e}")
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logger.warning(f"User '{username}' not found.")
|
logger.warning(f"User '{username}' not found.")
|
||||||
|
@ -25,7 +25,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "4.2"
|
__version__ = "4.4"
|
||||||
|
|
||||||
system_banner = (
|
system_banner = (
|
||||||
f"\033[36mPyserSSH V{__version__} \033[0m\n"
|
f"\033[36mPyserSSH V{__version__} \033[0m\n"
|
||||||
|
@ -35,12 +35,14 @@ from .syscom import systemcommand
|
|||||||
|
|
||||||
logger = logging.getLogger("PyserSSH")
|
logger = logging.getLogger("PyserSSH")
|
||||||
|
|
||||||
def expect(self, chan, peername, echo=True):
|
def expect(self, client, echo=True):
|
||||||
buffer = bytearray()
|
buffer = bytearray()
|
||||||
cursor_position = 0
|
cursor_position = 0
|
||||||
outindexall = 0
|
outindexall = 0
|
||||||
history_index_position = 0 # Initialize history index position outside the loop
|
history_index_position = 0 # Initialize history index position outside the loop
|
||||||
currentuser = self.client_handlers[chan.getpeername()]
|
chan = client["channel"]
|
||||||
|
peername = client["peername"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -66,7 +68,11 @@ def expect(self, chan, peername, echo=True):
|
|||||||
buffer = buffer[:cursor_position - 1] + buffer[cursor_position:]
|
buffer = buffer[:cursor_position - 1] + buffer[cursor_position:]
|
||||||
cursor_position -= 1
|
cursor_position -= 1
|
||||||
outindexall -= 1
|
outindexall -= 1
|
||||||
chan.sendall(b"\b \b")
|
if cursor_position != outindexall:
|
||||||
|
chan.sendall(b"\b \b")
|
||||||
|
chan.sendall(buffer[cursor_position:])
|
||||||
|
else:
|
||||||
|
chan.sendall(b"\b \b")
|
||||||
else:
|
else:
|
||||||
chan.sendall(b"\x07")
|
chan.sendall(b"\x07")
|
||||||
elif byte == b"\x1b" and chan.recv(1) == b'[':
|
elif byte == b"\x1b" and chan.recv(1) == b'[':
|
||||||
@ -76,18 +82,22 @@ def expect(self, chan, peername, echo=True):
|
|||||||
# Right arrow key, move cursor right if not at the end
|
# Right arrow key, move cursor right if not at the end
|
||||||
if cursor_position < len(buffer):
|
if cursor_position < len(buffer):
|
||||||
chan.sendall(b'\x1b[C')
|
chan.sendall(b'\x1b[C')
|
||||||
cursor_position += 1
|
# cursor_position += 1
|
||||||
|
cursor_position = min(len(buffer), cursor_position + 1)
|
||||||
|
|
||||||
elif arrow_key == b'D':
|
elif arrow_key == b'D':
|
||||||
# Left arrow key, move cursor left if not at the beginning
|
# Left arrow key, move cursor left if not at the beginning
|
||||||
if cursor_position > 0:
|
if cursor_position > 0:
|
||||||
chan.sendall(b'\x1b[D')
|
chan.sendall(b'\x1b[D')
|
||||||
cursor_position -= 1
|
# cursor_position -= 1
|
||||||
elif self.history:
|
cursor_position = max(0, cursor_position - 1)
|
||||||
|
|
||||||
|
if self.history:
|
||||||
if arrow_key == b'A':
|
if arrow_key == b'A':
|
||||||
if history_index_position == 0:
|
if history_index_position == 0:
|
||||||
command = self.accounts.get_lastcommand(currentuser["current_user"])
|
command = self.accounts.get_lastcommand(client["current_user"])
|
||||||
else:
|
else:
|
||||||
command = self.accounts.get_history(currentuser["current_user"], history_index_position)
|
command = self.accounts.get_history(client["current_user"], history_index_position)
|
||||||
|
|
||||||
# Clear the buffer
|
# Clear the buffer
|
||||||
for i in range(cursor_position):
|
for i in range(cursor_position):
|
||||||
@ -105,9 +115,9 @@ def expect(self, chan, peername, echo=True):
|
|||||||
elif arrow_key == b'B':
|
elif arrow_key == b'B':
|
||||||
if history_index_position != -1:
|
if history_index_position != -1:
|
||||||
if history_index_position == 0:
|
if history_index_position == 0:
|
||||||
command = self.accounts.get_lastcommand(currentuser["current_user"])
|
command = self.accounts.get_lastcommand(client["current_user"])
|
||||||
else:
|
else:
|
||||||
command = self.accounts.get_history(currentuser["current_user"], history_index_position)
|
command = self.accounts.get_history(client["current_user"], history_index_position)
|
||||||
|
|
||||||
# Clear the buffer
|
# Clear the buffer
|
||||||
for i in range(cursor_position):
|
for i in range(cursor_position):
|
||||||
@ -139,8 +149,10 @@ def expect(self, chan, peername, echo=True):
|
|||||||
self._handle_event("ontype", self.client_handlers[chan.getpeername()], byte)
|
self._handle_event("ontype", self.client_handlers[chan.getpeername()], byte)
|
||||||
if echo:
|
if echo:
|
||||||
if outindexall != cursor_position:
|
if outindexall != cursor_position:
|
||||||
|
chan.sendall(b" ")
|
||||||
|
chan.sendall(b'\033[s')
|
||||||
chan.sendall(byte + buffer[cursor_position:])
|
chan.sendall(byte + buffer[cursor_position:])
|
||||||
chan.sendall(f"\033[{cursor_position}G".encode())
|
chan.sendall(b'\033[u')
|
||||||
else:
|
else:
|
||||||
chan.sendall(byte)
|
chan.sendall(byte)
|
||||||
|
|
||||||
@ -149,13 +161,15 @@ def expect(self, chan, peername, echo=True):
|
|||||||
cursor_position += 1
|
cursor_position += 1
|
||||||
outindexall += 1
|
outindexall += 1
|
||||||
|
|
||||||
|
client["inputbuffer"] = buffer
|
||||||
|
|
||||||
if echo:
|
if echo:
|
||||||
chan.sendall(b'\r\n')
|
chan.sendall(b'\r\n')
|
||||||
|
|
||||||
command = str(buffer.decode('utf-8')).strip()
|
command = str(buffer.decode('utf-8')).strip()
|
||||||
|
|
||||||
if self.history and command.strip() != "" and self.accounts.get_lastcommand(currentuser["current_user"]) != command:
|
if self.history and command.strip() != "" and self.accounts.get_lastcommand(client["current_user"]) != command:
|
||||||
self.accounts.add_history(currentuser["current_user"], command)
|
self.accounts.add_history(client["current_user"], command)
|
||||||
|
|
||||||
if command.strip() != "":
|
if command.strip() != "":
|
||||||
if self.accounts.get_user_timeout(self.client_handlers[chan.getpeername()]["current_user"]) != None:
|
if self.accounts.get_user_timeout(self.client_handlers[chan.getpeername()]["current_user"]) != None:
|
||||||
@ -164,25 +178,25 @@ def expect(self, chan, peername, echo=True):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if self.enasyscom:
|
if self.enasyscom:
|
||||||
sct = systemcommand(currentuser, command)
|
sct = systemcommand(client, command)
|
||||||
else:
|
else:
|
||||||
sct = False
|
sct = False
|
||||||
|
|
||||||
if not sct:
|
if not sct:
|
||||||
if self.XHandler != None:
|
if self.XHandler != None:
|
||||||
self._handle_event("beforexhandler", currentuser, command)
|
self._handle_event("beforexhandler", client, command)
|
||||||
|
|
||||||
self.XHandler.call(currentuser, command)
|
self.XHandler.call(client, command)
|
||||||
|
|
||||||
self._handle_event("afterxhandler", currentuser, command)
|
self._handle_event("afterxhandler", client, command)
|
||||||
else:
|
else:
|
||||||
self._handle_event("command", currentuser, command)
|
self._handle_event("command", client, command)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._handle_event("error", currentuser, e)
|
self._handle_event("error", client, e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
chan.send(replace_enter_with_crlf(self.accounts.get_prompt(currentuser["current_user"]) + " ").encode('utf-8'))
|
chan.send(replace_enter_with_crlf(client["prompt"] + " ").encode('utf-8'))
|
||||||
except:
|
except:
|
||||||
logger.error("Send error")
|
logger.error("Send error")
|
||||||
|
|
||||||
|
@ -26,13 +26,11 @@ SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from ..interactive import *
|
from ..interactive import Send, Clear, Title
|
||||||
|
|
||||||
def systemcommand(client, command):
|
def systemcommand(client, command):
|
||||||
channel = client["channel"]
|
|
||||||
|
|
||||||
if command == "whoami":
|
if command == "whoami":
|
||||||
Send(channel, client["current_user"])
|
Send(client, client["current_user"])
|
||||||
return True
|
return True
|
||||||
elif command.startswith("title"):
|
elif command.startswith("title"):
|
||||||
args = shlex.split(command)
|
args = shlex.split(command)
|
||||||
@ -40,7 +38,7 @@ def systemcommand(client, command):
|
|||||||
Title(client, title)
|
Title(client, title)
|
||||||
return True
|
return True
|
||||||
elif command == "exit":
|
elif command == "exit":
|
||||||
channel.close()
|
client["channel"].close()
|
||||||
return True
|
return True
|
||||||
elif command == "clear":
|
elif command == "clear":
|
||||||
Clear(client)
|
Clear(client)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user