1
Fork 0
mirror of https://github.com/diced/zipline.git synced 2025-04-11 23:31:17 -05:00
This commit is contained in:
dicedtomatoreal 2020-05-04 20:00:13 -07:00
parent 5c2ee55994
commit 20c3bf9910
18 changed files with 402 additions and 165 deletions

5
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

6
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/typex.iml" filepath="$PROJECT_DIR$/.idea/typex.iml" />
</modules>
</component>
</project>

12
.idea/typex.iml generated Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

309
README.md
View file

@ -243,159 +243,170 @@ Particles.JS, can be enabled and it's config can be changed willingly.
```json
{
"upload": {
"fileLength": 6,
"tempDir": "./temp",
"uploadDir": "./uploads",
"route": "/u"
},
"user": {
"tokenLength": 32
},
"site": {
"protocol": "http",
"ssl": {
"key": "./ssl/server.key",
"cert": "./ssl/server.crt"
"upload": {
"fileLength": 6,
"tempDir": "./temp",
"uploadDir": "./uploads",
"route": "/u"
},
"serveHTTPS": 8000,
"serveHTTP": 8000
},
"administrator": {
"password": "1234",
"authorization": "Administrator 1234"
},
"orm": {
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "user",
"password": "1234",
"database": "typex",
"synchronize": true,
"logging": false,
"entities": [
"out/src/entities/**/*.js"
]
},
"sessionSecret": "qwerty",
"meta": {
"favicon": "/public/assets/typex_small_circle.png",
"title": "TypeX"
},
"particles": {
"enabled": true,
"settings": {
"particles": {
"number": {
"value": 52,
"density": {
"enable": true,
"value_area": 800
}
"shorten": {
"idLength": 4,
"route": "/s"
},
"user": {
"tokenLength": 32
},
"site": {
"protocol": "http",
"returnProtocol": "https",
"ssl": {
"key": "./ssl/server.key",
"cert": "./ssl/server.crt"
},
"color": {
"value": "#cd4c4c"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 9
},
"image": {
"src": "img/github.svg",
"width": 60,
"height": 100
}
},
"opacity": {
"value": 0.5,
"random": false,
"anim": {
"enable": false,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 0,
"random": true,
"anim": {
"enable": false,
"speed": 40,
"size_min": 0.1,
"sync": false
}
},
"line_linked": {
"enable": true,
"distance": 150,
"color": "#ffffff",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 6,
"direction": "none",
"random": false,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
}
"serveHTTPS": 8000,
"serveHTTP": 443,
"logRoutes": true
},
"administrator": {
"password": "1234"
},
"orm": {
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "user",
"password": "1234",
"database": "typex",
"synchronize": true,
"logging": false,
"entities": [
"out/src/entities/**/*.js"
]
},
"sessionSecret": "1234",
"meta": {
"favicon": "/public/assets/typex_small_circle.png",
"title": "TypeX"
},
"discordWebhook": {
"enabled": true,
"url": "https://canary.discordapp.com/api/webhooks/id/token",
"username": "TypeX Logs",
"avatarURL": "https://domain/public/assets/typex_small_circle.png"
},
"particles": {
"enabled": true,
"settings": {
"particles": {
"number": {
"value": 52,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#cd4c4c"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 9
},
"image": {
"src": "img/github.svg",
"width": 60,
"height": 100
}
},
"opacity": {
"value": 0.5,
"random": false,
"anim": {
"enable": false,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 0,
"random": true,
"anim": {
"enable": false,
"speed": 40,
"size_min": 0.1,
"sync": false
}
},
"line_linked": {
"enable": true,
"distance": 150,
"color": "#ffffff",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 6,
"direction": "none",
"random": false,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": false,
"mode": "grab"
},
"onclick": {
"enable": false,
"mode": "repulse"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 8,
"speed": 3
},
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": false,
"mode": "grab"
},
"onclick": {
"enable": false,
"mode": "repulse"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 8,
"speed": 3
},
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}
}
}
```

7
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "typex",
"version": "0.3.1",
"version": "0.3.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -852,6 +852,11 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",

View file

@ -18,9 +18,10 @@
"http-status-codes": "^1.4.0",
"mime": "^2.4.4",
"multer": "^1.4.2",
"node-fetch": "^2.6.0",
"typeorm": "^0.2.24"
},
"devDependencies": {
"pg": "^8.0.3"
}
}
}

View file

@ -2,7 +2,7 @@ import { OK, BAD_REQUEST, FORBIDDEN } from 'http-status-codes';
import { Controller, Middleware, Get, Post, Put, Delete, Patch } from '@overnightjs/core';
import { Request, Response } from 'express';
import { ORMHandler } from '..';
import { randomId, getUser, getImage, findFile } from '../util';
import { randomId, getUser, getImage, findFile, getShorten } from '../util';
import { createReadStream, createWriteStream, unlinkSync, existsSync, mkdirSync, readFileSync } from 'fs'
import multer from 'multer'
import { getExtension } from 'mime';
@ -10,6 +10,9 @@ import { User } from '../entities/User';
import { sep } from 'path';
import { cookiesForAPI } from '../middleware/cookiesForAPI';
import Logger from '@ayanaware/logger';
import { DiscordWebhook } from '../structures/DiscordWebhook';
import { ImageUtil } from '../structures/ImageUtil';
import { ShortenUtil } from '../structures/ShortenUtil';
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
@ -42,9 +45,24 @@ export class APIController {
source.on("end", function () {
unlinkSync(file.path);
});
const img = await getImage(this.orm, `${config.site.returnProtocol}://${req.headers['host']}/u/${id}.${extension}`, user.id)
const img = await getImage(this.orm, `${config.site.returnProtocol}://${req.headers['host']}${config.upload.route}/${id}.${extension}`, user.id)
Logger.get('TypeX.Uploader').info(`New image uploaded ${img.url} (${img.id}) by ${user.username} (${user.id})`)
return res.status(200).send(`${config.site.returnProtocol}://${req.headers['host']}/u/${id}.${extension}`)
if (config.discordWebhook.enabled) new DiscordWebhook(config.discordWebhook.url).sendImageUpdate(user, ImageUtil.parseURL(img.url), config);
return res.status(200).send(`${config.site.returnProtocol}://${req.headers['host']}${config.upload.route}/${id}.${extension}`)
}
@Post('shorten')
private async shorten(req: Request, res: Response) {
if (req.headers['authorization'] === config.administrator.authorization) return res.status(BAD_REQUEST).json({ code: BAD_REQUEST, message: "You can't upload files with the administrator account." })
const users = await this.orm.repos.user.find({ where: { token: req.headers['authorization'] } });
if (!users[0]) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: "Unauthorized" })
if (req.headers['authorization'] !== users[0].token) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: "Unauthorized" })
const user = users[0];
const id = randomId(config.shorten.idLength)
const shrt = await getShorten(this.orm, id, req.body.url, `${config.site.returnProtocol}://${req.headers['host']}${config.shorten.route}/${id}`, user.id);
Logger.get('TypeX.Shortener').info(`New url shortened ${shrt.url} (${req.body.url}) (${shrt.id}) by ${user.username} (${user.id})`)
if (config.discordWebhook.enabled) new DiscordWebhook(config.discordWebhook.url).sendShortenUpdate(user, shrt, ShortenUtil.parseURL(shrt.url), config);
return res.status(200).send(`${config.site.returnProtocol}://${req.headers['host']}${config.shorten.route}/${id}`)
}
@Post('user')

View file

@ -6,6 +6,8 @@ import { readFileSync } from 'fs'
import multer from 'multer';
import { cookies } from '../middleware/cookies';
import Logger from '@ayanaware/logger';
import { ShortenUtil } from '../structures/ShortenUtil';
import { ImageUtil } from '../structures/ImageUtil';
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
@ -61,7 +63,14 @@ export class IndexController {
if (req.body.password !== user.password) return res.status(200).render('login', { failed: true, config })
req.session.user = user;
res.cookie('typex_user', req.session.user.id, { maxAge: 1036800000 });
return res.redirect('/')
return res.redirect('/');
}
@Get(`${config.shorten.route.slice(1)}/:id`)
private async getShorten(req: Request, res: Response) {
const shorten = await this.orm.repos.shorten.findOne({ key: req.params.id });
if (!shorten) return res.render('404');
return res.redirect(shorten.origin)
}
public set(orm: ORMHandler) {

View file

@ -11,9 +11,13 @@ export class Image {
@Column("bigint")
user: number;
@Column("bigint", { default: 0 })
views: number;
set(options: { url: string, user: number }) {
this.url = options.url;
this.user = options.user;
this.views = 0;
return this;
}
}

31
src/entities/Shorten.ts Normal file
View file

@ -0,0 +1,31 @@
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Shorten {
@PrimaryGeneratedColumn({ type: 'bigint' })
id: number;
@Column("text")
origin: string;
@Column("text")
url: string;
@Column("text", { default: "" })
key: string;
@Column("bigint")
user: number;
@Column("bigint", { default: 0 })
views: number
set(options: { key: string, origin: string, url: string, user: number }) {
this.key = options.key;
this.origin = options.origin;
this.url = options.url;
this.user = options.user;
this.views = 0;
return this;
}
}

View file

@ -3,7 +3,7 @@ import {
Repository,
Connection,
createConnection,
ConnectionOptions,
ConnectionOptions
} from "typeorm";
import { User } from "./entities/User";
import { TypeXServer } from "./server";
@ -11,6 +11,7 @@ import Logger from "@ayanaware/logger";
import { Image } from "./entities/Image";
import { findFile } from "./util";
import { readFileSync } from 'fs';
import { Shorten } from "./entities/Shorten";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
@ -22,13 +23,12 @@ const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), '
if (!config.upload?.route) {
Logger.get('TypeX.Config').error(`Missing needed property on configuration: upload.route`)
process.exit(1);
} else if (!config.forever?.route) {
Logger.get('TypeX.Config').error(`Missing needed property on configuration: forever.route`)
}
export interface ORMRepos {
user?: Repository<User>;
image?: Repository<Image>;
shorten?: Repository<Shorten>;
}
export interface ORMHandler {
@ -36,6 +36,10 @@ export interface ORMHandler {
connection: Connection;
}
const pk = JSON.parse(readFileSync(findFile('package.json', process.cwd()), 'utf8'));
Logger.get('TypeX').info(`Starting TypeX ${pk.version}`);
(async () => {
const connection = await createConnection(config.orm as ConnectionOptions);
const orm: ORMHandler = {
@ -43,6 +47,7 @@ export interface ORMHandler {
repos: {
user: connection.getRepository(User),
image: connection.getRepository(Image),
shorten: connection.getRepository(Shorten)
},
};
if (orm.connection.isConnected)

View file

@ -32,12 +32,23 @@ export class TypeXServer extends Server {
saveUninitialized: false,
})
);
this.app.use(async (req, res, next) => {
console.log(req.url, req.baseUrl, req.originalUrl)
if (!req.url.startsWith(config.upload.route)) return next();
console.log(`${config.site.returnProtocol}://${req.headers['host']}${req.url}`)
const upload = await orm.repos.image.findOne({ url: `${config.site.returnProtocol}://${req.headers['host']}${req.url}` });
console.log(upload);
if (!upload) return next();
upload.views++;
console.log(upload.views);
orm.repos.image.save(upload);
return next();
})
this.app.use(cookies());
try {
this.app.use(config.upload.route, express.static(config.upload.uploadDir));
this.app.use(config.forever.route, express.static(config.forever.directory));
} catch (e) {
Logger.get('TypeX.Routes').error(`Could not formulate upload and forever static routes`)
Logger.get('TypeX.Routes').error(`Could not formulate upload static route`)
process.exit(1);
}
this.app.use("/public", express.static("public"));
@ -46,13 +57,13 @@ export class TypeXServer extends Server {
this.app.use(async (req, res, next) => {
if (!config.site.logRoutes) return next();
if (req.url.startsWith(config.upload.route)) return next();
if (req.url.startsWith(config.forever.route)) return next();
let user = req.session.user;
const users = await orm.repos.user.find({ where: { token: req.headers['authorization'] } });
if (users[0]) user = users[0]
Logger.get('TypeX.Route').info(`Route ${req.url} was accessed by ${user ? user.username : '<no user found>'}`)
return next();
})
});
this.setupControllers(orm);
}

View file

@ -0,0 +1,54 @@
import fetch from 'node-fetch';
import { User } from '../entities/User';
import { Shorten } from '../entities/Shorten';
import { Imaged } from './ImageUtil';
import { Shortened } from './ShortenUtil';
export class DiscordWebhook {
public url: string;
constructor(url: string) {
this.url = url;
this.checkExists();
}
async checkExists(): Promise<boolean> {
const json = await (await fetch(this.url)).json();
if (json.code === 10015) throw new Error('Unknown Webhook')
else if (json.code === 50027) throw new Error('Invalid Webhook Token')
else if (json.code) throw new Error(`DiscordAPIError[${json.code}]: ${json.message}`);
return json.code ? false : true;
}
async sendImageUpdate(user: User, image: Imaged, config: any) {
try {
await (await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
user: config.discordWebhook.username,
avatar_url: config.discordWebhook.avatarURL,
content: `New image uploaded to ${image.origin} by ${user.username} (${user.id}). [View Image](${image.url})`
})
}));
} catch (e) {
throw new Error(`Coulndn't send webhook: ${e.message}`)
}
}
async sendShortenUpdate(user: User, shorten: Shorten, ex: Shortened, config: any) {
try {
await (await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
user: config.discordWebhook.username,
avatar_url: config.discordWebhook.avatarURL,
content: `New shortened url added to ${ex.origin} by ${user.username} (${user.id}). ${shorten.origin} -> ${shorten.url}`
})
}));
} catch (e) {
throw new Error(`Coulndn't send webhook: ${e.message}`)
}
}
}

View file

@ -1,4 +1,14 @@
import { getType } from 'mime';
import { findFile } from '../util';
import Logger from '@ayanaware/logger';
import { readFileSync } from 'fs';
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
export interface Imaged {
url: string;
@ -16,9 +26,9 @@ export class ImageUtil {
url: parsed.href,
origin: parsed.origin,
protocol: parsed.protocol.slice(0, -1),
key: parsed.pathname.startsWith('/u/') ? parsed.pathname.slice(3).split('.')[0] : null,
extension: parsed.pathname.startsWith('/u/') ? parsed.pathname.slice(3).split('.')[1] : null,
mime: parsed.pathname.startsWith('/u/') ? getType(parsed.pathname.slice(3).split('.')[1]) : null
key: parsed.pathname.startsWith(config.upload.route) ? parsed.pathname.slice(3).split('.')[0] : null,
extension: parsed.pathname.startsWith(config.upload.route) ? parsed.pathname.slice(3).split('.')[1] : null,
mime: parsed.pathname.startsWith(config.upload.route) ? getType(parsed.pathname.slice(3).split('.')[1]) : null
}
}
}

View file

@ -0,0 +1,34 @@
import { getType } from 'mime';
import { findFile } from '../util';
import Logger from '@ayanaware/logger';
import { readFileSync } from 'fs';
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
export interface Shortened {
url: string;
origin: string;
protocol: string;
key: string;
extension: string;
mime: string;
}
export class ShortenUtil {
static parseURL(url: string): Shortened {
const parsed = new URL(url);
return {
url: parsed.href,
origin: parsed.origin,
protocol: parsed.protocol.slice(0, -1),
key: parsed.pathname.startsWith(config.shorten.route) ? parsed.pathname.slice(3).split('.')[0] : null,
extension: parsed.pathname.startsWith(config.shorten.route) ? parsed.pathname.slice(3).split('.')[1] : null,
mime: parsed.pathname.startsWith(config.shorten.route) ? getType(parsed.pathname.slice(3).split('.')[1]) : null
}
}
}

View file

@ -4,6 +4,7 @@ import { User } from './entities/User';
import { Image } from './entities/Image';
import { statSync, readdirSync } from 'fs';
import { join, basename } from 'path';
import { Shorten } from './entities/Shorten';
export function randomId(length) {
@ -38,6 +39,12 @@ export async function getImage(orm: ORMHandler, url: string, user: number) {
return image;
}
export async function getShorten(orm: ORMHandler, key: string, origin: string, url: string, user: number) {
const image = await orm.repos.shorten.findOne({ key });
if (!image) return orm.repos.shorten.save(new Shorten().set({ key, origin, url, user }));
return image;
}
export function findFile(file, directory) {
const result = [];
(function read(dir) {