├── LICENSE
├── README.md
├── bot
├── bot.py
├── bot_setup.py
├── db.py
└── utils.py
└── requirements.txt
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Tostapunk
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # PC Control bot
5 |
6 | Through this bot you can execute actions on your PC directly from Telegram!
7 |
8 | ## Getting Started
9 |
10 | ### Prerequisites
11 |
12 | - Python 3.6+
13 | - A [BotFather](https://t.me/BotFather) token
14 |
15 | GNU/Linux users: to use UI features (setup with UI and memo) you need to install the python-tk package
16 |
17 | ### Install the requirements
18 | Execute ```python -m pip install -r requirements.txt```
19 |
20 | ## Setup the bot
21 | ### UI
22 | Launch the setup with ```python bot/bot_setup.py```
23 |
24 | Add your BotFather token and start it!
25 |
26 | 
27 |
28 | ### Command line
29 | You can also setup the bot from the command line by using ```python bot/bot_setup.py``` followed by a valid option.\
30 | To see all the available options use ```python bot/bot_setup.py -h```
31 |
32 | ## Set the permissions
33 |
34 | **The first user registered into the database will have admin permissions by default.** \
35 | You can add or remove a user from the admin group by using the UI or the command line.\
36 | **Note:** you need to use a Telegram username (write it without '@')
37 |
38 | UI example:
39 |
40 | 
41 |
42 | ## Available commands
43 |
44 | | Command | Description | Note
45 | | --- | --- | --- |
46 | | /shutdown | Shutdown your PC |
47 | | /reboot | Reboot your PC |
48 | | /logout | Log out from your current account | Currently not working on Linux |
49 | | /hibernate | Hibernate your PC |
50 | | /lock | Lock your PC | Currently not working on Linux |
51 | | /cancel | Annul the previous command | It work with the first two commands_t |
52 | | /check | Check the PC status |
53 | | /launch | Launch a program | Example: /launch notepad |
54 | | /link | Open a web link | Example: /link http://google.com (don't use "www") |
55 | | /memo | Show a memo on your pc | Tkinter needed |
56 | | /task | Check if a process is currently running or kill it| Example: /task chrome |
57 | | /screen | Take a screenshot and receive it |
58 | | /menu | Shows the inline menu |
59 | | /kb or /keyboard | Brings the normal keyboard up |
60 |
61 | You can set a delay time for the execution of the first four commands by using _t + time in minutes after a command.\
62 | Example: ```/shutdown_t 2```
63 |
64 | ## Contributors
65 | Thanks to [Jasoc](https://github.com/jasoc) for the awesome [logo](https://i.imgur.com/V6B5ZEf.png)!
66 |
--------------------------------------------------------------------------------
/bot/bot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import ctypes
5 | import getpass
6 | import logging
7 | import os
8 | import platform
9 | import socket
10 | import subprocess
11 | import sys
12 | import threading
13 | try:
14 | import tkinter as tk
15 | from tkinter import ttk
16 | except ImportError:
17 | pass
18 | from typing import Optional
19 | from shlex import quote
20 |
21 | import distro
22 | import psutil
23 | import pyscreenshot
24 | from telegram import ParseMode, ReplyKeyboardMarkup, ReplyKeyboardRemove, Update, Bot
25 | from telegram import error as tg_error
26 | from telegram.ext import CommandHandler, Filters, MessageHandler, Updater, CallbackContext
27 | from telegram.utils import helpers
28 |
29 | import db
30 | import utils
31 |
32 | if sys.version_info < (3, 6, 0):
33 | raise Exception("This bot works only with Python 3.6+")
34 |
35 | if db.exists() is False:
36 | raise Exception("You need to start bot_setup first")
37 |
38 | # Enable logging
39 | logging.basicConfig(
40 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
41 | level=logging.DEBUG)
42 | logger = logging.getLogger(__name__)
43 |
44 |
45 | def startupinfo() -> Optional[int]:
46 | if db.console_get() == "hide":
47 | if platform.system() == "Windows":
48 | value = subprocess.STARTUPINFO()
49 | value.dwFlags |= subprocess.STARTF_USESHOWWINDOW
50 | else:
51 | value = None
52 | else:
53 | value = None
54 | return value
55 |
56 |
57 | def start(update: Update, context: CallbackContext) -> None:
58 | db.update_user(update.message.from_user, context.bot)
59 | text = """Welcome to [PC\-Control bot](https://goo.gl/9TjCHR),
60 | you can get the bot profile picture [here](http://i.imgur.com/294uZ8G.png)
61 |
62 | Use /help to see all the commands\!
63 |
64 |
65 | Made by [Tostapunk](https://github.com/Tostapunk)"""
66 |
67 | context.bot.sendMessage(
68 | chat_id=update.message.chat.id,
69 | text=text,
70 | parse_mode=ParseMode.MARKDOWN_V2,
71 | disable_web_page_preview="true")
72 | keyboard_up(update, context)
73 |
74 |
75 | @db.admin_check
76 | def bot_help(update: Update, context: CallbackContext) -> None:
77 | db.update_user(update.message.from_user, context.bot)
78 | text = "*Available commands:*"
79 | text += helpers.escape_markdown("""
80 | /shutdown - To shutdown your PC
81 | /reboot - To reboot your PC
82 | /logout - To log out from your current account
83 | /hibernate - To hibernate your PC
84 | /lock - To lock your PC
85 | /cancel - To annul the previous command
86 | /check - To check the PC status
87 | /launch - To launch a program | Example: /launch notepad
88 | /link - To open a link | Example: /link http://google.com (don't use \"www\")
89 | /memo - To show a memo on your pc
90 | /task - To check if a process is currently running or to kill it | Example: /task chrome
91 | /screen - To take a screenshot and receive it
92 | /kb or /keyboard - Brings the normal keyboard up
93 |
94 | You can set a delay time for the execution of the first four commands by using _t + time in minutes after a command.
95 | Example: /shutdown_t 2""", 2)
96 | context.bot.sendMessage(
97 | chat_id=update.message.chat.id,
98 | text=text,
99 | parse_mode=ParseMode.MARKDOWN_V2,
100 | disable_web_page_preview="true")
101 |
102 |
103 | def keyboard_up(update: Update, context: CallbackContext) -> None:
104 | db.update_user(update.message.from_user, context.bot)
105 | text = "Keyboard is up"
106 | keyboard = [['Shutdown', 'Reboot'],
107 | ['Logout', 'Hibernate'],
108 | ['Lock', 'Screenshot'],
109 | ['PC status'],
110 | ['Cancel'],
111 | ['Close Keyboard']]
112 | reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
113 | update.message.reply_text(reply_markup=reply_markup, text=text)
114 |
115 |
116 | def message_handler(update: Update, context: CallbackContext) -> None:
117 | if update.message.text == "Shutdown":
118 | shutdown(update, context)
119 | elif update.message.text == "Reboot":
120 | reboot(update, context)
121 | elif update.message.text == "Logout":
122 | logout(update, context)
123 | elif update.message.text == "Hibernate":
124 | hibernate(update, context)
125 | elif update.message.text == "Lock":
126 | lock(update, context)
127 | elif update.message.text == "PC status":
128 | check(update, context)
129 | elif update.message.text == "Screenshot":
130 | screenshot(update, context)
131 | elif update.message.text == "Cancel":
132 | cancel(update, context)
133 | elif update.message.text == "Close Keyboard":
134 | text = "Keyboard is down."
135 | reply_markup = ReplyKeyboardRemove()
136 | update.message.reply_text(text=text, reply_markup=reply_markup)
137 | elif update.message.text == f"Kill {update.message.text[5:]}":
138 | task_kill(update, context)
139 | elif update.message.text == "Exit":
140 | keyboard_up(update, context)
141 |
142 |
143 | @db.admin_check
144 | def shutdown(update: Update, context: CallbackContext) -> None:
145 | db.update_user(update.message.from_user, context.bot)
146 | if platform.system() == "Windows":
147 | subprocess.run('shutdown /s', startupinfo=startupinfo())
148 | text = "Shutted down."
149 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
150 | else:
151 | subprocess.run('shutdown -h now', startupinfo=startupinfo(), shell=True)
152 | text = "Shutted down."
153 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
154 |
155 |
156 | @db.admin_check
157 | def shutdown_time(update: Update, context: CallbackContext) -> None:
158 | db.update_user(update.message.from_user, context.bot)
159 | if context.args:
160 | if platform.system() == "Windows":
161 | subprocess.run(f"shutdown /s /t {str(int(context.args[0])*60)}",
162 | startupinfo=startupinfo())
163 | text = "Shutting down..."
164 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
165 | else:
166 | subprocess.run(f"shutdown -P +{quote(context.args[0])}",
167 | startupinfo=startupinfo(), shell=True)
168 | text = "Shutting down..."
169 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
170 | else:
171 | text = """No time inserted
172 | ``` Usage: /shutdown_t + time in minutes```"""
173 | context.bot.sendMessage(chat_id=update.message.chat.id,
174 | text=text, parse_mode=ParseMode.MARKDOWN_V2)
175 |
176 |
177 | @db.admin_check
178 | def reboot(update: Update, context: CallbackContext) -> None:
179 | db.update_user(update.message.from_user, context.bot)
180 | if platform.system() == "Windows":
181 | subprocess.run('shutdown /r', startupinfo=startupinfo())
182 | text = "Rebooted."
183 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
184 | else:
185 | subprocess.run('reboot', startupinfo=startupinfo())
186 | text = "Rebooted."
187 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
188 |
189 |
190 | @db.admin_check
191 | def reboot_time(update: Update, context: CallbackContext) -> None:
192 | db.update_user(update.message.from_user, context.bot)
193 | if context.args:
194 | if platform.system() == "Windows":
195 | subprocess.run(f"shutdown /r /t {str(int(context.args[0])*60)}",
196 | startupinfo=startupinfo())
197 | text = "Rebooting..."
198 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
199 | else:
200 | subprocess.run(f"shutdown -r +{quote(context.args[0])}",
201 | startupinfo=startupinfo(), shell=True)
202 | text = "Rebooting..."
203 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
204 | else:
205 | text = """No time inserted
206 | ``` Usage: /reboot_t + time in minutes```"""
207 | context.bot.sendMessage(chat_id=update.message.chat.id,
208 | text=text, parse_mode=ParseMode.MARKDOWN_V2)
209 |
210 |
211 | @db.admin_check
212 | def logout(update: Update, context: CallbackContext) -> None:
213 | db.update_user(update.message.from_user, context.bot)
214 | if platform.system() == "Windows":
215 | subprocess.run('shutdown /l', startupinfo=startupinfo())
216 | text = "Logged out."
217 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
218 | else:
219 | text = "Currently not working on Linux."
220 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
221 |
222 |
223 | @db.admin_check
224 | def logout_time(update: Update, context: CallbackContext) -> None:
225 | db.update_user(update.message.from_user, context.bot)
226 | if platform.system() != "Windows":
227 | text = "Currently not working on Linux."
228 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
229 | elif not context.args:
230 | text = """No time inserted ```Usage: /logout_t + time in minutes```"""
231 | context.bot.sendMessage(chat_id=update.message.chat.id,
232 | text=text, parse_mode=ParseMode.MARKDOWN_V2)
233 | else:
234 | time = int(context.args[0])
235 | if thread_name := utils.ThreadTimer().start(logout, time, update, context):
236 | text = f"{thread_name} function is already running, cancel it and retry"
237 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
238 |
239 |
240 | @db.admin_check
241 | def hibernate(update: Update, context: CallbackContext) -> None:
242 | db.update_user(update.message.from_user, context.bot)
243 | if platform.system() == "Windows":
244 | subprocess.run('shutdown /h', startupinfo=startupinfo())
245 | text = "Hibernated."
246 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
247 | else:
248 | subprocess.run('systemctl suspend', startupinfo=startupinfo(), shell=True)
249 | text = "Hibernated."
250 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
251 |
252 |
253 | @db.admin_check
254 | def hibernate_time(update: Update, context: CallbackContext) -> None:
255 | db.update_user(update.message.from_user, context.bot)
256 | if not context.args:
257 | text = """No time inserted ```Usage: /hibernate_t + time in minutes```"""
258 | context.bot.sendMessage(chat_id=update.message.chat.id,
259 | text=text, parse_mode=ParseMode.MARKDOWN_V2)
260 | else:
261 | time = int(context.args[0])
262 | if thread_name := utils.ThreadTimer().start(hibernate, time, update, context):
263 | text = f"{thread_name} function is already running, cancel it and retry"
264 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
265 |
266 |
267 | @db.admin_check
268 | def lock(update: Update, context: CallbackContext) -> None:
269 | db.update_user(update.message.from_user, context.bot)
270 | if platform.system() == "Windows":
271 | ctypes.windll.user32.LockWorkStation()
272 | text = "PC locked."
273 | else:
274 | text = "Currently not working on Linux."
275 | context.bot.sendMessage(chat_id=update.message.from_user.id, text=text)
276 |
277 |
278 | @db.admin_check
279 | def cancel(update: Update, context: CallbackContext) -> None:
280 | db.update_user(update.message.from_user, context.bot)
281 | if (thread_name := utils.ThreadTimer().stop()):
282 | text = f"{thread_name} cancelled"
283 | else:
284 | text = "Annulled."
285 | if platform.system() == "Windows":
286 | subprocess.run('shutdown /a', startupinfo=startupinfo())
287 | else:
288 | subprocess.run('shutdown -c', startupinfo=startupinfo(), shell=True)
289 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
290 |
291 |
292 | @db.admin_check
293 | def check(update: Update, context: CallbackContext) -> None:
294 | db.update_user(update.message.from_user, context.bot)
295 | text = ""
296 | text += "Your PC is online.\n\n"
297 | text += f"PC name: {socket.gethostname()}"
298 | text += f"\nLogged user: {getpass.getuser()}"
299 | if platform.system() == "Windows":
300 | text += f"\nOS: Windows {platform.win32_ver()[0]}"
301 | else:
302 | text += f"\nOS: {distro.id()}"
303 | text += f"\nCPU: {str(psutil.cpu_percent())}%"
304 | text += f"\nMemory: {str(int(psutil.virtual_memory().percent))}%"
305 | battery = psutil.sensors_battery()
306 | if battery:
307 | text += f"\nBattery: {str(format(battery.percent, '.0f'))}%"
308 | if battery.power_plugged is True:
309 | text += " | Charging"
310 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
311 |
312 |
313 | @db.admin_check
314 | def launch(update: Update, context: CallbackContext) -> None:
315 | db.update_user(update.message.from_user, context.bot)
316 | if context.args:
317 | if platform.system() == "Windows":
318 | ret = subprocess.run(f"start {quote(context.args[0])}",
319 | startupinfo=startupinfo(), shell=True).returncode
320 | text = f"Launching {context.args[0]}..." if ret == 0 else f"Cannot launch {context.args[0]}"
321 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
322 | else:
323 | ret = subprocess.run(f"{str(quote(context.args[0]))} &",
324 | startupinfo=startupinfo(), shell=True).returncode
325 | text = f"Launching {context.args[0]}..." if ret == 0 else f"Cannot launch {context.args[0]}"
326 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
327 | else:
328 | text = """No program name inserted
329 | ``` Usage: /launch + program name```"""
330 | context.bot.sendMessage(chat_id=update.message.chat.id,
331 | text=text, parse_mode=ParseMode.MARKDOWN_V2)
332 |
333 |
334 | @db.admin_check
335 | def link(update: Update, context: CallbackContext) -> None:
336 | db.update_user(update.message.from_user, context.bot)
337 | if context.args:
338 | if platform.system() == "Windows":
339 | ret = subprocess.run(f"start {quote(context.args[0])}",
340 | startupinfo=startupinfo(), shell=True).returncode
341 | text = f"Opening {context.args[0]}..." if ret == 0 else f"Cannot open {context.args[0]}"
342 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
343 | else:
344 | ret = subprocess.run(f"xdg-open {quote(context.args[0])}",
345 | startupinfo=startupinfo(), shell=True).returncode
346 | text = f"Opening {context.args[0]}..." if ret == 0 else f"Cannot open {context.args[0]}"
347 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
348 | else:
349 | text = "No link inserted\n``` Usage: /link + web link```"
350 | context.bot.sendMessage(chat_id=update.message.chat.id,
351 | text=text, parse_mode=ParseMode.MARKDOWN_V2)
352 |
353 |
354 | @db.admin_check
355 | def memo_thread(update: Update, context: CallbackContext) -> None:
356 | db.update_user(update.message.from_user, context.bot)
357 | module = "tkinter"
358 | if module not in sys.modules:
359 | context.bot.sendMessage(chat_id=update.message.chat.id, text=f"Error: you need to install {module} to use this function")
360 |
361 | args = update.message.text[6:]
362 | if len(args) != 0:
363 | def memo() -> None:
364 | popup = tk.Tk()
365 | popup.wm_title("Memo")
366 | label = ttk.Label(
367 | popup,
368 | text=f"{args} \
369 | \nsent by {update.message.from_user.name} through PC-Control",
370 | font=(
371 | "Helvetica",
372 | 10))
373 | label.pack(side="top", fill="x", pady=10)
374 | B1 = ttk.Button(popup, text="Okay", command=popup.destroy)
375 | B1.pack()
376 | popup.mainloop()
377 |
378 | t = threading.Thread(target=memo)
379 | t.start()
380 | else:
381 | text = "No text inserted"
382 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
383 |
384 |
385 | @db.admin_check
386 | def task(update: Update, context: CallbackContext) -> None:
387 | db.update_user(update.message.from_user, context.bot)
388 | if context.args:
389 | kill_kb = [[f"Kill {context.args[0]}"],
390 | ["Exit"]]
391 | reply_markup = ReplyKeyboardMarkup(
392 | kill_kb, resize_keyboard=True)
393 | if platform.system() == "Windows":
394 | try:
395 | out = os.popen(f"tasklist | findstr {context.args[0]}").read()
396 | context.bot.sendMessage(chat_id=update.message.chat.id,
397 | text=out, reply_markup=reply_markup)
398 | except tg_error.BadRequest:
399 | context.bot.sendMessage(chat_id=update.message.chat.id, text="The program is not running")
400 | else:
401 | try:
402 | out = os.popen(f"ps -A | grep {context.args[0]}").read()
403 | context.bot.sendMessage(chat_id=update.message.chat.id, text=out, reply_markup=reply_markup)
404 | except tg_error.BadRequest:
405 | context.bot.sendMessage(chat_id=update.message.chat.id, text="The program is not running")
406 | else:
407 | text = "No task inserted\n``` Usage: /task + process name```"
408 | context.bot.sendMessage(chat_id=update.message.chat.id,
409 | text=text, parse_mode=ParseMode.MARKDOWN_V2)
410 |
411 |
412 | @db.admin_check
413 | def task_kill(update: Update, context: CallbackContext) -> None:
414 | db.update_user(update.message.from_user, context.bot)
415 | args = update.message.text[5:]
416 | if platform.system() == "Windows":
417 | try:
418 | ret = subprocess.run(f"tskill {quote(args)}", startupinfo=startupinfo()).returncode
419 | text =f"I've killed {args}" if ret == 0 else f"Cannot kill {args}"
420 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
421 | keyboard_up(update, context)
422 | except Exception as e:
423 | context.bot.sendMessage(chat_id=update.message.chat.id, text=f"Error: {str(e)}")
424 | else:
425 | try:
426 | ret = subprocess.run(f'pkill -f "^"{quote(args)}', startupinfo=startupinfo(), shell=True).returncode
427 | text =f"I've killed {args}" if ret == 0 else f"Cannot kill {args}"
428 | context.bot.sendMessage(chat_id=update.message.chat.id, text=text)
429 | keyboard_up(update, context)
430 | except Exception as e:
431 | context.bot.sendMessage(chat_id=update.message.chat.id, text=f"Error: {str(e)}")
432 |
433 |
434 | @db.admin_check
435 | def screenshot(update: Update, context: CallbackContext) -> None:
436 | db.update_user(update.message.from_user, context.bot)
437 | path = os.path.join(os.path.dirname(utils.current_path()), "tmp/screenshot.png")
438 | img = pyscreenshot.grab()
439 | img.save(path)
440 | context.bot.send_document(chat_id=update.message.chat.id, document=open(path, "rb"))
441 | os.remove(path)
442 |
443 |
444 | def is_up_notification(bot: Bot) -> None:
445 | admins = db.get_admins_id()
446 | for admin_id in admins:
447 | bot.sendMessage(chat_id=admin_id, text="Bot up and running")
448 |
449 |
450 | def error(update: Update, context: CallbackContext) -> None:
451 | logger.warning(f"Update {update} caused error {error}")
452 |
453 |
454 | def main() -> None:
455 | # Create the EventHandler and pass it your bot's token.
456 | if not db.token_get("BotFather_token"):
457 | raise ValueError("You need to add a BotFather token first")
458 |
459 | updater = Updater(db.token_get("BotFather_token"))
460 | # Get the dispatcher to register handlers
461 | dp = updater.dispatcher
462 |
463 | # Send a message when the bot is up and running
464 | is_up_notification(updater.bot)
465 |
466 | # Start
467 | dp.add_handler(CommandHandler("start", start))
468 |
469 | # Help
470 | dp.add_handler(CommandHandler("help", bot_help))
471 |
472 | # Send keyboard up
473 | dp.add_handler(CommandHandler("keyboard", keyboard_up))
474 | dp.add_handler(CommandHandler("kb", keyboard_up))
475 |
476 | # Shutdown
477 | dp.add_handler(CommandHandler("shutdown", shutdown))
478 |
479 | # Shutdown time
480 | dp.add_handler(CommandHandler(
481 | "shutdown_t", shutdown_time, pass_args=True))
482 |
483 | # Reboot
484 | dp.add_handler(CommandHandler("reboot", reboot))
485 |
486 | # Reboot time
487 | dp.add_handler(CommandHandler(
488 | "reboot_t", reboot_time, pass_args=True))
489 |
490 | # Log out
491 | dp.add_handler(CommandHandler("logout", logout))
492 |
493 | # Log out time
494 | dp.add_handler(CommandHandler("logout_t", logout_time))
495 |
496 | # Hibernate
497 | dp.add_handler(CommandHandler("hibernate", hibernate))
498 |
499 | # Hibernate time
500 | dp.add_handler(CommandHandler("hibernate_t", hibernate_time))
501 |
502 | # Lock
503 | dp.add_handler(CommandHandler("lock", lock))
504 |
505 | # Annul the previous command
506 | dp.add_handler(CommandHandler("cancel", cancel))
507 |
508 | # Check the PC status
509 | dp.add_handler(CommandHandler("check", check))
510 |
511 | # Launch a program
512 | dp.add_handler(CommandHandler("launch", launch, pass_args=True))
513 |
514 | # Open a link with the default browser
515 | dp.add_handler(CommandHandler("link", link, pass_args=True))
516 |
517 | # Show a popup with the memo
518 | dp.add_handler(CommandHandler("memo", memo_thread, pass_args=True))
519 |
520 | # Check if a program is currently active
521 | dp.add_handler(CommandHandler("task", task, pass_args=True))
522 |
523 | # Kill the selected process
524 | dp.add_handler(CommandHandler("task_kill", task_kill))
525 |
526 | # Send a full screen screenshot
527 | dp.add_handler(CommandHandler("screen", screenshot))
528 |
529 | # Keyboard Button Reply
530 | dp.add_handler(MessageHandler(Filters.text |
531 | Filters.status_update, message_handler))
532 |
533 | # Log all errors
534 | dp.add_error_handler(error)
535 |
536 | # Start the Bot
537 | updater.start_polling()
538 |
539 | # Run the bot until you press Ctrl-C or the process receives SIGINT,
540 | # SIGTERM or SIGABRT. This should be used most of the time, since
541 | # start_polling() is non-blocking and will stop the bot gracefully.
542 | updater.idle()
543 |
544 |
545 | if __name__ == "__main__":
546 | main()
547 |
--------------------------------------------------------------------------------
/bot/bot_setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import argparse
5 | import os
6 | import pathlib
7 | import platform
8 | import subprocess
9 | import sys
10 | try:
11 | import tkinter as tk
12 | from tkinter import Entry, Label, Menu, LEFT
13 | except ImportError:
14 | if len(sys.argv) == 1:
15 | print("To use the UI mode you need to install tkinter. To use the command line mode type python bot/bot_setup.py -h\n")
16 | from typing import Optional, Any
17 |
18 | import db
19 | import utils
20 |
21 | if platform.system() == "Windows":
22 | import winreg
23 |
24 | if sys.version_info < (3, 6, 0):
25 | raise Exception("This bot works only with Python 3.6+")
26 |
27 |
28 | def startupinfo() -> Optional[int]:
29 | if db.console_get() == "hide":
30 | if platform.system() == "Windows":
31 | value = subprocess.STARTUPINFO()
32 | value.dwFlags |= subprocess.STARTF_USESHOWWINDOW
33 | else:
34 | value = None
35 | else:
36 | value = None
37 | return value
38 |
39 |
40 | def db_and_co() -> None:
41 | pathlib.Path(os.path.join(os.path.dirname(utils.current_path()), "data")).mkdir(parents=True, exist_ok=True)
42 | pathlib.Path(os.path.join(os.path.dirname(utils.current_path()), "tmp")).mkdir(parents=True, exist_ok=True)
43 | db.create()
44 |
45 |
46 | def tokens_check(btn: Any=None) -> None:
47 | if not db.token_get("BotFather_token"):
48 | if btn:
49 | btn.configure(text="Confirm")
50 | else:
51 | if btn:
52 | btn.configure(text="Change token")
53 |
54 |
55 | def botfather_token_set(token: str, entry: Any=None, btn: Any=None, lbl: Any=None) -> None:
56 | if token:
57 | db.token_set("BotFather_token", token)
58 | text = "Token saved!"
59 | if entry and btn and lbl:
60 | entry.destroy()
61 | btn.destroy()
62 | lbl.configure(text=text, font="Times 11", fg="green", justify=LEFT)
63 | else:
64 | print(text+"\n")
65 | else:
66 | text="Your entry is empty"
67 | if lbl:
68 | lbl.configure(text=text, font="Times 11", fg="red", justify=LEFT)
69 | else:
70 | print(text+"\n")
71 |
72 |
73 | def bot_start(root: Any=None) -> None:
74 | if root:
75 | root.withdraw()
76 | if startupinfo() is not None or platform.system() == "Windows":
77 | if db.startup_get() == "true":
78 | subprocess.run(f"{sys.executable} {os.path.join(utils.current_path(), 'bot.pyw')}", creationflags=0x08000000, shell=True)
79 | else:
80 | subprocess.run(f"{sys.executable} {os.path.join(utils.current_path(), 'bot.py')}", creationflags=0x08000000, shell=True)
81 | else:
82 | if db.startup_get() == "true":
83 | subprocess.run(f"{sys.executable} {os.path.join(utils.current_path(), 'bot.pyw')}", shell=True)
84 | else:
85 | subprocess.run(f"{sys.executable} {os.path.join(utils.current_path(), 'bot.py')}", shell=True)
86 |
87 |
88 | def privs_window(root: Any) -> None:
89 | privs = tk.Toplevel(root)
90 | privs.wm_title("Permissions")
91 | usr_l = Label(privs, text="Username", font="Times 11 bold", justify=LEFT)
92 | usr_l.pack()
93 | usr_e = Entry(privs, bd=5)
94 | usr_e.pack()
95 | add_b = tk.Button(privs, text="Add permissions",
96 | command=lambda: add_privs(usr_e.get(), usr_e, add_b, rm_b, usr_done))
97 | add_b.pack()
98 | rm_b = tk.Button(privs, text="Remove permissions",
99 | command=lambda: remove_privs(usr_e.get(), usr_e, add_b, rm_b, usr_done))
100 | rm_b.pack()
101 | usr_done = Label(privs, text="")
102 | usr_done.pack()
103 |
104 |
105 | def add_privs(user: str, usr_e: Any=None, add_b: Any=None, rm_b: Any=None, usr_done_l: Any=None) -> None:
106 | if db.user_exists(user):
107 | db.user_role(user, admin=True)
108 | text = f"Permissions for {user} changed"
109 | if usr_e and add_b and rm_b and usr_done_l:
110 | usr_e.destroy()
111 | add_b.destroy()
112 | rm_b.destroy()
113 | usr_done_l.configure(text=text, font="Times 11", fg="green", justify=LEFT)
114 | else:
115 | print(text)
116 | else:
117 | text = f"{user} isn't in your database"
118 | if usr_done_l:
119 | usr_done_l.configure(text=text, font="Times 11", fg="red", justify=LEFT)
120 | else:
121 | print(text)
122 |
123 |
124 | def remove_privs(user: str, usr_e: Any=None, add_b: Any=None, rm_b: Any=None, usr_done_l: Any=None) -> None:
125 | if db.user_exists(user):
126 | db.user_role(user, admin=False)
127 | text = f"Permissions for {user} changed"
128 | if usr_e and add_b and rm_b and usr_done_l:
129 | usr_e.destroy()
130 | add_b.destroy()
131 | rm_b.destroy()
132 | usr_done_l.configure(text=text, font="Times 11", fg="green", justify=LEFT)
133 | else:
134 | print(text)
135 | else:
136 | text = f"{user} isn't in your database"
137 | if usr_done_l:
138 | usr_done_l.configure(text=text, font="Times 11", fg="red", justify=LEFT)
139 | else:
140 | print(text)
141 |
142 |
143 | def restart_popup(root: Any) -> None:
144 | privs = tk.Toplevel(root)
145 | privs.wm_title("Restart")
146 | lp = Label(privs, text="Please restart bot_setup to apply the change",
147 | font="Times 11", justify=LEFT)
148 | lp.pack()
149 | add_b = tk.Button(privs, text="Restart", command=lambda: restart())
150 | add_b.pack()
151 |
152 | def restart() -> None:
153 | python = sys.executable
154 | os.execl(python, python, *sys.argv)
155 |
156 |
157 | def console_show(ui: bool=False, root: Any=None) -> None:
158 | db.console_set("show")
159 | if ui and root:
160 | restart_popup(root)
161 |
162 |
163 | def console_hide(ui: bool=False, root: Any=None) -> None:
164 | db.console_set("hide")
165 | if ui and root:
166 | restart_popup(root)
167 |
168 |
169 | def startup_enable(root: Any=None) -> None:
170 | if db.startup_get() == "false":
171 | py_path = os.path.join(utils.current_path(), "bot.py")
172 | pyw_path = os.path.join(utils.current_path(), "bot.pyw")
173 | if platform.system() == "Windows":
174 | if os.path.isfile(py_path) is True:
175 | os.rename(py_path, pyw_path)
176 | key = winreg.OpenKey(
177 | winreg.HKEY_CURRENT_USER,
178 | 'Software\Microsoft\Windows\CurrentVersion\Run', 0,
179 | winreg.KEY_SET_VALUE)
180 | winreg.SetValueEx(key, 'PC-Control', 0, winreg.REG_SZ, '"' + pyw_path +'"')
181 | key.Close()
182 | db.startup_set("true")
183 | else:
184 | if os.path.isfile(py_path) is True:
185 | os.rename(py_path, pyw_path)
186 | try:
187 | xdg_autostart_user_config_path = os.path.join(str(pathlib.Path.home()), ".config/autostart/")
188 | os.makedirs(xdg_autostart_user_config_path, exist_ok=True)
189 | text = "[Desktop Entry]\n"
190 | text += "Type=Application\n"
191 | text += f"Path={utils.current_path()}/\n"
192 | text += f"Exec={sys.executable} bot.pyw\n"
193 | text += "Name=PC-Control bot\n"
194 | text += "Comment=PC-Control bot startup\n"
195 | text += "\n"
196 | with open(os.path.join(xdg_autostart_user_config_path + "PC-Control.desktop"), 'x') as file:
197 | file.write(text)
198 | db.startup_set("true")
199 | except IOError as e:
200 | text = f"Error: {str(e)}"
201 | if root:
202 | error = tk.Toplevel(root)
203 | error.wm_title("Error")
204 | warn_l = Label(error, text=text, font="Times 11 bold", justify=LEFT)
205 | warn_l.pack()
206 | ok_b = tk.Button(error, text="Okay", command=lambda: error.destroy())
207 | ok_b.pack()
208 | else:
209 | print(text+"\n")
210 | else:
211 | text = "Already enabled"
212 | if root:
213 | error = tk.Toplevel(root)
214 | error.wm_title("Error")
215 | warn_l = Label(error, text=text, font="Times 11 bold", justify=LEFT)
216 | warn_l.pack()
217 | ok_b = tk.Button(error, text="Okay",
218 | command=lambda: error.destroy())
219 | ok_b.pack()
220 | else:
221 | print(text+"\n")
222 |
223 |
224 | def startup_disable(root: Any=None) -> None:
225 | if db.startup_get() == "true":
226 | py_path = os.path.join(utils.current_path(), "bot.py")
227 | pyw_path = os.path.join(utils.current_path(), "bot.pyw")
228 | if platform.system() == "Windows":
229 | key = winreg.OpenKey(
230 | winreg.HKEY_CURRENT_USER,
231 | 'Software\Microsoft\Windows\CurrentVersion\Run', 0,
232 | winreg.KEY_SET_VALUE)
233 | winreg.DeleteValue(key, 'PC-Control')
234 | key.Close()
235 | os.rename(pyw_path, py_path)
236 | db.startup_set("false")
237 | else:
238 | try:
239 | os.rename(pyw_path, py_path)
240 | os.remove(os.path.join(str(pathlib.Path.home()),".config/autostart/PC-Control.desktop"))
241 | db.startup_set("false")
242 | except OSError as e:
243 | os.rename(py_path, pyw_path)
244 | text = f"Error: {str(e)}"
245 | if root:
246 | error = tk.Toplevel(root)
247 | error.wm_title("Error")
248 | warn_l = Label(error, text=text, font="Times 11 bold", justify=LEFT)
249 | warn_l.pack()
250 | ok_b = tk.Button(error, text="Okay", command=lambda: error.destroy())
251 | ok_b.pack()
252 | else:
253 | print(text+"\n")
254 | else:
255 | text = "Already disabled"
256 | if root:
257 | error = tk.Toplevel(root)
258 | error.wm_title("Error")
259 | warn_l = Label(error, text=text, font="Times 11 bold", justify=LEFT)
260 | warn_l.pack()
261 | ok_b = tk.Button(error, text="Okay",
262 | command=lambda: error.destroy())
263 | ok_b.pack()
264 | else:
265 | print(text+"\n")
266 |
267 |
268 | def ui() -> None:
269 | root = tk.Tk()
270 | root.wm_title("Setup")
271 | root.geometry("200x200")
272 | root.resizable(width=False, height=False)
273 |
274 | bft_l = Label(root, text="BotFather token", font="Times 11 bold", justify=LEFT)
275 | bft_l.pack()
276 | bft_e = Entry(root, bd=5)
277 | bft_e.pack()
278 | bft_b = tk.Button(
279 | root, text="", command=lambda: botfather_token_set(bft_e.get(), bft_e, bft_b, bft_done_l))
280 | bft_b.pack(pady=5)
281 | bft_done_l = Label(root, text="")
282 | bft_done_l.pack()
283 |
284 | start_btn = tk.Button(root, text="Start it!", command=lambda: bot_start(root))
285 | start_btn.pack(pady=5)
286 |
287 | privs_btn = tk.Button(root, text="Change user permissions", command= lambda: privs_window(root))
288 | privs_btn.pack(pady=5)
289 |
290 | db_and_co()
291 | menubar = Menu(root)
292 | root.config(menu=menubar)
293 | filemenu = Menu(menubar, tearoff=0)
294 | menubar.add_cascade(label="Options", menu=filemenu)
295 |
296 | console_menu = Menu(root, tearoff=0)
297 | console_menu.add_command(label="Show", command=lambda: console_show(True, root))
298 | console_menu.add_command(label="Hide", command=lambda: console_hide(True, root))
299 | filemenu.add_cascade(label="Console", menu=console_menu, underline=0)
300 |
301 | startup_menu = Menu(root, tearoff=0)
302 | startup_menu.add_command(label="Enable", command=lambda: startup_enable(root))
303 | startup_menu.add_command(label="Disable", command=lambda: startup_disable(root))
304 | filemenu.add_cascade(label="Startup", menu=startup_menu, underline=0)
305 |
306 | db_and_co()
307 | tokens_check(bft_b)
308 | root.mainloop()
309 |
310 |
311 | def parse_args() -> bool:
312 | parser = argparse.ArgumentParser(description="PC-Control setup utility", epilog="If called without options it will launch in UI mode")
313 | group = parser.add_mutually_exclusive_group()
314 | group.add_argument("--token", help="BotFather token", metavar="BotFather_token")
315 | group.add_argument("--admin_add", help="add user to admin group", metavar="username")
316 | group.add_argument("--admin_remove", help="remove user from admin group", metavar="username")
317 | group.add_argument("--output_show", help="show command line output", action="store_true")
318 | group.add_argument("--output_hide", help="hide command line output", action="store_true")
319 | group.add_argument("--startup_enable", help="enable startup (run auomatically at startup)", action="store_true")
320 | group.add_argument("--startup_disable", help="disable startup", action="store_true")
321 | group.add_argument("--start", help="start the bot", action="store_true")
322 |
323 | args = parser.parse_args()
324 |
325 | if args.token:
326 | botfather_token_set(args.token)
327 | elif args.start:
328 | bot_start()
329 | elif args.admin_add:
330 | add_privs(args.admin_add)
331 | elif args.admin_remove:
332 | remove_privs(args.admin_remove)
333 | elif args.output_show:
334 | console_show()
335 | elif args.output_hide:
336 | console_hide()
337 | elif args.startup_enable:
338 | startup_enable()
339 | elif args.startup_disable:
340 | startup_disable()
341 | else:
342 | return False
343 | return True
344 |
345 |
346 | def main() -> None:
347 | db_and_co()
348 | if parse_args() == False:
349 | ui()
350 |
351 |
352 | if __name__ == "__main__":
353 | main()
354 |
--------------------------------------------------------------------------------
/bot/db.py:
--------------------------------------------------------------------------------
1 | import array
2 | import platform
3 | from datetime import datetime
4 | from functools import wraps
5 | from pathlib import Path
6 | import os
7 | from typing import Callable, Optional
8 |
9 | import pytz
10 | from telegram import ParseMode, Update, User, Bot
11 | from telegram.ext import CallbackContext
12 | from telegram.utils import helpers
13 | from tzlocal import get_localzone
14 |
15 | import utils
16 |
17 | from sqlalchemy.ext.declarative import declarative_base
18 | from sqlalchemy.orm import sessionmaker
19 | from sqlalchemy import create_engine, Column, Integer, Text
20 |
21 |
22 | def database() -> str:
23 | return os.path.join(os.path.dirname(utils.current_path()), "data/pccontrol.sqlite")
24 |
25 |
26 | engine = create_engine("sqlite:///" + database(), echo=False)
27 | Base = declarative_base()
28 |
29 | Base.metadata.bind = engine
30 | DBsession = sessionmaker(bind=engine)
31 |
32 |
33 | class Config(Base):
34 | __tablename__ = "config"
35 | id = Column(Integer, primary_key=True, unique=True)
36 | name = Column(Text, nullable=False, unique=True)
37 | value = Column(Text, nullable=False)
38 |
39 |
40 | class Users(Base):
41 | __tablename__ = "users"
42 | id = Column(Integer, primary_key=True, unique=True)
43 | name_first = Column(Text)
44 | name_last = Column(Text)
45 | username = Column(Text)
46 | privs = Column(Text)
47 | last_use = Column(Text)
48 | time_used = Column(Integer)
49 |
50 |
51 | def exists() -> bool:
52 | return Path(database()).exists()
53 |
54 |
55 | def create() -> None:
56 | if exists() is False:
57 | Base.metadata.create_all(engine)
58 | console_set("hide")
59 | startup_set("false")
60 |
61 |
62 | def get_admins_id() -> array.array:
63 | admins_arr = array.array('i')
64 | session = DBsession()
65 | admins = session.query(Users).filter(Users.privs == "-2").all()
66 | for admin in admins:
67 | admins_arr.append(admin.id)
68 | session.commit()
69 | return admins_arr
70 |
71 |
72 | def update_user(from_user: User, bot: Bot) -> None: # Update the user list (db)
73 | session = DBsession()
74 | user = session.query(Users).filter(Users.id == from_user.id).one_or_none()
75 | used = 0
76 | if user:
77 | if user.time_used:
78 | used = user.time_used
79 | if user:
80 | user.name_first = from_user.first_name
81 | user.name_last = from_user.last_name
82 | user.username = from_user.username
83 | user.last_use = datetime.now(pytz.timezone(str(get_localzone()))).strftime('%Y-%m-%d %H:%M')
84 | user.time_used = used + 1
85 | else:
86 | if session.query(Users).count() == 0:
87 | new_user = Users(name_first=from_user.first_name,
88 | name_last=from_user.last_name,
89 | username=from_user.username,
90 | last_use=datetime.now(pytz.timezone(str(get_localzone()))).strftime('%Y-%m-%d %H:%M'),
91 | time_used=used + 1,
92 | id=from_user.id,
93 | privs="-2")
94 | else:
95 | new_user = Users(name_first=from_user.first_name,
96 | name_last=from_user.last_name,
97 | username=from_user.username,
98 | last_use=datetime.now(pytz.timezone(str(get_localzone()))).strftime('%Y-%m-%d %H:%M'),
99 | time_used=used + 1,
100 | id=from_user.id)
101 | session.add(new_user)
102 | admins = get_admins_id()
103 | for admin_id in admins:
104 | if admin_id != from_user.id:
105 | text = "*New user registered into the database* \n\n"
106 | text += f"Name: {helpers.escape_markdown(from_user.first_name, 2)}"
107 | if from_user.last_name:
108 | text += f"\nLast name: {helpers.escape_markdown(from_user.last_name, 2)}"
109 | if from_user.username:
110 | text += f"\nUsername: @{helpers.escape_markdown(from_user.username, 2)}"
111 | bot.sendMessage(
112 | chat_id=admin_id, text=text, parse_mode=ParseMode.MARKDOWN_V2)
113 | session.commit()
114 |
115 |
116 | def admin_check(func: Callable) -> Callable[[Update, CallbackContext], Optional[Callable]]:
117 | @wraps(func)
118 | def is_admin(update: Update, context: CallbackContext) -> Optional[Callable]:
119 | session = DBsession()
120 | privs = session.query(Users).filter(Users.id == update.message.from_user.id).one_or_none().privs
121 | if privs == "-2":
122 | return func(update, context)
123 | else:
124 | update.effective_message.reply_text("Unauthorized")
125 | return is_admin
126 |
127 |
128 | def token_set(token_type: str, token_value: str) -> None:
129 | session = DBsession()
130 | if token_get(token_type):
131 | session.query(Config).filter(Config.name == token_type).one().value = token_value
132 | else:
133 | token = Config(name=token_type, value=token_value)
134 | session.add(token)
135 | session.commit()
136 |
137 |
138 | def token_get(token_type: str) -> Optional[str]:
139 | session = DBsession()
140 | entry = session.query(Config).filter(Config.name == token_type).one_or_none()
141 | if entry:
142 | return entry.value
143 |
144 |
145 | def user_exists(user: Optional[str] = None) -> bool:
146 | session = DBsession()
147 | if session.query(Users).filter(Users.username == user).one_or_none():
148 | return True
149 | else:
150 | return False
151 |
152 |
153 | def user_role(user: str, admin: bool) -> None:
154 | session = DBsession()
155 | db_user = session.query(Users).filter(Users.username == user).one()
156 | if admin:
157 | db_user.privs = "-2"
158 | else:
159 | db_user.privs = ""
160 | session.commit()
161 |
162 |
163 | def console_get() -> Optional[str]:
164 | session = DBsession()
165 | entry = session.query(Config).filter(Config.name == "console").one_or_none()
166 | if entry:
167 | return entry.value
168 |
169 |
170 | def console_set(value: str) -> None:
171 | session = DBsession()
172 | if console_get():
173 | session.query(Config).filter(Config.name == "console").one().value = value
174 | else:
175 | console_value = Config(name="console", value=value)
176 | session.add(console_value)
177 | session.commit()
178 |
179 |
180 | def startup_set(value: str) -> None:
181 | session = DBsession()
182 | if startup_get():
183 | session.query(Config).filter(Config.name == "startup").one().value = value
184 | else:
185 | startup_value = Config(name="startup", value=value)
186 | session.add(startup_value)
187 | session.commit()
188 |
189 |
190 | def startup_get() -> Optional[str]:
191 | session = DBsession()
192 | entry = session.query(Config).filter(Config.name == "startup").one_or_none()
193 | if entry:
194 | return entry.value
195 |
--------------------------------------------------------------------------------
/bot/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import threading
3 | from typing import Callable, Optional, Any
4 |
5 |
6 | class ThreadTimer:
7 | thread = None
8 |
9 | def start(self, fun: Callable, time: int, *args: Any) -> Optional[str]:
10 | if self.thread and self.thread.is_alive():
11 | return self.thread.name
12 | ThreadTimer.thread = threading.Timer(time*60, fun, [*args])
13 | ThreadTimer.thread.name = f"{fun.__name__}_t"
14 | ThreadTimer.thread.start()
15 |
16 | def stop(self) -> Optional[str]:
17 | if self.thread and self.thread.is_alive():
18 | self.thread.cancel()
19 | return self.thread.name
20 |
21 |
22 | def current_path() -> str:
23 | return os.path.abspath(os.path.dirname(__file__))
24 |
25 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | distro==1.6.0
2 | Pillow==10.3.0
3 | psutil==5.9.0
4 | pyscreenshot==3.0
5 | python-telegram-bot==13.10
6 | pytz==2021.3
7 | SQLAlchemy==1.4.31
8 | tzlocal==4.1
9 |
--------------------------------------------------------------------------------