-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
106 lines (96 loc) · 4.31 KB
/
server.py
File metadata and controls
106 lines (96 loc) · 4.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import scratchattach as scratch3
import json
import datetime
import asyncio
from typing import Callable, Optional, Awaitable
config = {
"username": "USERNAME HERE",
"session": "SESSION ID HERE",
"target_user": "griffpatch",
"whitelistEnabled": False,
"whitelist": ["username1", "username2"],
"runAsAdmin": False,
"commentsSynced": []
}
sessuion = None
boot_time = datetime.datetime.now()
def load_config():
with open('config.json', 'r') as f:
return json.load(f)
def setup_scratch_session():
global session, config
try:
config = load_config()
except Exception as e:
print("Failed to load config:", e)
exit(1)
print("Config loaded.")
print("Logging in...")
session = scratch3.login_by_id(config["session"], username=config["username"])
print("Logged in as", config["username"])
async def poll_scratch_comments(username: str, callback: Callable[[dict], Awaitable[None]], interval: int = 2):
"""
Polls the Scratch profile for new comments and fires callback when a new comment is posted.
:param username: Scratch username to monitor
:param callback: Async function to call with the new comment dict
:param interval: Polling interval in seconds
"""
last_comment_id: Optional[int] = None
print(f"Monitoring comments for Scratch user: {username}")
user = session.connect_user(username)
while True:
try:
comments = user.comments(limit=10)
if comments:
print(f"Fetched {len(comments)} comments.")
for j in comments:
timestamp = str.split(j.datetime_created, "T")
timestamp = str.split(timestamp[0], "-")
timestamp = datetime.datetime(int(timestamp[0]), int(timestamp[1]), int(timestamp[2]))
# Only process comments created after boot_time
threshold = boot_time - datetime.timedelta(days=1)
if str(j.id) not in config["commentsSynced"] and timestamp > threshold:
await callback({
'author': {'username': j.author_name},
'content': j.content,
'id': j.id,
"comment_obj": j
})
config["commentsSynced"].append(str(j.id))
with open('config.json', 'w') as f:
json.dump(config, f, indent=4)
except Exception as e:
raise e
await asyncio.sleep(interval)
async def parse_comment(comment):
username = comment.get('author', {}).get('username')
content = comment.get('content')
id = comment.get('id')
print(f"Processing comment by {comment.get('author', {}).get('username')}: {comment.get('content')}")
if config["whitelistEnabled"]:
if username not in config["whitelist"]:
print(f"{username} is not whitelisted. Ignoring comment.")
return
output = await run_shell_command(content)
comment_obj = comment.get("comment_obj")
comment_obj.reply(f"@{username} Command output:\n{output}")
print(f"Replied to comment ID {comment.get('id')}")
async def run_shell_command(command: str) -> str:
proc = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
return stdout.decode() + stderr.decode()
if __name__ == "__main__":
setup_scratch_session()
if config["runAsAdmin"]:
print("Warning: The remote shell is running with admin privileges. This means others could cause damage to your system or comprimise your device and network. Proceed with caution.")
if config["whitelistEnabled"]:
print("Whitelist is enabled. Only the following users can run commands:")
for user in config["whitelist"]:
print(f"- {user}")
if not config["whitelistEnabled"]:
print("Warning: Whitelist is disabled. This means anyone can run commands on your system. Proceed with caution.")
asyncio.run(poll_scratch_comments(config["target_user"], parse_comment))