Merge remote-tracking branch 'upstream/dev/0.15.0' into dev/0.15.0

pull/249/head
xwashere 6 months ago
commit cb051d12f0
No known key found for this signature in database
GPG Key ID: 042F8BFA1B0EF93B

5
.gitignore vendored

@ -9,4 +9,7 @@ dist*/
# VitePress documentation
docs/.vitepress/dist/
docs/.vitepress/cache/
docs/.vitepress/cache/
# Wrangler local cache (docs dev server)
.wrangler/

@ -61,8 +61,6 @@ const Checkers: UserConfigTypeChecker = {
user: basicStringChecker,
password: basicStringChecker,
database: basicStringChecker,
},
postgres: {
port: (val) => numChecker(val) && val >= 1 && val <= 65535
}
},
@ -118,11 +116,7 @@ export class UserConfig {
if (!Checkers.sql.mySql.user(config.database.options.user)) throw new Error('Invalid databse user');
if (!Checkers.sql.mySql.password(config.database.options.password)) throw new Error('Invalid database password');
if (!Checkers.sql.mySql.database(config.database.options.database)) throw new Error('Invalid database');
if (config.database.kind == 'postgres' || config.database.kind == 'mongodb') {
if (!Checkers.sql.postgres.port((config.database.options as PostgresConfiguration | MongoDBConfiguration).port)) {
throw new Error('Invalid database port');
}
}
if (!Checkers.sql.mySql.port(config.database.options.port)) throw new Error('Invalid database port');
} else throw new Error('Database options missing');
}
}

@ -1,4 +1,4 @@
import { AssFile, AssUser, NID } from 'ass';
import { AssFile, AssUser, DatabaseValue, NID } from 'ass';
import { log } from './log.js';
import { UserConfig } from './UserConfig.js';
@ -36,19 +36,18 @@ export const put = (sector: DataSector, key: NID, data: AssFile | AssUser): Prom
}
});
export const get = (sector: DataSector, key: NID): Promise<AssFile | AssUser | false> => new Promise(async (resolve, reject) => {
export const get = (sector: DataSector, key: NID): Promise<DatabaseValue> => new Promise(async (resolve, reject) => {
try {
const data: AssFile | AssUser | undefined = await DBManager.get(sector === 'files' ? 'assfiles' : 'assusers', key) as AssFile | AssUser | undefined
(!data) ? resolve(false) : resolve(data);
const data = await DBManager.get(sector === 'files' ? 'assfiles' : 'assusers', key);
resolve(data);
} catch (err) {
reject(err);
}
});
export const getAll = (sector: DataSector): Promise<{ [key: string]: AssFile | AssUser }> => new Promise(async (resolve, reject) => {
export const getAll = (sector: DataSector): Promise<DatabaseValue[]> => new Promise(async (resolve, reject) => {
try {
// todo: fix MySQL
const data: { [key: string]: AssFile | AssUser } = await DBManager.getAll(sector === 'files' ? 'assfiles' : 'assusers') as /* AssFile[] | AssUser[] | */ {}
const data = await DBManager.getAll(sector === 'files' ? 'assfiles' : 'assusers');
resolve(data);
} catch (err) {
reject(err);

@ -57,6 +57,7 @@ router.post('/setup', BodyParserJson(), async (req, res) => {
return res.json({ success: true });
} catch (err: any) {
log.error('Setup failed', err);
return res.status(400).json({ success: false, message: err.message });
}
});
@ -68,8 +69,8 @@ router.post('/login', rateLimiterMiddleware('login', UserConfig.config?.rateLimi
data.getAll('users')
.then((users) => {
if (!users) throw new Error('Missing users data');
else return Object.entries(users as { [key: string]: AssUser })
.filter(([_uid, user]: [string, AssUser]) => user.username === username)[0][1]; // [0] is the first item in the filter results, [1] is is AssUser
else return Object.entries(users as AssUser[])
.filter(([_uid, user]: [string, AssUser]) => user.username === username)[0][1]; // [0] is the first item in the filter results, [1] is AssUser
})
.then((user) => Promise.all([bcrypt.compare(password, user.password), user]))
.then(([success, user]) => {
@ -88,7 +89,7 @@ router.post('/login', rateLimiterMiddleware('login', UserConfig.config?.rateLimi
// Delete the pre-login path after successful login
if (success) delete req.session.ass?.preLoginPath;
})
.catch((err) => res.status(400).json({ success: false, message: err.message }));
.catch((err) => log.error(err).callback(() => res.status(400).json({ success: false, message: err.message })));
});
// todo: authenticate API endpoints
@ -133,7 +134,10 @@ router.post('/user', rateLimiterMiddleware('api', UserConfig.config?.rateLimit?.
} catch (err: any) { issue = `Error: ${err.message}`; }
if (issue) return res.status(400).json({ success: false, messsage: issue });
if (issue) {
log.error('Failed to create user', issue);
return res.status(400).json({ success: false, messsage: issue });
}
log.debug(`User created`, user!.username);
res.json(({ success: true, message: `User ${user!.username} created` }));

@ -1,42 +1,4 @@
import { AssFile, AssUser, NID, UploadToken } from 'ass';
export type DatabaseValue = AssFile | AssUser | UploadToken;
export type DatabaseTable = 'assfiles' | 'assusers' | 'asstokens';
/**
* interface for database classes
*/
export interface Database {
/**
* preform database initialization tasks
*/
open(): Promise<void>;
/**
* preform database suspension tasks
*/
close(): Promise<void>;
/**
* set up database
*/
configure(): Promise<void>;
/**
* put a value in the database
*/
put(table: DatabaseTable, key: NID, data: DatabaseValue): Promise<void>;
/**
* get a value from the database
*/
get(table: DatabaseTable, key: NID): Promise<DatabaseValue | undefined>;
/**
* get all values from the database
*/
getAll(table: DatabaseTable): Promise<{ [index: string]: DatabaseValue }>;
}
import { NID, Database, DatabaseTable, DatabaseValue } from "ass";
export class DBManager {
private static _db: Database;
@ -79,7 +41,7 @@ export class DBManager {
/**
* put a value in the database
*/
public static put(table: DatabaseTable, key: NID, data: DatabaseValue): Promise<void> {
public static put(table: DatabaseTable, key: NID, data: DatabaseValue): Promise<void> {
if (this._db && this._dbReady) {
return this._db.put(table, key, data);
} else throw new Error('No database active');
@ -88,7 +50,7 @@ export class DBManager {
/**
* get a value from the database
*/
public static get(table: DatabaseTable, key: NID): Promise<DatabaseValue | undefined> {
public static get(table: DatabaseTable, key: NID): Promise<DatabaseValue> {
if (this._db && this._dbReady) {
return this._db.get(table, key);
} else throw new Error('No database active');
@ -97,7 +59,7 @@ export class DBManager {
/**
* get all values from the database
*/
public static getAll(table: DatabaseTable): Promise<{ [index: string]: DatabaseValue }> {
public static getAll(table: DatabaseTable): Promise<DatabaseValue[]> {
if (this._db && this._dbReady) {
return this._db.getAll(table);
} else throw new Error('No database active');

@ -1,9 +1,8 @@
import { AssFile, AssUser, FilesSchema, UsersSchema } from 'ass';
import { AssFile, AssUser, FilesSchema, UsersSchema, Database, DatabaseTable, DatabaseValue } from 'ass';
import path, { resolve } from 'path';
import fs from 'fs-extra';
import { Database, DatabaseTable, DatabaseValue } from './database.js';
import { log } from '../log.js';
import { nanoid } from '../generators.js';
@ -136,17 +135,18 @@ export class JSONDatabase implements Database {
})
}
public get(table: DatabaseTable, key: string): Promise<DatabaseValue | undefined> {
public get(table: DatabaseTable, key: string): Promise<DatabaseValue> {
return new Promise(async (resolve, reject) => {
const data = (await fs.readJson(PATHMAP[table]))[SECTORMAP[table]][key];
(!data) ? resolve(undefined) : resolve(data);
(!data) ? reject(new Error(`Key '${key}' not found in '${table}'`)) : resolve(data);
});
}
public getAll(table: DatabaseTable): Promise<{ [index: string]: DatabaseValue }> {
public getAll(table: DatabaseTable): Promise<DatabaseValue[]> {
return new Promise(async (resolve, reject) => {
const data = (await fs.readJson(PATHMAP[table]))[SECTORMAP[table]];
(!data) ? resolve({}) : resolve(data);
// todo: fix this
(!data) ? resolve(data) : resolve(data);
});
}
}

@ -1,9 +1,8 @@
import { AssFile, AssUser, MongoDBConfiguration, NID, UploadToken } from 'ass';
import { AssFile, AssUser, MongoDBConfiguration, NID, UploadToken, Database, DatabaseTable, DatabaseValue } from 'ass';
import mongoose, { Model, Mongoose, Schema } from 'mongoose';
import { UserConfig } from '../UserConfig.js';
import { Database, DatabaseTable, DatabaseValue } from './database.js';
import { log } from '../log.js';
interface TableVersion {
@ -194,7 +193,7 @@ export class MongoDBDatabase implements Database {
});
}
get(table: DatabaseTable, key: string): Promise<DatabaseValue | undefined> {
get(table: DatabaseTable, key: string): Promise<DatabaseValue> {
return new Promise(async (resolve, reject) => {
try {
const models = {
@ -217,7 +216,7 @@ export class MongoDBDatabase implements Database {
});
}
getAll(table: DatabaseTable): Promise<{ [index: string]: DatabaseValue; }> {
getAll(table: DatabaseTable): Promise<DatabaseValue[]> {
return new Promise(async (resolve, reject) => {
try {
const models = {

@ -1,10 +1,9 @@
import { AssFile, AssUser, NID, UploadToken } from 'ass';
import { AssFile, AssUser, NID, UploadToken, Database, DatabaseTable, DatabaseValue } from 'ass';
import mysql, { Pool } from 'mysql2/promise';
import { log } from '../log.js';
import { UserConfig } from '../UserConfig.js';
import { Database, DatabaseTable, DatabaseValue } from './database.js';
export class MySQLDatabase implements Database {
private _pool: Pool;
@ -78,7 +77,7 @@ export class MySQLDatabase implements Database {
this._tableManager('create', 'assusers'),
this._tableManager('create', 'asstokens')
]);
log.success('MySQL', 'Tables created').callback(resolve);
log.success('MySQL', 'Tables created');
} else {
// There's at least one row, do further checks
@ -113,12 +112,13 @@ export class MySQLDatabase implements Database {
// Hopefully we are ready
if (tablesExist.files && tablesExist.users)
log.info('MySQL', 'Tables exist, ready').callback(() => {
this._ready = true;
resolve(void 0);
});
log.info('MySQL', 'Tables exist, ready');
else throw new Error('Table(s) missing!');
}
// We are ready!
this._ready = true;
resolve();
} catch (err) {
log.error('MySQL', 'failed to initialize');
console.error(err);
@ -131,7 +131,13 @@ export class MySQLDatabase implements Database {
return new Promise(async (resolve, reject) => {
if (!this._ready) return reject(new Error('MySQL not ready'));
if (await this.get(table, key)) reject(new Error(`${table == 'assfiles' ? 'File' : table == 'assusers' ? 'User' : 'Token'} key ${key} already exists`));
try {
if (await this.get(table, key))
reject(new Error(`${table == 'assfiles' ? 'File' : table == 'assusers' ? 'User' : 'Token'} key ${key} already exists`));
} catch (err: any) {
if (!err.message.includes('not found in'))
reject(err);
}
const query = `
INSERT INTO ${table} ( NanoID, Data )
@ -144,7 +150,7 @@ VALUES ('${key}', '${JSON.stringify(data)}');
});
}
public get(table: DatabaseTable, key: NID): Promise<DatabaseValue | undefined> {
public get(table: DatabaseTable, key: NID): Promise<DatabaseValue> {
return new Promise(async (resolve, reject) => {
try {
// Run query
@ -153,27 +159,24 @@ VALUES ('${key}', '${JSON.stringify(data)}');
// Disgustingly interpret the query results
const rows_tableData = (rowz as unknown as { [key: string]: string }[])[0] as unknown as ({ Data: UploadToken | AssFile | AssUser | undefined });
resolve(rows_tableData?.Data ?? undefined);
if (rows_tableData?.Data) resolve(rows_tableData.Data);
else throw new Error(`Key '${key}' not found in '${table}'`);
} catch (err) {
reject(err);
}
});
}
// todo: unknown if this works
public getAll(table: DatabaseTable): Promise<{ [index: string]: DatabaseValue }> {
public getAll(table: DatabaseTable): Promise<DatabaseValue[]> {
return new Promise(async (resolve, reject) => {
try {
// Run query // ! this may not work as expected
// Run query
const [rowz, _fields] = await this._pool.query(`SELECT Data FROM ${table}`);
// Interpret results this is pain
const rows = (rowz as unknown as { [key: string]: string }[]);
// console.log(rows);
const rows = (rowz as unknown as { Data: UploadToken | AssFile | AssUser }[]);
// aaaaaaaaaaaa
resolve({});
resolve(rows.map((row) => row.Data));
} catch (err) {
reject(err);
}

@ -1,8 +1,7 @@
import { PostgresConfiguration } from 'ass';
import { PostgresConfiguration, Database, DatabaseTable, DatabaseValue } from 'ass';
import pg from 'pg';
import { log } from '../log.js';
import { Database, DatabaseTable, DatabaseValue } from './database.js';
import { UserConfig } from '../UserConfig.js';
/**
@ -161,7 +160,7 @@ export class PostgreSQLDatabase implements Database {
});
}
public get(table: DatabaseTable, key: string): Promise<DatabaseValue | undefined> {
public get(table: DatabaseTable, key: string): Promise<DatabaseValue> {
return new Promise(async (resolve, reject) => {
try {
const queries = {
@ -179,7 +178,8 @@ export class PostgreSQLDatabase implements Database {
});
}
public getAll(table: DatabaseTable): Promise<{ [index: string]: DatabaseValue; }> {
// XXX: This is broken
public getAll(table: DatabaseTable): Promise<DatabaseValue[]> {
return new Promise(async (resolve, reject) => {
try {
const queries = {

44
common/types.d.ts vendored

@ -3,6 +3,9 @@ declare module 'ass' {
type IdType = 'random' | 'original' | 'gfycat' | 'timestamp' | 'zws'
export type DatabaseValue = AssFile | AssUser | UploadToken;
export type DatabaseTable = 'assfiles' | 'assusers' | 'asstokens';
/**
* Core Express server config.
* This is separate from the user configuration starting in 0.15.0
@ -70,6 +73,42 @@ declare module 'ass' {
}
}
/**
* interface for database classes
*/
export interface Database {
/**
* preform database initialization tasks
*/
open(): Promise<void>;
/**
* preform database suspension tasks
*/
close(): Promise<void>;
/**
* set up database
*/
configure(): Promise<void>;
/**
* put a value in the database
*/
put(table: DatabaseTable, key: NID, data: DatabaseValue): Promise<void>;
/**
* get a value from the database
*/
get(table: DatabaseTable, key: NID): Promise<DatabaseValue>;
/**
* get all values from the database
*/
getAll(table: DatabaseTable): Promise<DatabaseValue[]>;
}
interface DatabaseConfiguration {
kind: 'mysql' | 'postgres' | 'json' | 'mongodb';
options?: MySQLConfiguration | PostgresConfiguration | MongoDBConfiguration;
@ -77,6 +116,7 @@ declare module 'ass' {
interface MySQLConfiguration {
host: string;
port: number;
user: string;
password: string;
database: string;
@ -153,13 +193,11 @@ declare module 'ass' {
sql: {
mySql: {
host: (val: any) => boolean;
port: (val: any) => boolean;
user: (val: any) => boolean;
password: (val: any) => boolean;
database: (val: any) => boolean;
}
postgres: {
port: (val: any) => boolean;
}
}
rateLimit: {
endpoint: (val: any) => boolean;

@ -1,13 +1,29 @@
import { defineConfig } from 'vitepress'
import { defineConfig } from 'vitepress';
const LOGO = 'https://i.tycrek.dev/ass';
const GIT_BRANCH = 'dev/0.15.0'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "ass docs",
description: "Documentation for ass, a ShareX server",
lang: 'en-US',
title: 'ass docs',
titleTemplate: ':title ~ ass docs',
description: 'Documentation for ass, an open-source ShareX server',
cleanUrls: true,
lastUpdated: true,
head: [
['meta', { property: 'og:image', content: LOGO }],
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'twitter:domain', content: 'ass.tycrek.dev' }],
['meta', { property: 'twitter:image', content: LOGO }],
['link', { rel: 'icon', href: LOGO }],
],
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
logo: LOGO,
nav: [
{ text: 'Home', link: '/' },
{
@ -15,7 +31,8 @@ export default defineConfig({
{ text: 'Docker', link: '/install/docker' },
{ text: 'Local', link: '/install/local' }
]
}
},
{ text: 'Configure', link: '/configure/' }
],
sidebar: [
@ -27,6 +44,38 @@ export default defineConfig({
{ text: 'Local', link: '/install/local' }
]
},
{
text: 'Configure',
link: '/configure/',
items: [
{
text: 'SQL',
items: [
{
text: 'MySQL',
link: '/configure/sql/mysql'
},
{
text: 'PostgreSQL',
link: '/configure/sql/postgresql'
}
]
},
{
text: 'Clients',
items: [
{
text: 'ShareX',
link: '/configure/clients/sharex'
},
{
text: 'Flameshot',
link: '/configure/clients/flameshot'
}
]
}
]
},
{
text: 'Customize',
link: '/customize/',
@ -36,9 +85,19 @@ export default defineConfig({
}
],
editLink: {
pattern: `https://github.com/tycrek/ass/edit/${GIT_BRANCH}/docs/:path`,
text: 'Edit this page on GitHub',
},
footer: {
message: 'Released under the ISC License.',
copyright: 'Copyright © 2023 tycrek & ass contributors',
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/tycrek/ass/' },
{ icon: 'discord', link: 'https://discord.gg/wGZYt5fasY' }
]
}
})
});

@ -0,0 +1,11 @@
# Flameshot
The Flameshot script has been updated to be a lot more dynamic, including adding support for [cheek](https://github.com/tycrek/cheek#readme), my serverless ShareX upload server. To set cheek mode, edit the file [`flameshot-v2.sh`](https://github.com/tycrek/ass/blob/dev/0.15.0/flameshot-v2.sh) and set `MODE=0` to `MODE=1`.
To set your token (not in use yet, can be random) and domain for the script, create these directories with the following files:
- `~/.ass/` (or `~/.cheek/`)
- `~/.ass/.token`
- `~/.ass/.domain`
For `.domain`, you do **not** need to include `http(s)://`.

@ -0,0 +1,10 @@
# ShareX
| Setting | Value |
| ------- | ----- |
| Request URL | Your server domain (including `http(s)://`) |
| Request Method | `POST` |
| Destination Type | `Image`, `Text`, `File` |
| Body | `multipart/form-data` |
| File Form Name | `file` |
| URL | `{json.resource}` |

@ -0,0 +1,25 @@
# Configure
Most of the configuration is managed through the administrator dashboard.
## `server.json` overrides
The webserver in ass 15 is hosted independently of any user configuration. If you wish to set a specific server setting, you may do so with a `server.json` file.
Place this file in `<root>/.ass-data/`.
| Property | Use | Default |
| -------- | --- | ------- |
| `host` | Local IP to bind to | `0.0.0.0` |
| `port` | Port to listen on | `40115` |
| `proxied` | If ass is behind a reverse proxy | `false`, unless `NODE_ENV=production` is specified, otherwise `true` |
**Example**
```json
{
"host": "127.0.1.2",
"port": 40200,
"proxied": false
}
```

@ -0,0 +1,7 @@
# MySQL
## Provider-specific instructions
### Aiven
In the **Overview** panel, scroll down to **Advanced**, and set `mysql.sql_require_primary_key` to **`Disabled`**.

@ -6,15 +6,16 @@ title: Home
hero:
name: ass
text: open-source file management server
text: open-source file hosting server
tagline: Unopinionated, customizable, uniquely yours.
actions:
- theme: brand
text: Install
text: Get Started
link: /install/
- theme: alt
text: Customize
link: /customize/
text: View on GitHub
link: /configure/
image:
src: 'https://i.tycrek.dev/ass-round-square-logo-white-with-text'
alt: ass logo

@ -1,3 +1,30 @@
# Docker
Deploy with Docker Compose
The Docker method uses [Docker Compose][1] for a quick and easy installation. For a faster deployment, a pre-built image is pulled from [Docker Hub](https://hub.docker.com/r/tycrek/ass).
## Requirements
- Latest [Docker](https://docs.docker.com/engine/install/)
- [Docker Compose][1] v2 plugin
[1]: https://docs.docker.com/compose/
## Install
```bash
git clone -b dev/0.15.0 https://github.com/tycrek/ass.git
cd ass/
docker compose up -d
```
### View logs
Use the following command to view the container logs:
```bash
docker compose logs -n <lines> --follow
```
## Build local image
If you wish to build a Docker image locally for development, you can use the provided [docker-dev-container.sh](https://github.com/tycrek/ass/blob/dev/0.15.0/docker-dev-container.sh) script.

@ -1,3 +1,18 @@
# Installation
You can use either [Docker](docker) (recommended) or your [local Node.js](local) installation.
You can use either [Docker](docker) (recommended) or your [local Node.js](local) installation.
::: warning ass 0.15.0 is experimental
Branch [`dev/0.15.0`](https://github.com/tycrek/ass/tree/dev/0.15.0/) is a full rewrite of the ass codebase.
At this time, it is working and ready for testing, **but is very incomplete** and is lacking many features currently found in ass 0.14.
**The existing configs, data.json, and auth.json will be abandoned. There is currently no migration nor one planned.**
::::
## Alternatives
These are maintained by the ass community.
- Nix flake (soon)
- Pterodactyl Egg (soon)

@ -1,3 +1,20 @@
# Local install
Local installation with Node.js
The local method uses the [Node.js](https://nodejs.org/en) installation found on your system.
## Requirements
- **Node 20** or later
- [pnpm](https://pnpm.io/installation)
## Install
```bash
git clone -b dev/0.15.0 https://github.com/tycrek/ass.git && cd ass/
pnpm i
# or: npm i -D
pnpm run dev
# After ass has been compiled, you can instead use:
pnpm run start
```

@ -13,11 +13,16 @@
# ! Upload mode (ass=0,cheek=1)
MODE=0
# Function to check if a tool is installed
check_tool() {
command -v "$1" >/dev/null 2>&1
}
# Mode string switcher
get_mode() {
if [[ $MODE -eq 0 ]];
then echo "ass"
else echo "cheek"
then echo "ass"
else echo "cheek"
fi
}
@ -29,42 +34,56 @@ FILE="$IMGPATH/$(get_mode)-$(date +%s).png"
TOKEN=$(cat $IMGPATH/.token)
DOMAIN=$(cat $IMGPATH/.domain)
# Build dynamic Flameshot user-agent
USERAGENT=$(flameshot -v | sed -n -E 's/(Flameshot) (v[0-9]+\.[0-9]+\.[0-9]+) .+/\1-\2/p')
# Take screenshot with Flameshot
flameshot gui -r -p "$FILE" > /dev/null # Append the random gibberish to /dev/null
# Upload file
if [ -f "$FILE" ]; then
echo "Uploading $FILE to $(get_mode)..."
# Configure upload fields
FIELD="$([[ $MODE -eq 0 ]] && echo "file" || echo "image")=@$FILE"
[[ "${DOMAIN%%:*}" = "127.0.0.1" ]] && PROTOCOL="http" || PROTOCOL="https"
POSTTO="$PROTOCOL://$DOMAIN/$([[ $MODE -eq 0 ]] && echo "" || echo "upload")"
# Upload the file
URL=$(curl -sS -X POST \
-H "Content-Type: multipart/form-data" \
-H "Accept: application/json" \
-H "User-Agent: $USERAGENT" \
-H "Authorization: $TOKEN" \
-F $FIELD $POSTTO
)
# Response parser unique to ass
if [[ $MODE -eq 0 ]]; then
URL=$(echo $URL | grep -Po '(?<="resource":")[^"]+')
fi
takeScreenshot() {
REQUIRED_TOOLS=("flameshot" "curl" "xclip" "notify-send")
# Check if the proper tools are installed
for tool in "${REQUIRED_TOOLS[@]}"; do
if ! check_tool "$tool"; then
echo "Error: $tool is missing!"
exit 1
fi
done
# Build dynamic Flameshot user-agent
USERAGENT=$(flameshot -v | sed -n -E 's/(Flameshot) (v[0-9]+\.[0-9]+\.[0-9]+) .+/\1-\2/p')
# Take screenshot with Flameshot
flameshot gui -r -p "$FILE" > /dev/null # Append the random gibberish to /dev/null
# Upload file
if [ -f "$FILE" ]; then
echo "Uploading $FILE to $(get_mode)..."
# Copy the URL to clipboard (using printf instead of echo to avoid a newline)
printf "%s" "$URL" | xclip -sel clip
echo "URL copied: $URL"
notify-send -a $(get_mode) -t 2000 "URL copied to clipboard" "<a href=\"$URL\">View in browser</a>"
# Configure upload fields
FIELD="$([[ $MODE -eq 0 ]] && echo "file" || echo "image")=@$FILE"
[[ "${DOMAIN%%:*}" = "127.0.0.1" ]] && PROTOCOL="http" || PROTOCOL="https"
POSTTO="$PROTOCOL://$DOMAIN/$([[ $MODE -eq 0 ]] && echo "" || echo "upload")"
# Upload the file
URL=$(curl -sS -X POST \
-H "Content-Type: multipart/form-data" \
-H "Accept: application/json" \
-H "User-Agent: $USERAGENT" \
-H "Authorization: $TOKEN" \
-F $FIELD $POSTTO
)
# Response parser unique to ass
if [[ $MODE -eq 0 ]]; then
URL=$(echo $URL | grep -Po '(?<="resource":")[^"]+')
fi
# Copy the URL to clipboard (using printf instead of echo to avoid a newline)
printf "%s" "$URL" | xclip -sel clip
echo "URL copied: $URL"
notify-send -a $(get_mode) -t 4000 "URL copied to clipboard" "<a href=\"$URL\">View in browser</a>"
# Delete local file
rm "$FILE"
else
echo "Aborted."
fi
}
# Delete local file
rm "$FILE"
else
echo "Aborted."
fi
takeScreenshot

@ -45,6 +45,7 @@ document.addEventListener('DOMContentLoaded', () => {
mySqlTab: document.querySelector('#mysql-tab') as SlTab,
mySqlHost: document.querySelector('#mysql-host') as SlInput,
mySqlPort: document.querySelector('#mysql-port') as SlInput,
mySqlUser: document.querySelector('#mysql-user') as SlInput,
mySqlPassword: document.querySelector('#mysql-password') as SlInput,
mySqlDatabase: document.querySelector('#mysql-database') as SlInput,
@ -115,8 +116,9 @@ document.addEventListener('DOMContentLoaded', () => {
config.database = {
kind: 'mysql',
options: {
host: Elements.mySqlHost.value,
user: Elements.mySqlUser.value,
host: Elements.mySqlHost.value,
port: parseInt(Elements.mySqlPort.value),
user: Elements.mySqlUser.value,
password: Elements.mySqlPassword.value,
database: Elements.mySqlDatabase.value
}
@ -127,9 +129,9 @@ document.addEventListener('DOMContentLoaded', () => {
config.database = {
kind: 'postgres',
options: {
host: Elements.pgsqlHost.value,
port: parseInt(Elements.pgsqlPort.value),
user: Elements.pgsqlUser.value,
host: Elements.pgsqlHost.value,
port: parseInt(Elements.pgsqlPort.value),
user: Elements.pgsqlUser.value,
password: Elements.pgsqlPassword.value,
database: Elements.pgsqlDatabase.value
}

7644
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -8,28 +8,28 @@
"node": "^20"
},
"scripts": {
"start": "node dist/backend/app.js",
"dev": "npm run build && npm start",
"fresh": "rm -dr .ass-data/ & npm run dev",
"build": "rm -dr dist/ & npm run build:backend && npm run build:frontend && npm run build:fix-frontend",
"dev:docs": "wrangler pages dev --proxy 5173 -- npm run vp:dev",
"build": "rm -dr dist/ ; npm run build:backend && npm run build:frontend && npm run build:fix-frontend",
"build:backend": "tsc -p backend/",
"build:frontend": "tsc -p frontend/",
"build:fix-frontend": "node common/fix-frontend-js.js",
"start": "node dist/backend/app.js",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
"vp:dev": "vitepress dev docs",
"vp:build": "vitepress build docs",
"vp:preview": "vitepress preview docs"
},
"repository": "github:tycrek/ass",
"keywords": [
"sharex",
"sharex-server"
],
"author": "tycrek <t@tycrek.com> (https://tycrek.com/)",
"author": "tycrek <sylvie@tycrek.com> (https://tycrek.com/)",
"license": "ISC",
"bugs": "https://github.com/tycrek/ass/issues",
"homepage": "https://github.com/tycrek/ass#readme",
"dependencies": {
"@aws-sdk/client-s3": "^3.456.0",
"@aws-sdk/client-s3": "^3.465.0",
"@shoelace-style/shoelace": "^2.12.0",
"@tinycreek/postcss-font-magician": "^4.2.0",
"@tsconfig/node20": "^20.1.2",
@ -45,10 +45,10 @@
"cssnano": "^6.0.1",
"express": "^4.18.2",
"express-busboy": "^10.1.0",
"express-rate-limit": "^7.1.4",
"express-rate-limit": "^7.1.5",
"express-session": "^1.17.3",
"ffmpeg-static": "^5.2.0",
"fs-extra": "^11.1.1",
"fs-extra": "^11.2.0",
"luxon": "^3.4.4",
"memorystore": "^1.6.7",
"mongoose": "^8.0.0",
@ -59,7 +59,7 @@
"sharp": "^0.32.6",
"shoelace-fontawesome-pug": "^6.4.3",
"shoelace-pug-loader": "^2.11.0",
"tailwindcss": "^3.3.5",
"tailwindcss": "^3.3.6",
"typescript": "^5.3.2"
},
"devDependencies": {
@ -69,10 +69,11 @@
"@types/express-session": "^1.17.10",
"@types/ffmpeg-static": "^3.0.3",
"@types/fs-extra": "^11.0.4",
"@types/luxon": "^3.3.5",
"@types/node": "^20.10.0",
"@types/luxon": "^3.3.6",
"@types/node": "^20.10.3",
"@types/pg": "^8.10.9",
"vitepress": "1.0.0-rc.31",
"vue": "^3.3.9"
"vue": "^3.3.10",
"wrangler": "^3.18.0"
}
}

File diff suppressed because it is too large Load Diff

@ -44,6 +44,8 @@ block content
sl-tab-panel(name='mysql')
h3.setup-text-item-title Host
sl-input#mysql-host(type='text' placeholder='mysql.example.com' clearable): sl-icon(slot='prefix' name='fas-server' library='fa')
h3.setup-text-item-title Port
sl-input#mysql-port(type='number' placeholder='3306' min='1' max='65535' no-spin-buttons clearable): sl-icon(slot='prefix' name='fas-hashtag' library='fa')
h3.setup-text-item-title User
sl-input#mysql-user(type='text' placeholder='myassql' clearable): sl-icon(slot='prefix' name='fas-user' library='fa')
h3.setup-text-item-title Password
@ -57,7 +59,7 @@ block content
h3.setup-text-item-title Host
sl-input#pgsql-host(type='text' placeholder='postgres.example.com' clearable): sl-icon(slot='prefix' name='fas-server' library='fa')
h3.setup-text-item-title Port
sl-input#pgsql-port(type='number' placeholder='5432' min='1' max='65535' no-spin-buttons clearable): sl-icon(slot='prefix' name='fas-number' library='fa')
sl-input#pgsql-port(type='number' placeholder='5432' min='1' max='65535' no-spin-buttons clearable): sl-icon(slot='prefix' name='fas-hashtag' library='fa')
h3.setup-text-item-title User
sl-input#pgsql-user(type='text' placeholder='posgrassql' clearable): sl-icon(slot='prefix' name='fas-user' library='fa')
h3.setup-text-item-title Password

Loading…
Cancel
Save