Screencloud support (#287)

* Working on screencloud support

* Working screencloud implementation
This commit is contained in:
Sergio Brighenti 2020-10-20 13:34:57 +02:00 committed by GitHub
parent 1012984661
commit 8ce337233c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 309 additions and 10 deletions

View file

@ -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,
]
);
}

View file

@ -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);

View file

@ -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',

View file

@ -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>

View 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()

View 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>

View 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>