mirror of
https://github.com/rikkaneko/paste.git
synced 2025-08-06 14:10:13 +01:00
Add support to generate QR code for paste link
This commit is contained in:
parent
f7273ceb79
commit
fb3f8d1ef1
5 changed files with 187 additions and 123 deletions
23
README.md
23
README.md
|
@ -1,4 +1,5 @@
|
||||||
# Paste
|
# Paste
|
||||||
|
|
||||||
This is a pastebin-like, simple file sharing application targeted to run on Cloudflare Worker.
|
This is a pastebin-like, simple file sharing application targeted to run on Cloudflare Worker.
|
||||||
[pb.nekoul.com](http://pb.nekoul.com) is the current deployment of this project.
|
[pb.nekoul.com](http://pb.nekoul.com) is the current deployment of this project.
|
||||||
The maximum upload file size is limited to **10 MB** and the paste will be kept for **28 days** only by default.
|
The maximum upload file size is limited to **10 MB** and the paste will be kept for **28 days** only by default.
|
||||||
|
@ -6,6 +7,7 @@ The maximum upload file size is limited to **10 MB** and the paste will be kept
|
||||||
Please **DO NOT** abuse this service.
|
Please **DO NOT** abuse this service.
|
||||||
|
|
||||||
## Supported features
|
## Supported features
|
||||||
|
|
||||||
- [x] Upload paste
|
- [x] Upload paste
|
||||||
- [x] Download paste
|
- [x] Download paste
|
||||||
- [x] Delete paste
|
- [x] Delete paste
|
||||||
|
@ -15,8 +17,10 @@ Please **DO NOT** abuse this service.
|
||||||
- [x] View paste in browsers (only for text and media file)
|
- [x] View paste in browsers (only for text and media file)
|
||||||
- [ ] Expiring paste (*not support directly, see [this section](#expiring-paste)*)
|
- [ ] Expiring paste (*not support directly, see [this section](#expiring-paste)*)
|
||||||
- [ ] Render paste code with syntax highlighting
|
- [ ] Render paste code with syntax highlighting
|
||||||
|
- [x] Generate QR code for paste link
|
||||||
|
|
||||||
## Service architecture
|
## Service architecture
|
||||||
|
|
||||||
This project is designed to use a S3-compatible object storage (via [aws4fetch](https://github.com/mhart/aws4fetch)) as the backend storage
|
This project is designed to use a S3-compatible object storage (via [aws4fetch](https://github.com/mhart/aws4fetch)) as the backend storage
|
||||||
and [Cloudflare Worker KV](https://developers.cloudflare.com/workers/runtime-apis/kv) as index.
|
and [Cloudflare Worker KV](https://developers.cloudflare.com/workers/runtime-apis/kv) as index.
|
||||||
All requests are handled by [Cloudflare Worker](https://developers.cloudflare.com/workers) with the entry point `fetch()`.
|
All requests are handled by [Cloudflare Worker](https://developers.cloudflare.com/workers) with the entry point `fetch()`.
|
||||||
|
@ -24,6 +28,7 @@ It is worth noting that Cloudflare Worker is run *before* the cache. Therefore,
|
||||||
[Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) is used instead to interact with Cloudflare cache.
|
[Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) is used instead to interact with Cloudflare cache.
|
||||||
|
|
||||||
## Environment variable
|
## Environment variable
|
||||||
|
|
||||||
|Name|Description|
|
|Name|Description|
|
||||||
|-|-|
|
|-|-|
|
||||||
|`SERVICE_URL`|The URL of the service|
|
|`SERVICE_URL`|The URL of the service|
|
||||||
|
@ -38,26 +43,36 @@ It is worth noting that Cloudflare Worker is run *before* the cache. Therefore,
|
||||||
**`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `ENDPOINT` should be kept secret, i.e., [put into the encrypted store](https://developers.cloudflare.com/workers/platform/environment-variables/#adding-secrets-via-wrangler).**
|
**`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `ENDPOINT` should be kept secret, i.e., [put into the encrypted store](https://developers.cloudflare.com/workers/platform/environment-variables/#adding-secrets-via-wrangler).**
|
||||||
|
|
||||||
## API Specification
|
## API Specification
|
||||||
|
|
||||||
### GET /
|
### GET /
|
||||||
|
|
||||||
Fetch the Web frontpage HTML for uploading text/file (used for browsers)
|
Fetch the Web frontpage HTML for uploading text/file (used for browsers)
|
||||||
|
|
||||||
### GET /api
|
### GET /api
|
||||||
|
|
||||||
Fetch API specification
|
Fetch API specification
|
||||||
|
|
||||||
### GET /\<uuid\>
|
### GET /\<uuid\>
|
||||||
Fetch the paste by uuid. *If the password is set, this request requires additional `x-pass` header or to use [HTTP Basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).*
|
|
||||||
|
Fetch the paste by uuid. *If the password is set, this request requires additional `x-pass` header or to
|
||||||
|
use [HTTP Basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).*
|
||||||
|
|
||||||
### POST /
|
### POST /
|
||||||
|
|
||||||
Create new paste. Currently, only `multipart/form-data` and raw request are supported.
|
Create new paste. Currently, only `multipart/form-data` and raw request are supported.
|
||||||
|
Add `?qr=1` to enable QR code generation for paste link.
|
||||||
|
|
||||||
#### For `multipart/form-data` request,
|
#### For `multipart/form-data` request,
|
||||||
|
|
||||||
|Form Key|Description|
|
|Form Key|Description|
|
||||||
|-|-|
|
|-|-|
|
||||||
|`u`|Upload content|
|
|`u`|Upload content|
|
||||||
|`pass`|Paste's password|
|
|`pass`|Paste's password|
|
||||||
|`read-limit`|The maximum access count|
|
|`read-limit`|The maximum access count|
|
||||||
|
|`qrcode`|Toggle QR code generation|
|
||||||
|
|
||||||
#### For raw request,
|
#### For raw request,
|
||||||
|
|
||||||
|Header Key|Description|
|
|Header Key|Description|
|
||||||
|-|-|
|
|-|-|
|
||||||
|`content-type`|The media type (MIME) of the data and encoding|
|
|`content-type`|The media type (MIME) of the data and encoding|
|
||||||
|
@ -68,7 +83,9 @@ Create new paste. Currently, only `multipart/form-data` and raw request are supp
|
||||||
The request body contains the upload content.
|
The request body contains the upload content.
|
||||||
|
|
||||||
### GET /\<uuid\>/\<option\> (Not implemented)
|
### GET /\<uuid\>/\<option\> (Not implemented)
|
||||||
|
|
||||||
Fetch the paste (code) in rendered HTML with syntax highlighting
|
Fetch the paste (code) in rendered HTML with syntax highlighting
|
||||||
|
Add `?qr=1` to enable QR code generation for paste link.
|
||||||
Currently, only the following options is supported for `option`
|
Currently, only the following options is supported for `option`
|
||||||
|Option|Meaning|
|
|Option|Meaning|
|
||||||
|-|-|
|
|-|-|
|
||||||
|
@ -79,17 +96,21 @@ Currently, only the following options is supported for `option`
|
||||||
*The authentication requirement is as same as `GET /<uuid>`.*
|
*The authentication requirement is as same as `GET /<uuid>`.*
|
||||||
|
|
||||||
### DELETE /\<uuid\>
|
### DELETE /\<uuid\>
|
||||||
|
|
||||||
Delete paste by uuid. *If the password is set, this request requires additional `x-pass` header*
|
Delete paste by uuid. *If the password is set, this request requires additional `x-pass` header*
|
||||||
|
|
||||||
### POST /\<uuid\>/settings (Not implemented)
|
### POST /\<uuid\>/settings (Not implemented)
|
||||||
|
|
||||||
Update paste setting. *If the password is set, this request requires additional `x-pass` header*
|
Update paste setting. *If the password is set, this request requires additional `x-pass` header*
|
||||||
|
|
||||||
## Expiring paste
|
## Expiring paste
|
||||||
|
|
||||||
S3 object lifecycle rules and Cloudflare KV's expiring key can be used to implemented expiring paste.
|
S3 object lifecycle rules and Cloudflare KV's expiring key can be used to implemented expiring paste.
|
||||||
Reference for Amazon S3 can be found in [here](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html)
|
Reference for Amazon S3 can be found in [here](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html)
|
||||||
, and Blackblaze B2 in [here](https://www.backblaze.com/b2/docs/lifecycle_rules.html).
|
, and Blackblaze B2 in [here](https://www.backblaze.com/b2/docs/lifecycle_rules.html).
|
||||||
|
|
||||||
## Remark
|
## Remark
|
||||||
|
|
||||||
You are welcome to use my project and depoly your own service.
|
You are welcome to use my project and depoly your own service.
|
||||||
Due to the fact that the `SERVICE_URL` is hard-coded into the `paste.html`,
|
Due to the fact that the `SERVICE_URL` is hard-coded into the `paste.html`,
|
||||||
you may simply use `Ctrl`+`R` to replace `pb.nekoul.com` with your own service URL.
|
you may simply use `Ctrl`+`R` to replace `pb.nekoul.com` with your own service URL.
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws4fetch": "^1.0.13",
|
"aws4fetch": "^1.0.13",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
"js-sha256": "^0.9.0"
|
"js-sha256": "^0.9.0",
|
||||||
|
"dedent-js": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "^3.11.0",
|
"@cloudflare/workers-types": "^3.11.0",
|
||||||
|
|
49
paste.html
49
paste.html
|
@ -28,7 +28,8 @@
|
||||||
<p>
|
<p>
|
||||||
<a href="https://pb.nekoul.com">pb.nekoul.com</a> is a pastebin-like service hosted on Cloudflare Worker.<br>
|
<a href="https://pb.nekoul.com">pb.nekoul.com</a> is a pastebin-like service hosted on Cloudflare Worker.<br>
|
||||||
This service is primarily designed for own usage and interest only.<br>
|
This service is primarily designed for own usage and interest only.<br>
|
||||||
All data may be deleted or expired without any notification and guarantee. Please <b>DO NOT</b> abuse this service.<br>
|
All data may be deleted or expired without any notification and guarantee. Please <b>DO NOT</b> abuse this
|
||||||
|
service.<br>
|
||||||
The limit for file upload is <b>10 MB</b> and the paste will be kept for <b>28 days</b> only by default.<br>
|
The limit for file upload is <b>10 MB</b> and the paste will be kept for <b>28 days</b> only by default.<br>
|
||||||
The source code is available in my GitHub repository <a href="https://github.com/rikkaneko/paste">[here]</a>.<br>
|
The source code is available in my GitHub repository <a href="https://github.com/rikkaneko/paste">[here]</a>.<br>
|
||||||
This webpage is designed for upload files only.
|
This webpage is designed for upload files only.
|
||||||
|
@ -58,6 +59,10 @@
|
||||||
<label for="read_limit_input">Read limit: </label>
|
<label for="read_limit_input">Read limit: </label>
|
||||||
<input id="read_limit_input" type="number" name="read-limit" min="1" style="width: 3em">
|
<input id="read_limit_input" type="number" name="read-limit" min="1" style="width: 3em">
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="show_qr_checkbox" type="checkbox" name="qrcode">
|
||||||
|
<label for="show_qr_checkbox">Show QR code on sumbitted</label>
|
||||||
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div>
|
<div>
|
||||||
<input id="reset_button" type="reset" value="Reset">
|
<input id="reset_button" type="reset" value="Reset">
|
||||||
|
@ -68,48 +73,48 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function update_textarea() {
|
function update_textarea() {
|
||||||
this.style.height = "auto"
|
this.style.height = 'auto';
|
||||||
this.style.height = this.scrollHeight + "px";
|
this.style.height = this.scrollHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_file_size() {
|
function update_file_size() {
|
||||||
let bytes = this.files[0]?.size ?? 0;
|
let bytes = this.files[0]?.size ?? 0;
|
||||||
let size = bytes + " bytes";
|
let size = bytes + ' bytes';
|
||||||
const units = ["KiB", "MiB", "GiB", "TiB"];
|
const units = ['KiB', 'MiB', 'GiB', 'TiB'];
|
||||||
for (let i = 0, approx = bytes / 1024; approx > 1; approx /= 1024, i++) {
|
for (let i = 0, approx = bytes / 1024; approx > 1; approx /= 1024, i++) {
|
||||||
size = approx.toFixed(3) + " " + units[i];
|
size = approx.toFixed(3) + ' ' + units[i];
|
||||||
}
|
}
|
||||||
document.getElementById("file_size").innerHTML = size;
|
document.getElementById('file_size').innerHTML = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle_password() {
|
function toggle_password() {
|
||||||
let input_field = document.getElementById("pass_input");
|
let input_field = document.getElementById('pass_input');
|
||||||
if (this.checked) {
|
if (this.checked) {
|
||||||
input_field.type = "text";
|
input_field.type = 'text';
|
||||||
} else {
|
} else {
|
||||||
input_field.type = "password";
|
input_field.type = 'password';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset_form() {
|
function reset_form() {
|
||||||
// Re-enable all input elements
|
// Re-enable all input elements
|
||||||
let elements = document.getElementById("upload_file_form").elements;
|
let elements = document.getElementById('upload_file_form').elements;
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < elements.length; i++) {
|
||||||
elements[i].disabled = false;
|
elements[i].disabled = false;
|
||||||
}
|
}
|
||||||
let size = document.getElementById("file_size");
|
let size = document.getElementById('file_size');
|
||||||
size.innerHTML = "0 bytes";
|
size.innerHTML = '0 bytes';
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle_submit_form(event) {
|
function handle_submit_form(event) {
|
||||||
let elements = this.elements;
|
let elements = this.elements;
|
||||||
let select_file = elements.namedItem("upload_file");
|
let select_file = elements.namedItem('upload_file');
|
||||||
let text = elements.namedItem("text_input");
|
let text = elements.namedItem('text_input');
|
||||||
if (!!select_file.value.length ^ !!text.value.length) {
|
if (!!select_file.value.length ^ !!text.value.length) {
|
||||||
// Check file size
|
// Check file size
|
||||||
const size = select_file.files[0]?.size ?? 0;
|
const size = select_file.files[0]?.size ?? 0;
|
||||||
if (size > 10485760) {
|
if (size > 10485760) {
|
||||||
alert("Upload file size must not excess 10 MB.");
|
alert('Upload file size must not excess 10 MB.');
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -117,17 +122,17 @@
|
||||||
elements[i].disabled = elements[i].value.length === 0;
|
elements[i].disabled = elements[i].value.length === 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert("You must either upload a file or upload text, but not bothor neither.");
|
alert('You must either upload a file or upload text, but not bothor neither.');
|
||||||
// Prevent default submission
|
// Prevent default submission
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("upload_file").addEventListener("input", update_file_size, false);
|
document.getElementById('upload_file').addEventListener('input', update_file_size, false);
|
||||||
document.getElementById("text_input").addEventListener("input", update_textarea, false);
|
document.getElementById('text_input').addEventListener('input', update_textarea, false);
|
||||||
document.getElementById("show_pass_button").addEventListener("change", toggle_password, false);
|
document.getElementById('show_pass_button').addEventListener('change', toggle_password, false);
|
||||||
document.getElementById("reset_button").addEventListener("click", reset_form, false);
|
document.getElementById('reset_button').addEventListener('click', reset_form, false);
|
||||||
document.getElementById("upload_file_form").addEventListener("submit", handle_submit_form, false)
|
document.getElementById('upload_file_form').addEventListener('submit', handle_submit_form, false);
|
||||||
</script>
|
</script>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://nekoul.com">[Homepage]</a><a href="https://pb.nekoul.com/api">[API]</a>
|
<a href="https://nekoul.com">[Homepage]</a><a href="https://pb.nekoul.com/api">[API]</a>
|
||||||
|
|
68
src/index.ts
68
src/index.ts
|
@ -19,6 +19,7 @@
|
||||||
import {AwsClient} from 'aws4fetch';
|
import {AwsClient} from 'aws4fetch';
|
||||||
import {customAlphabet} from 'nanoid';
|
import {customAlphabet} from 'nanoid';
|
||||||
import {sha256} from 'js-sha256';
|
import {sha256} from 'js-sha256';
|
||||||
|
import dedent from 'dedent-js';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const SERVICE_URL = 'pb.nekoul.com';
|
const SERVICE_URL = 'pb.nekoul.com';
|
||||||
|
@ -27,6 +28,7 @@ const UUID_LENGTH = 4;
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
PASTE_INDEX: KVNamespace;
|
PASTE_INDEX: KVNamespace;
|
||||||
|
QRCODE: ServiceWorkerGlobalScope;
|
||||||
AWS_ACCESS_KEY_ID: string;
|
AWS_ACCESS_KEY_ID: string;
|
||||||
AWS_SECRET_ACCESS_KEY: string;
|
AWS_SECRET_ACCESS_KEY: string;
|
||||||
ENDPOINT: string;
|
ENDPOINT: string;
|
||||||
|
@ -43,18 +45,19 @@ GET /api Fetch API specification
|
||||||
GET /<uuid> Fetch the paste by uuid [x]
|
GET /<uuid> Fetch the paste by uuid [x]
|
||||||
|
|
||||||
# Currently, only the following options is supported for <option>,
|
# Currently, only the following options is supported for <option>,
|
||||||
# "settings": Fetch the paste information
|
# "settings": Fetch the paste information, add \`?qr=1\` to enable QR code generation for paste link.
|
||||||
# "download": Download paste as attachment
|
# "download": Download paste as attachment
|
||||||
# "raw": Display paste as plain text
|
# "raw": Display paste as plain text
|
||||||
GET /<uuid>/<option> Fetch the paste (code) in rendered HTML with syntax highlighting [ ]
|
GET /<uuid>/<option> Fetch the paste (code) in rendered HTML with syntax highlighting [ ]
|
||||||
|
|
||||||
# Only support multipart/form-data and raw request
|
# Only support multipart/form-data and raw request
|
||||||
# For form-data, u=<upload-data>, both title and content-type is deduced from the u
|
# For form-data, u=<upload-data>, both title and content-type is deduced from the u
|
||||||
# The following key is supported for both HTTP form request and headers, prefix "x-" for header keys
|
# Add \`?qr=1\` or qrcode=(on|true) using form-data to enable QR code generation for paste link.
|
||||||
# x-title: File title, i.e.,
|
# The following key is supported for both HTTP form request and headers, add the prefix "x-" for header keys in raw request
|
||||||
|
# title: File title, i.e. main.py
|
||||||
# content-type: The media type (MIME) of the data and encoding, i.e., text/plain; charset=UTF-8;
|
# content-type: The media type (MIME) of the data and encoding, i.e., text/plain; charset=UTF-8;
|
||||||
# x-pass: Paste password
|
# pass: Paste password
|
||||||
# x-read-limit: Limit access times to paste to <read-limit>
|
# read-limit: Limit access times to paste to <read-limit>
|
||||||
POST / Create new paste [x]
|
POST / Create new paste [x]
|
||||||
|
|
||||||
DELETE /<uuid> Delete paste by uuid [x]
|
DELETE /<uuid> Delete paste by uuid [x]
|
||||||
|
@ -64,17 +67,18 @@ POST /<uuid>/settings Update paste setting, i.e., passcode and valid time [ ]
|
||||||
|
|
||||||
* uuid: [A-z0-9]{${UUID_LENGTH}}
|
* uuid: [A-z0-9]{${UUID_LENGTH}}
|
||||||
|
|
||||||
Features
|
Supported Features
|
||||||
* Password protection [x]
|
* Password protection
|
||||||
* Expiring paste [ ]
|
* Limit access times
|
||||||
|
* Generate QR code for paste link
|
||||||
|
|
||||||
[ ] indicated not implemented
|
[ ] indicated not implemented
|
||||||
|
|
||||||
Limitation
|
Limitation
|
||||||
* Max. 10MB file size upload
|
* Max. 10MB file size upload
|
||||||
* Paste will be kept for 180 days only
|
* Paste will be kept for 28 days only by default
|
||||||
|
|
||||||
Last update on 7 June.
|
Last update on 11 Sept.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const gen_id = customAlphabet(
|
const gen_id = customAlphabet(
|
||||||
|
@ -87,7 +91,7 @@ export default {
|
||||||
ctx: ExecutionContext,
|
ctx: ExecutionContext,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const {url, method, headers} = request;
|
const {url, method, headers} = request;
|
||||||
const {pathname} = new URL(url);
|
const {pathname, searchParams} = new URL(url);
|
||||||
const path = pathname.replace(/\/+$/, '') || '/';
|
const path = pathname.replace(/\/+$/, '') || '/';
|
||||||
let cache = caches.default;
|
let cache = caches.default;
|
||||||
|
|
||||||
|
@ -138,6 +142,7 @@ export default {
|
||||||
let mime_type: string | undefined;
|
let mime_type: string | undefined;
|
||||||
let password: string | undefined;
|
let password: string | undefined;
|
||||||
let read_limit: number | undefined;
|
let read_limit: number | undefined;
|
||||||
|
let need_qrcode: boolean = false;
|
||||||
// Content-Type: multipart/form-data
|
// Content-Type: multipart/form-data
|
||||||
if (content_type.includes('form')) {
|
if (content_type.includes('form')) {
|
||||||
const formdata = await request.formData();
|
const formdata = await request.formData();
|
||||||
|
@ -170,12 +175,18 @@ export default {
|
||||||
read_limit = Number(count) || undefined;
|
read_limit = Number(count) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if qrcode generation needed
|
||||||
|
const qr = formdata.get('qrcode');
|
||||||
|
if (typeof qr === 'string' && qr.toLowerCase() === 'true' || qr === 'on') {
|
||||||
|
need_qrcode = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Raw body
|
// Raw body
|
||||||
} else {
|
} else {
|
||||||
if (headers.has('x-title')) {
|
if (headers.has('x-title')) {
|
||||||
title = headers.get('x-title') || '';
|
title = headers.get('x-title') || '';
|
||||||
}
|
}
|
||||||
mime_type = headers.get('content-type') || mime_type;
|
mime_type = headers.get('x-content-type') || mime_type;
|
||||||
password = headers.get('x-pass') || undefined;
|
password = headers.get('x-pass') || undefined;
|
||||||
const count = headers.get('x-read-limit') || undefined;
|
const count = headers.get('x-read-limit') || undefined;
|
||||||
if (count !== undefined && !isNaN(+count)) {
|
if (count !== undefined && !isNaN(+count)) {
|
||||||
|
@ -184,6 +195,11 @@ export default {
|
||||||
buffer = await request.arrayBuffer();
|
buffer = await request.arrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if qrcode generation needed
|
||||||
|
if (searchParams.get('qr') === '1') {
|
||||||
|
need_qrcode = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check password rules
|
// Check password rules
|
||||||
if (password && !check_password_rules(password)) {
|
if (password && !check_password_rules(password)) {
|
||||||
return new Response('Invalid password. ' +
|
return new Response('Invalid password. ' +
|
||||||
|
@ -225,7 +241,7 @@ export default {
|
||||||
|
|
||||||
// Key will be expired after 28 day if unmodified
|
// Key will be expired after 28 day if unmodified
|
||||||
ctx.waitUntil(env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), {expirationTtl: 100800}));
|
ctx.waitUntil(env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), {expirationTtl: 100800}));
|
||||||
return new Response(get_paste_info(uuid, descriptor));
|
return new Response(await get_paste_info(uuid, descriptor, env, need_qrcode));
|
||||||
} else {
|
} else {
|
||||||
return new Response('Unable to upload the paste.\n', {
|
return new Response('Unable to upload the paste.\n', {
|
||||||
status: 500,
|
status: 500,
|
||||||
|
@ -261,8 +277,10 @@ export default {
|
||||||
// Handling /<uuid>/settings
|
// Handling /<uuid>/settings
|
||||||
if (option === 'settings') {
|
if (option === 'settings') {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'GET':
|
case 'GET': {
|
||||||
return new Response(get_paste_info(uuid, descriptor));
|
const need_qrcode = searchParams.get('qr') === '1';
|
||||||
|
return new Response(await get_paste_info(uuid, descriptor, env, need_qrcode));
|
||||||
|
}
|
||||||
|
|
||||||
case 'POST': {
|
case 'POST': {
|
||||||
// TODO Implement paste setting update
|
// TODO Implement paste setting update
|
||||||
|
@ -433,9 +451,10 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function get_paste_info(uuid: string, descriptor: PasteIndexEntry): string {
|
async function get_paste_info(uuid: string, descriptor: PasteIndexEntry, env: Env, need_qr: boolean = false): Promise<string> {
|
||||||
const date = new Date(descriptor.last_modified);
|
const date = new Date(descriptor.last_modified);
|
||||||
return `id: ${uuid}
|
let content = dedent`
|
||||||
|
id: ${uuid}
|
||||||
link: https://${SERVICE_URL}/${uuid}
|
link: https://${SERVICE_URL}/${uuid}
|
||||||
title: ${descriptor.title || '<empty>'}
|
title: ${descriptor.title || '<empty>'}
|
||||||
mime-type: ${descriptor.mime_type ?? '-'}
|
mime-type: ${descriptor.mime_type ?? '-'}
|
||||||
|
@ -446,6 +465,21 @@ remaining read count: ${descriptor.read_count_remain !== undefined ?
|
||||||
descriptor.read_count_remain ? descriptor.read_count_remain : `0 (expired)` : '-'}
|
descriptor.read_count_remain ? descriptor.read_count_remain : `0 (expired)` : '-'}
|
||||||
created at ${date.toISOString()}
|
created at ${date.toISOString()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
if (need_qr) {
|
||||||
|
// Cloudflare currently does not support doing a subrequest to the same zone, use service binding instead
|
||||||
|
const res = await env.QRCODE.fetch('https://qrcode.nekoul.com?' + new URLSearchParams({
|
||||||
|
q: `https://${SERVICE_URL}/${uuid}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const qrcode = await res.text();
|
||||||
|
content += '\n';
|
||||||
|
content += qrcode;
|
||||||
|
content += '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_password_rules(password: string): boolean {
|
function check_password_rules(password: string): boolean {
|
||||||
|
|
|
@ -6,6 +6,9 @@ workers_dev = false
|
||||||
kv_namespaces = [
|
kv_namespaces = [
|
||||||
{ binding = "PASTE_INDEX", id = "a578863da0564cd7beadd9ce4a2d53e8", preview_id = "66d9440e13124099a5e508fe1ff0a489" }
|
{ binding = "PASTE_INDEX", id = "a578863da0564cd7beadd9ce4a2d53e8", preview_id = "66d9440e13124099a5e508fe1ff0a489" }
|
||||||
]
|
]
|
||||||
|
services = [
|
||||||
|
{ binding = "QRCODE", service = "qrcode-gen", environment = "production" }
|
||||||
|
]
|
||||||
|
|
||||||
# [secret]
|
# [secret]
|
||||||
# AWS_ACCESS_KEY_ID
|
# AWS_ACCESS_KEY_ID
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue