Screencloud support (#287)
* Working on screencloud support * Working screencloud implementation
This commit is contained in:
parent
1012984661
commit
8ce337233c
7 changed files with 309 additions and 10 deletions
|
@ -5,6 +5,9 @@ namespace App\Controllers;
|
|||
use App\Database\Queries\UserQuery;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
|
||||
class ClientController extends Controller
|
||||
{
|
||||
|
@ -27,22 +30,60 @@ class ClientController extends Controller
|
|||
|
||||
$json = [
|
||||
'DestinationType' => 'ImageUploader, TextUploader, FileUploader',
|
||||
'RequestURL' => route('upload'),
|
||||
'FileFormName' => 'upload',
|
||||
'Arguments' => [
|
||||
'file' => '$filename$',
|
||||
'text' => '$input$',
|
||||
'RequestURL' => route('upload'),
|
||||
'FileFormName' => 'upload',
|
||||
'Arguments' => [
|
||||
'file' => '$filename$',
|
||||
'text' => '$input$',
|
||||
'token' => $user->token,
|
||||
],
|
||||
'URL' => '$json:url$',
|
||||
'URL' => '$json:url$',
|
||||
'ThumbnailURL' => '$json:url$/raw',
|
||||
'DeletionURL' => '$json:url$/delete/'.$user->token,
|
||||
'DeletionURL' => '$json:url$/delete/'.$user->token,
|
||||
];
|
||||
|
||||
return json($response, $json, 200, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
|
||||
->withHeader('Content-Disposition', 'attachment;filename="'.$user->username.'-ShareX.sxcu"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param string|null $token
|
||||
* @return Response
|
||||
* @throws \ZipStream\Exception\FileNotFoundException
|
||||
* @throws \ZipStream\Exception\FileNotReadableException
|
||||
* @throws \ZipStream\Exception\OverflowException
|
||||
* @throws HttpNotFoundException
|
||||
*/
|
||||
public function getScreenCloudConfig(Request $request, string $token): Response
|
||||
{
|
||||
$user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $token)->fetch();
|
||||
if (!$user) {
|
||||
throw new HttpNotFoundException($request);
|
||||
}
|
||||
|
||||
$config = [
|
||||
'token' => $token,
|
||||
'host' => route('root'),
|
||||
];
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
$options = new Archive();
|
||||
$options->setSendHttpHeaders(true);
|
||||
|
||||
$zip = new ZipStream($user->username.'-screencloud.zip', $options);
|
||||
|
||||
$zip->addFileFromPath('main.py', BASE_DIR.'resources/uploaders/screencloud/main.py');
|
||||
$zip->addFileFromPath('icon.png', BASE_DIR.'static/images/favicon-32x32.png');
|
||||
$zip->addFileFromPath('metadata.xml', BASE_DIR.'resources/uploaders/screencloud/metadata.xml');
|
||||
$zip->addFileFromPath('settings.ui', BASE_DIR.'resources/uploaders/screencloud/settings.ui');
|
||||
$zip->addFile('config.json', json_encode($config, JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$zip->finish();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
|
@ -67,9 +108,9 @@ class ClientController extends Controller
|
|||
$response->withHeader('Content-Disposition', 'attachment;filename="xbackbone_uploader_'.$user->username.'.sh"'),
|
||||
'scripts/xbackbone_uploader.sh.twig',
|
||||
[
|
||||
'username' => $user->username,
|
||||
'username' => $user->username,
|
||||
'upload_url' => route('upload'),
|
||||
'token' => $user->token,
|
||||
'token' => $user->token,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ $app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setNa
|
|||
|
||||
$app->post('/upload', [UploadController::class, 'uploadEndpoint'])->setName('upload');
|
||||
|
||||
$app->get('/user/{token}/config/screencloud', [ClientController::class, 'getScreenCloudConfig'])->setName('config.screencloud')->add(CheckForMaintenanceMiddleware::class);
|
||||
$app->get('/{userCode}/{mediaCode}', [MediaController::class, 'show'])->setName('public');
|
||||
$app->get('/{userCode}/{mediaCode}/delete/{token}', [MediaController::class, 'show'])->setName('public.delete.show')->add(CheckForMaintenanceMiddleware::class);
|
||||
$app->post('/{userCode}/{mediaCode}/delete/{token}', [MediaController::class, 'deleteByToken'])->setName('public.delete')->add(CheckForMaintenanceMiddleware::class);
|
||||
|
|
|
@ -48,6 +48,7 @@ return [
|
|||
'your_profile' => 'Your Profile',
|
||||
'token' => 'Token',
|
||||
'copy' => 'Copy',
|
||||
'copied' => 'Copied to clipboard!',
|
||||
'update' => 'Update',
|
||||
'edit' => 'Edit',
|
||||
'client_config' => 'Client Configuration',
|
||||
|
|
|
@ -64,7 +64,8 @@
|
|||
<label class="col-sm-3 col-form-label">{{ lang('client_config') }}</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="btn-group">
|
||||
<a href="{{ route('config.sharex', {'id': user.id}) }}" class="btn btn-lg btn-outline-dark"><i class="fas fa-fw fa-download"></i> ShareX Config</a>
|
||||
<a href="{{ route('config.sharex', {'id': user.id}) }}" class="btn btn-lg btn-outline-dark"><i class="fas fa-fw fa-download"></i> ShareX</a>
|
||||
<a href="javascript:alert('{{ lang('copied') }}')" data-clipboard-text="{{ route('config.screencloud', {'token': user.token}) }}" class="btn btn-lg btn-outline-info btn-clipboard"><i class="fas fa-fw fa-download"></i> Screencloud</a>
|
||||
<a href="{{ route('config.script', {'id': user.id}) }}" class="btn btn-lg btn-outline-danger"><i class="fas fa-fw fa-download"></i> Linux Script</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
156
resources/uploaders/screencloud/main.py
Normal file
156
resources/uploaders/screencloud/main.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
import ScreenCloud
|
||||
import json
|
||||
import traceback
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import io
|
||||
import mimetypes
|
||||
import uuid
|
||||
|
||||
from PythonQt.QtCore import QByteArray, QBuffer, QIODevice, QFile
|
||||
from PythonQt.QtGui import QWidget, QDialog
|
||||
from PythonQt.QtUiTools import QUiLoader
|
||||
|
||||
|
||||
class XBackBoneUploader:
|
||||
|
||||
def __init__(self):
|
||||
self.uil = QUiLoader()
|
||||
self.config_path = workingDir + '/config.json'
|
||||
self.loadSettings()
|
||||
|
||||
def showSettingsUI(self, parentWidget):
|
||||
self.parentWidget = parentWidget
|
||||
self.settingsDialog = self.uil.load(QFile(workingDir + '/settings.ui'), parentWidget)
|
||||
|
||||
self.settingsDialog.connect('accepted()', self.saveSettings)
|
||||
self.loadSettings()
|
||||
|
||||
self.settingsDialog.group_url.token.text = self.token
|
||||
self.settingsDialog.group_url.host.text = self.host
|
||||
|
||||
self.settingsDialog.open()
|
||||
|
||||
def loadSettings(self):
|
||||
with open(self.config_path, 'r') as config:
|
||||
settings = json.load(config)
|
||||
|
||||
self.token = settings.get('token')
|
||||
self.host = settings.get('host')
|
||||
|
||||
def saveSettings(self):
|
||||
data = {
|
||||
'token': self.settingsDialog.group_url.token.text,
|
||||
'host': self.settingsDialog.group_url.host.text
|
||||
}
|
||||
with open(self.config_path, 'w') as config:
|
||||
json.dump(data, config)
|
||||
|
||||
def isConfigured(self):
|
||||
self.loadSettings()
|
||||
return not (not self.token or not self.host)
|
||||
|
||||
def getFilename(self):
|
||||
return ScreenCloud.formatFilename('screenshot_%Y-%m-%d_%H-%M-%S')
|
||||
|
||||
def upload(self, screenshot, name):
|
||||
self.loadSettings()
|
||||
|
||||
q_ba = QByteArray()
|
||||
q_buff = QBuffer(q_ba)
|
||||
|
||||
q_buff.open(QIODevice.WriteOnly)
|
||||
screenshot.save(q_buff, ScreenCloud.getScreenshotFormat())
|
||||
q_buff.close()
|
||||
|
||||
url = (self.host + '/upload').replace('//upload', '/upload')
|
||||
|
||||
form = MultiPartForm()
|
||||
form.add_field('token', self.token)
|
||||
form.add_file('file', self.getFilename(), q_ba.data())
|
||||
data = bytes(form)
|
||||
|
||||
r = urllib.request.Request(url, data, headers={
|
||||
'Content-Type': form.get_content_type(),
|
||||
'Content-Length': len(data),
|
||||
'User-Agent': 'XBackBone/Screencloud-client'
|
||||
})
|
||||
|
||||
try:
|
||||
res = urllib.request.urlopen(r)
|
||||
response = json.loads(res.read())
|
||||
url = response.get('url')
|
||||
|
||||
if not url:
|
||||
raise Exception(response.get('message'))
|
||||
|
||||
ScreenCloud.setUrl(url)
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
response = json.loads(e.read())
|
||||
ScreenCloud.setError('Error while connecting to: ' + self.host + '\n' + response.get('message'))
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
ScreenCloud.setError('Could not upload to: ' + self.host + '\nError: ' + str(e))
|
||||
except AttributeError:
|
||||
ScreenCloud.setError('Unexpected error while uploading:\n' + traceback.format_exc())
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
class MultiPartForm:
|
||||
def __init__(self):
|
||||
self.form_fields = []
|
||||
self.files = []
|
||||
self.boundary = uuid.uuid4().hex.encode('utf-8')
|
||||
return
|
||||
|
||||
def get_content_type(self):
|
||||
return 'multipart/form-data; boundary={}'.format(self.boundary.decode('utf-8'))
|
||||
|
||||
def add_field(self, name, value):
|
||||
self.form_fields.append((name, value))
|
||||
|
||||
def add_file(self, fieldname, filename, body, mimetype=None):
|
||||
if mimetype is None:
|
||||
mimetype = (mimetypes.guess_type(filename)[0] or 'application/octet-stream')
|
||||
self.files.append((fieldname, filename, mimetype, body))
|
||||
|
||||
@staticmethod
|
||||
def _form_data(name):
|
||||
return ('Content-Disposition: form-data; name="{}"\r\n').format(name).encode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def _attached_file(name, filename):
|
||||
return ('Content-Disposition: file; name="{}"; filename="{}"\r\n').format(name, filename).encode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def _content_type(ct):
|
||||
return 'Content-Type: {}\r\n'.format(ct).encode('utf-8')
|
||||
|
||||
def __bytes__(self):
|
||||
buffer = io.BytesIO()
|
||||
boundary = b'--' + self.boundary + b'\r\n'
|
||||
|
||||
# Add the form fields
|
||||
for name, value in self.form_fields:
|
||||
buffer.write(boundary)
|
||||
buffer.write(self._form_data(name))
|
||||
buffer.write(b'\r\n')
|
||||
buffer.write(value.encode('utf-8'))
|
||||
buffer.write(b'\r\n')
|
||||
|
||||
# Add the files to upload
|
||||
for f_name, filename, f_content_type, body in self.files:
|
||||
buffer.write(boundary)
|
||||
buffer.write(self._attached_file(f_name, filename))
|
||||
buffer.write(self._content_type(f_content_type))
|
||||
buffer.write(b'\r\n')
|
||||
buffer.write(body)
|
||||
buffer.write(b'\r\n')
|
||||
|
||||
buffer.write(b'--' + self.boundary + b'--\r\n')
|
||||
return buffer.getvalue()
|
7
resources/uploaders/screencloud/metadata.xml
Normal file
7
resources/uploaders/screencloud/metadata.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<metadata>
|
||||
<name>XBackBone Uploader 1.0</name>
|
||||
<shortname>xbackbone</shortname>
|
||||
<className>XBackBoneUploader</className>
|
||||
<icon>icon.png</icon>
|
||||
<version>1.0</version>
|
||||
</metadata>
|
92
resources/uploaders/screencloud/settings.ui
Normal file
92
resources/uploaders/screencloud/settings.ui
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>125</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Upload Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="group_url">
|
||||
<property name="title">
|
||||
<string>XBackBone Instance</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_token">
|
||||
<property name="text">
|
||||
<string>Token:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="token">
|
||||
<property name="text">
|
||||
<string />
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_host">
|
||||
<property name="text">
|
||||
<string>Host:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="host">
|
||||
<property name="placeholderText">
|
||||
<string>http://example.com</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>toekn</tabstop>
|
||||
<tabstop>host</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources />
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints />
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints />
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
Loading…
Reference in a new issue