Initial commit
This commit is contained in:
@@ -1,2 +1,30 @@
|
||||
# url-shortener-public
|
||||
# URL Shortener
|
||||
|
||||
This is a small project i worked on out of boredom.
|
||||
Its not meant to be perfect, just an idea i had and wanted to work on to kill some time.
|
||||
Its an url shortener that is meant to allow people to easily create shorted urls, even automate this task if need be.
|
||||
|
||||
|
||||
## Setup
|
||||
Run `git clone https://git.dommymommy.xyz/emily/url-shortener.git`
|
||||
|
||||
Next run `cd url-shortener` and then run `npm i`
|
||||
|
||||
Run the sql file on your database server and add the credentials to `./config/main.json`
|
||||
|
||||
Now you can run `node main` or `npm start` to start up the service
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
|
||||
## API
|
||||
### POST
|
||||
>/api/v1/createKey
|
||||
Get Info about a shortcode
|
||||
|
||||
>
|
||||
### GET
|
||||
>/api/v1/info/\<code>
|
||||
Get Info about a shortcode
|
||||
### DELETE
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
{
|
||||
"baseUrl":[
|
||||
"https://shorten.dommymommy.xyz",
|
||||
"https://shorten.gofuckaduck.eu",
|
||||
"https://shorten.kitsunes.eu"
|
||||
],
|
||||
"database":{
|
||||
"host": "the ip of your database",
|
||||
"port": "the port",
|
||||
"username": "the username",
|
||||
"password": "the password",
|
||||
"database": "url_shortener"
|
||||
},
|
||||
"ratelimit":{
|
||||
"enabled":true,
|
||||
"hitsUntilBan":5,
|
||||
"resetHits":true,
|
||||
"resetTimeframe":5,
|
||||
"devKey":"a key used in the header to bypass ratelimiting"
|
||||
},
|
||||
"banhandler":{
|
||||
"whitelisted":[
|
||||
"ips you want to whitelist and prevent from being banned",
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
module.exports = {
|
||||
name:"authenticate",
|
||||
async function(req, res){
|
||||
let username = req.body.username.trim()
|
||||
let password = req.body.password
|
||||
|
||||
if(username == undefined || password == undefined || username == ""){
|
||||
res.json({code:32})
|
||||
return
|
||||
}
|
||||
|
||||
if(username.length > 32 || username.length < 3){
|
||||
res.json({code:32})
|
||||
return
|
||||
}
|
||||
|
||||
let user = await commands.get("query").function("SELECT dtPassword from tbl_users WHERE dtUsername = ?",[username])
|
||||
if(user.length == 0){
|
||||
res.json({code:32})
|
||||
return
|
||||
}
|
||||
let valid = await bcrypt.compare(password, user[0].dtPassword)
|
||||
if(!valid){
|
||||
res.json({code:31})
|
||||
return
|
||||
}
|
||||
|
||||
// Yes, i am using bcrypt to make the hash key cause i m too lazy to implement md5 here
|
||||
// I mean, afterall, it doesnt contain sensitive information, so, who cares what algorithm you use
|
||||
// it looks fancy and does its job, so, cry about it
|
||||
let apiKey = await bcrypt.hash(Math.random() * 100000000 * Math.random() + username,saltRounds)
|
||||
commands.get("query").function("UPDATE tbl_users SET dtApiKey = ? WHERE dtUsername = ?",[apiKey, username]).then(resp => {
|
||||
res.json({code:30,key:apiKey})
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
res.json({code:33})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
module.exports = {
|
||||
name:"codeList",
|
||||
async function(req, res){
|
||||
let apiKey = req.headers.auth
|
||||
|
||||
if(apiKey == undefined){
|
||||
res.json({code:51})
|
||||
return
|
||||
}
|
||||
var userid
|
||||
await commands.get("validate").check(apiKey).then(resp => {
|
||||
userid = resp
|
||||
}).catch(err => {
|
||||
switch(err){
|
||||
case 1: res.json({code:51}); break;
|
||||
case 2: res.json({code:52}); break;
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
let output = {
|
||||
code:50,
|
||||
shortcodes:[]
|
||||
}
|
||||
|
||||
let codes = await commands.get("query").function("SELECT idShortcode, dtOriginal, tbl_urls.dtTimestamp from tbl_urls, tbl_users where fiUser = idUser AND dtApiKey = ?",[apiKey])
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
output.shortcodes.push({
|
||||
"shortcode": codes[i].idShortcode,
|
||||
"destination": codes[i].dtOriginal,
|
||||
"timestamp": codes[i].dtTimestamp
|
||||
})
|
||||
}
|
||||
res.json(output)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
module.exports = {
|
||||
name:"createCode",
|
||||
async function(req, res){
|
||||
let apiKey = req.headers.auth
|
||||
let url = req.body.url
|
||||
|
||||
if(apiKey == undefined){
|
||||
res.json({code:41})
|
||||
return
|
||||
}
|
||||
var userid
|
||||
await commands.get("validate").check(apiKey).then(resp => {
|
||||
userid = resp
|
||||
}).catch(err => {
|
||||
switch(err){
|
||||
case 1: res.json({code:41}); break;
|
||||
case 2: res.json({code:42}); break;
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
let dupes = await commands.get("query").function("SELECT idShortcode FROM tbl_urls, tbl_users WHERE fiUser = idUser AND dtApiKey = ? AND dtOriginal = ?",[apiKey,url])
|
||||
if(dupes.length != 0){
|
||||
let urls = []
|
||||
config.baseUrl.forEach(el => {
|
||||
urls.push(`${el}/${dupes[0].idShortcode}`)
|
||||
});
|
||||
res.json({code:44,shortcode:dupes[0].idShortcode,full_url:urls})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let hashData = new Date().getTime()+""
|
||||
hashData = parseInt(hashData.slice(6)) + Math.floor((Math.random()*1000)-500)
|
||||
let hash = hashids.encode(hashData)
|
||||
// console.log(a);
|
||||
|
||||
commands.get("query").function("INSERT INTO tbl_urls (idShortcode, dtOriginal, dtTimestamp, fiUser) VALUES (?,?,?,?)",[hash, url, new Date().getTime(), userid]).then(resp => {
|
||||
let urls = []
|
||||
config.baseUrl.forEach(el => {
|
||||
urls.push(`${el}/${hash}`)
|
||||
});
|
||||
res.json({code:40,shortcode:hash,full_url:urls})
|
||||
}).catch(err => {
|
||||
res.json({code:43})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
module.exports = {
|
||||
name:"createUser",
|
||||
async function(req, res){
|
||||
let username = req.body.username.trim()
|
||||
let password = req.body.password
|
||||
|
||||
if(username == undefined || passowrd == undefined || username == ""){
|
||||
res.json({code:24})
|
||||
return
|
||||
}
|
||||
|
||||
if(username.length > 32 || username.length < 3){
|
||||
res.json({code:23})
|
||||
return
|
||||
}
|
||||
|
||||
let user = await commands.get("query").function("SELECT idUser from tbl_users WHERE dtUsername = ?",[username])
|
||||
if(user.length != 0){
|
||||
res.json({code:21})
|
||||
return
|
||||
}
|
||||
let hash = await bcrypt.hash(password, saltRounds)
|
||||
commands.get("query").function("INSERT INTO tbl_users (dtUsername,dtPassword,dtTimestamp) VALUES (?,?,?)",[username,hash,new Date().getTime()],true).then(res => {
|
||||
console.log(res);
|
||||
if(res != false){
|
||||
res.json({code:20})
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
res.json({code:22,error:err})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
module.exports = {
|
||||
name:"deleteCode",
|
||||
async function(req, res){
|
||||
let apiKey = req.headers.auth
|
||||
let shortcode = req.body.shortcode
|
||||
|
||||
if(apiKey == undefined){
|
||||
res.json({code:61})
|
||||
return
|
||||
}
|
||||
var userid
|
||||
await commands.get("validate").check(apiKey).then(resp => {
|
||||
userid = resp
|
||||
}).catch(err => {
|
||||
switch(err){
|
||||
case 1: res.json({code:61}); break;
|
||||
case 2: res.json({code:62}); break;
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
let code = await commands.get("query").function("SELECT idShortcode FROM tbl_urls, tbl_users WHERE fiUser = idUser AND dtApiKey = ? AND idShortcode = ?",[apiKey,shortcode])
|
||||
if(code.length == 0){
|
||||
res.json({code:65})
|
||||
return
|
||||
}
|
||||
|
||||
commands.get("query").function("DELETE FROM tbl_urls WHERE idShortcode = ?",[shortcode]).then(resp => {
|
||||
res.json({code:60})
|
||||
}).catch(err => {
|
||||
res.json({code:63})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
module.exports = {
|
||||
name:"info",
|
||||
async function(req, res){
|
||||
let query = await commands.get("query").function("SELECT * from tbl_urls, tbl_users WHERE idShortcode = ? AND fiUser = idUser",[req.params.shortcode])
|
||||
if(query.length == 0){
|
||||
res.json({code:11})
|
||||
return
|
||||
}
|
||||
|
||||
let query2 = await commands.get("query").function("SELECT COUNT(*) AS amount FROM tbl_access WHERE fiShortcode = ?",[req.params.shortcode])
|
||||
let query3 = await commands.get("query").function("SELECT COUNT(*) AS amount FROM (SELECT * FROM tbl_access WHERE fiShortcode = ? GROUP BY dtIpHash) AS sq",[req.params.shortcode])
|
||||
res.json({
|
||||
code:10,
|
||||
shortcode: req.params.shortcode,
|
||||
redirect: query[0].dtOriginal,
|
||||
creator: query[0].dtUsername,
|
||||
creationTime: query[0].dtTimestamp,
|
||||
clicked:{
|
||||
total:query2[0].amount,
|
||||
distinct_users:query3[0].amount
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
const crypto = require('crypto');
|
||||
module.exports = {
|
||||
name:"redirect",
|
||||
async function(req, res){
|
||||
if(req.params.shortcode == "")
|
||||
return
|
||||
if(shortcodes[req.params.shortcode]){
|
||||
res.redirect(shortcodes[req.params.shortcode])
|
||||
}else{
|
||||
let url = await commands.get("query").function("SELECT dtOriginal from tbl_urls WHERE idShortcode = ?",[req.params.shortcode])
|
||||
if(url.length == 0)
|
||||
return
|
||||
shortcodes[req.params.shortcode] = url[0].dtOriginal
|
||||
res.redirect(shortcodes[req.params.shortcode])
|
||||
}
|
||||
|
||||
// commands.get("query").function("UPDATE tbl_urls SET dtAccessed = dtAccessed + 1 WHERE idShortcode = ?",[req.params.shortcode])
|
||||
let ipHash = req.headers["cf-connecting-ip"] == undefined ? "localhost" : crypto.createHash('md5').update(req.headers["cf-connecting-ip"]).digest('hex')
|
||||
commands.get("query").function("INSERT INTO tbl_access (fiShortcode,dtIpHash,dtTimestamp) VALUES (?,?,?)",[req.params.shortcode, ipHash, new Date().getTime()])
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
module.exports = {
|
||||
name:"Startup_function",
|
||||
async function(msg, args){
|
||||
setTimeout(async () => {
|
||||
|
||||
console.log("-------------------------------- Startup Function ------------------------------");
|
||||
|
||||
// Load all shortcodes with their redirects directly into memeory on startup
|
||||
process.stdout.write("Loading all shortcodes into memory 🟥");
|
||||
let url = await commands.get("query").function("SELECT idShortcode, dtOriginal from tbl_urls",[])
|
||||
for (let i = 0; i < url.length; i++) {
|
||||
shortcodes[url[i].idShortcode] = url[i].dtOriginal
|
||||
}
|
||||
process.stdout.cursorTo(40)
|
||||
process.stdout.write(`🟩 (${url.length})\n`)
|
||||
|
||||
|
||||
// Load all bans into memory
|
||||
process.stdout.write("Loading all bans into memory 🟥");
|
||||
let bans = await commands.get("query").function("SELECT dtIpHash from tbl_bans",[])
|
||||
for (let i = 0; i < bans.length; i++) {
|
||||
ignorelist[bans[i].dtIpHash] = bans[i].dtIpHash
|
||||
}
|
||||
process.stdout.cursorTo(40)
|
||||
process.stdout.write(`🟩 (${bans.length})\n`)
|
||||
|
||||
|
||||
// Ratelimit hit cleanup
|
||||
if(config.ratelimit.resetHits){
|
||||
setInterval(() => {
|
||||
for(hash in ratelimit){
|
||||
if(ratelimit[hash][1] > 0)
|
||||
ratelimit[hash][1] = ratelimit[hash][1]-1
|
||||
}
|
||||
}, 60000* config.ratelimit.resetTimeframe); // Timeframe value in minutes
|
||||
}
|
||||
|
||||
console.log("--------------------------------------------------------------------------------");
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
const crypto = require('crypto');
|
||||
module.exports = {
|
||||
name:"banHandler",
|
||||
check: async function(req){
|
||||
return new Promise(async (resolve,reject) => {
|
||||
let hash = req.headers["cf-connecting-ip"] == undefined ? crypto.createHash('md5').update("localhost").digest('hex') : crypto.createHash('md5').update(req.headers["cf-connecting-ip"]).digest('hex')
|
||||
let ban = await commands.get("query").function("SELECT idBan from tbl_bans WHERE dtIpHash = ?",[hash])
|
||||
if(ban.length == 0){
|
||||
resolve()
|
||||
return
|
||||
}else{
|
||||
reject()
|
||||
return
|
||||
}
|
||||
})
|
||||
},
|
||||
ban: async function(req){
|
||||
if(req.headers["cf-connecting-ip"] != undefined){
|
||||
if(config.banhandler.whitelisted.includes(req.headers["cf-connecting-ip"]))
|
||||
return
|
||||
}
|
||||
let hash = req.headers["cf-connecting-ip"] == undefined ? crypto.createHash('md5').update("localhost").digest('hex') : crypto.createHash('md5').update(req.headers["cf-connecting-ip"]).digest('hex')
|
||||
await commands.get("query").function("INSERT INTO tbl_bans (dtIpHash,dtTimestamp) VALUES (?,?)",[hash, new Date().getTime()])
|
||||
ignorelist[hash] = hash
|
||||
},
|
||||
unban: async function(hash){
|
||||
await commands.get("query").function("DELETE FROM tbl_bans WHERE dtIpHash = ?",[hash])
|
||||
delete ignorelist[hash]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
module.exports = {
|
||||
name:"log",
|
||||
async function(message,status){
|
||||
switch(status){
|
||||
case "good":
|
||||
console.log(`${new Date()} : ${message}`.green)
|
||||
break;
|
||||
case "warn":
|
||||
console.log(`${new Date()} : ${message}`.yellow)
|
||||
break;
|
||||
case "bad":
|
||||
console.log(`${new Date()} : ${message}`.red)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// const included = require("../../requires")
|
||||
pool = mysql.createPool({
|
||||
connectionLimit : 10,
|
||||
host: config.database.host,
|
||||
port: config.database.port,
|
||||
user: config.database.username,
|
||||
password: config.database.password,
|
||||
database: config.database.database,
|
||||
charset : 'utf8mb4'
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
name: "query",
|
||||
async function(query, values, verbose){
|
||||
// if(values == undefined){
|
||||
// values = query
|
||||
// query = msg
|
||||
// }
|
||||
return new Promise((resolve, reject) => {
|
||||
// connection.query(query, values, function (error, results, fields) {
|
||||
// if (error) throw error;
|
||||
// resolve(results)
|
||||
// });
|
||||
|
||||
pool.getConnection(function(err, connection) {
|
||||
if (err) throw err; // not connected!
|
||||
// Use the connection
|
||||
connection.query(query, values, function (error, results, fields) {
|
||||
// When done with the connection, release it.
|
||||
connection.release();
|
||||
// Handle error after the release.
|
||||
if (error){
|
||||
// console.log(error) //-------------------------------------------------------------------------- DEBUGGING
|
||||
if(verbose){
|
||||
reject(error.sqlMessage);
|
||||
}
|
||||
resolve(false)
|
||||
}
|
||||
resolve(results)
|
||||
// Don't use the connection here, it has been returned to the pool.
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
const crypto = require('crypto');
|
||||
module.exports = {
|
||||
name:"ratelimit",
|
||||
async function(req){
|
||||
return new Promise(async (resolve,reject) => {
|
||||
// If developer key has been sent, skip ratelimiter
|
||||
if(req.headers.devkey){if(req.headers.devkey == config.ratelimit.devKey){resolve();return}}
|
||||
// If user is banned already, reject with code 3 (ignore)
|
||||
let bh = false
|
||||
await commands.get("banHandler").check(req).catch(err => {reject(3);bh=true})
|
||||
if(bh){return}
|
||||
|
||||
if(!config.ratelimit.enabled){
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
let hash = req.headers["cf-connecting-ip"] == undefined ? crypto.createHash('md5').update("localhost").digest('hex') : crypto.createHash('md5').update(req.headers["cf-connecting-ip"]).digest('hex')
|
||||
if(ratelimit[hash] == undefined){
|
||||
// Everything is fine
|
||||
ratelimit[hash] = [new Date().getTime()+1000,0]
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
else{
|
||||
if(ratelimit[hash][0] > new Date().getTime()){
|
||||
ratelimit[hash][1] = ratelimit[hash][1] + 1
|
||||
if(ratelimit[hash][1] >= config.ratelimit.hitsUntilBan){
|
||||
// User gets banned now, reject with code 2 (tell user about the ban)
|
||||
commands.get("banHandler").ban(req)
|
||||
reject(2)
|
||||
console.log(`${new Date()}: Banning ${hash}`);
|
||||
return
|
||||
}
|
||||
// User hits ratelimit, reject with code 1 (tell user about the hit)
|
||||
reject(1)
|
||||
// console.log(`Rate Limiting ${hash}`);
|
||||
return
|
||||
}else{
|
||||
// Everything is fine
|
||||
ratelimit[hash][0] = new Date().getTime()+1000
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
name:"readableTime",
|
||||
async timeframe(seconds){
|
||||
return new Promise(resolve => {
|
||||
seconds = Math.floor(Number(seconds)/1000);
|
||||
var d = Math.floor(seconds / (3600*24));
|
||||
var h = Math.floor(seconds % (3600*24) / 3600);
|
||||
var m = Math.floor(seconds % 3600 / 60);
|
||||
var s = Math.floor(seconds % 60);
|
||||
|
||||
var dDisplay = d > 0 ? d + (d == 1 ? " day, " : " days, ") : "";
|
||||
var hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : "";
|
||||
var mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes, ") : "";
|
||||
var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
|
||||
resolve(dDisplay + hDisplay + mDisplay + sDisplay);
|
||||
})
|
||||
},
|
||||
async timestamp(timestamp=new Date()){
|
||||
return new Promise(resolve => {
|
||||
let vals = {
|
||||
yyyy: timestamp.getFullYear(),
|
||||
m: timestamp.getMonth()+1,
|
||||
d: timestamp.getDate(),
|
||||
h: timestamp.getHours(),
|
||||
mi: timestamp.getMinutes(),
|
||||
s: timestamp.getSeconds(),
|
||||
ms: timestamp.getMilliseconds()
|
||||
};
|
||||
|
||||
// console.log(`${vals.yyyy}/${vals.m}/${vals.d} ${vals.h}:${vals.mi}:${vals.s}`);
|
||||
resolve(`${vals.yyyy}/${vals.m}/${vals.d} ${vals.h}:${vals.mi}:${vals.s}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// require("../../requires")
|
||||
|
||||
module.exports = {
|
||||
name: "registerCommands",
|
||||
async function(global, guild){
|
||||
return new Promise(resolve => {
|
||||
const { SlashCommandBuilder } = require('@discordjs/builders');
|
||||
const { REST } = require('@discordjs/rest');
|
||||
const { Routes } = require('discord-api-types/v9');
|
||||
|
||||
const token = config.token
|
||||
|
||||
const commandsList = []
|
||||
|
||||
|
||||
var commandstopush = "| "
|
||||
commands.forEach(element => {
|
||||
if(element.slashcommand != undefined){
|
||||
|
||||
if(global){
|
||||
if(element.slashcommandGlobal){
|
||||
commandstopush += `${element.name} | `
|
||||
commandsList.push(element.slashcommand)
|
||||
}
|
||||
}else{
|
||||
commandstopush += `${element.name} | `
|
||||
commandsList.push(element.slashcommand)
|
||||
if(element.permissions){
|
||||
commandsList[commandsList.length-1].default_member_permissions = element.permissions
|
||||
}
|
||||
}
|
||||
// commandsList.push(element.slashcommand)
|
||||
}
|
||||
});
|
||||
console.log(commandstopush);
|
||||
|
||||
const rest = new REST({ version: '9' }).setToken(token);
|
||||
|
||||
const clientId = client.user.id;
|
||||
const guildId = '929749429156724807';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
if(!global){
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(clientId, guildId),
|
||||
{ body: commandsList },
|
||||
);
|
||||
}else{
|
||||
if(guild == undefined){
|
||||
await rest.put(
|
||||
Routes.applicationCommands(clientId),
|
||||
{ body: commandsList },
|
||||
);
|
||||
}else{
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(clientId, guild.id),
|
||||
{ body: commandsList },
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log('Successfully registered application commands.');
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
resolve()
|
||||
}
|
||||
})();
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const included = require("../../requires")
|
||||
|
||||
// Here you can add functions that will be run at each startup of the bot
|
||||
|
||||
module.exports = {
|
||||
name:"reloadCommands",
|
||||
async function(msg, args){
|
||||
commands.clear()
|
||||
var path = `${__dirname}/../../functions`
|
||||
var folders = fs.readdirSync(path).filter(function (file) {
|
||||
return fs.statSync(path+'/'+file).isDirectory();
|
||||
});
|
||||
folders.forEach(element => {
|
||||
var commandFiles = fs.readdirSync(`${path}/${element}`).filter(file => file.endsWith('.js') && !file.startsWith("index"));
|
||||
for (const file of commandFiles) {
|
||||
delete require.cache[require.resolve(`${path}/${element}/${file}`)];
|
||||
try {
|
||||
const command = require(`${path}/${element}/${file}`);
|
||||
commands.set(command.name, command);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
// commands.get("garbage_collector").function() // reset garbage collector
|
||||
console.log(`Reloading ${commands.size} modules done`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
module.exports = {
|
||||
name:"validate",
|
||||
check: async function(apiKey){
|
||||
return new Promise(async (resolve,reject) => {
|
||||
if(apiKey == undefined){
|
||||
reject(1)
|
||||
}
|
||||
|
||||
let user = await commands.get("query").function("SELECT idUser from tbl_users WHERE dtApiKey = ?",[apiKey])
|
||||
if(user.length == 1){
|
||||
resolve(user[0].idUser)
|
||||
}else{
|
||||
// resolve(false)
|
||||
reject(2)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
⣷⠄⠄⠘⡄⠘⡀⠹⣿⠛⢿⣏⠈⢿⠈⠻⣧⠈⢿⠋⠉⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠄⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠁⠄⢀⣾⣿⣿⣿⣿⣿⣿⡟⠁⣼⡇⠄⣿⣿⠄⢸⠇⢠⠇⢰⠁⠄⠄⣿⡿⠄⣿⣿⡿⠄⠄⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⡀⠄⠄⢳⡀⠐⡀⠹⣧⠈⢿⣇⠈⢧⠄⠹⣧⠄⠄⠄⠄⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠄⠄⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠁⠄⠄⣠⣶⣿⣿⣿⣿⣿⣿⣿⡟⠄⠐⣿⡇⠄⣿⡏⠄⣼⠄⢸⠄⢸⠄⠄⢰⣿⡇⠄⠃⢸⡇⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣇⠄⠄⠈⣧⠄⢱⠄⢻⡆⠈⣿⣆⠈⢧⠄⢹⣧⠄⠄⠄⠄⠄⠙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠄⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠄⠄⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⠟⠄⣰⠄⢸⡇⢸⣿⡇⠄⣿⠄⡀⠄⡄⠄⠄⢸⣿⠁⠄⠄⣿⡇⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⠄⠄⠄⢻⣇⠄⢧⠄⢻⡀⠸⣿⡆⠈⣧⠄⢻⣧⠄⠄⠘⣦⠄⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠄⠄⠙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠄⠄⣠⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠄⣰⣿⡇⢸⡇⢸⣿⠄⠄⡇⠄⡇⠄⡇⠄⠄⢸⡿⠄⠄⢠⣿⠄⠄⠄⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣇⠄⠄⠈⢿⡄⠘⣇⠄⢧⠄⢿⣿⡄⠘⣧⠄⢿⣷⣶⡀⠸⣧⡀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠄⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠁⠄⠄⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠄⠄⢻⣿⠄⢸⡇⢸⣿⡆⢸⡇⢀⡇⠄⠁⠄⠄⢸⡇⠄⠄⢸⣿⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⡀⠄⠄⠄⢳⠄⢹⣆⠈⣧⣸⡿⠟⠄⠉⠁⠄⣉⣿⣿⣤⣿⣷⡀⠄⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠄⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⠄⠄⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠄⣰⡀⠈⠟⠁⢀⣀⣀⣀⣀⣀⠄⠈⠃⠄⠄⠄⠄⢸⠃⠄⠄⣿⡟⠄⠄⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⡇⠄⠄⠄⠄⢣⠄⢻⡄⠈⠁⠄⠠⠶⢶⣾⣿⣿⣿⣿⣿⣿⣿⣿⡄⠄⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣀⠄⠹⠿⠿⠿⠛⠛⠛⠛⠛⠛⠿⢿⣿⣿⡿⠁⠄⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠄⣰⣿⣿⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⠄⢰⠄⢸⠄⠄⠄⣿⡇⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⠄⠄⠄⢳⠄⢂⠈⣷⣄⣤⣤⠄⠄⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣀⠄⠄⠄⠄⢀⣀⣀⡀⠄⠄⠄⠈⠉⠄⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠄⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⣄⡀⠘⠄⢸⠄⡇⠄⣿⠇⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⡆⠄⠄⠘⣧⠈⢦⡘⣿⡿⠃⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣾⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣤⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠄⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠛⠒⠄⣿⣿⡇⠄⣿⠄⠄⠄⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⠄⠄⠄⠻⣧⠈⣿⡿⠁⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠄⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠹⣿⡇⢹⡇⢸⡟⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⡇⠄⠄⠄⠹⣆⠘⠃⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠄⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠘⡇⢸⡇⢸⡇⠄⠄⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⠄⠄⠄⠄⢻⣆⠄⠘⠋⠉⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣄⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠛⠛⠛⣻⣿⣿⣿⣿⣿⣿⡿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠄⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠄⠄⢸⡇⠈⠁⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣇⠄⠄⠰⡀⢻⣦⠄⠄⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠈⣿⣿⣿⣿⣿⣿⡿⠟⠛⠉⢀⣴⣶⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⢀⣤⣾⣿⣿⣿⣿⡿⠿⢿⠿⠻⠿⠿⠿⠿⣿⣿⣿⣿⡿⠄⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠄⢸⡇⠄⠄⠄⠄⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⡀⠄⠄⣷⣤⣿⠄⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⠿⠛⠋⠁⠄⢀⣠⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠄⠄⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⠄⠄⢄⡀⠄⢤⣀⣉⡙⠛⠁⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠋⠉⠙⡇⠄⡇⠄⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣧⠄⠄⢸⣿⡇⠄⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠇⠄⠋⠁⠄⠄⢀⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢀⠄⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠙⠄⠄⠻⣿⣿⣷⣶⣿⣏⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢀⠄⢸⡇⠄⡇⠄⣇⠄⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⡄⠄⠄⣿⠁⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠁⠄⠄⣠⠔⠁⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⣾⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠄⠄⠘⢿⣿⣿⣿⣿⣿⣶⣌⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠄⣾⠄⢸⣷⠄⣿⠄⢿⠄⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣧⠄⠄⢹⠄⠘⣿⣿⣿⣿⣿⣿⣿⡿⠁⡀⠄⠄⣠⡾⠃⢠⣾⣿⣿⣿⣿⣿⣿⣿⡿⠛⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠄⠈⢿⣿⣿⣿⣿⣿⣿⣷⣄⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⢹⠄⣸⣿⠄⣿⠄⢸⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠄⠘⣷⠖⢿⣿⣿⣿⣿⣿⣿⠁⣼⠃⢀⣾⡟⠁⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⢰⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠄⠻⣿⣿⣿⣿⣿⣿⣿⣦⠈⠉⠙⠿⣿⣿⣿⡿⠁⠈⠉⠄⠄⣻⣿⣿⣿⣤⣿⠄⢸⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠄⠄⢿⣆⠈⢻⣿⣿⠏⡉⠁⠄⠃⢀⣾⠋⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢀⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠄⠙⢿⣿⣿⣿⣿⣿⣿⣧⢀⡀⠄⡈⠙⠋⠄⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣾⠄⠄⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠄⢸⣿⣆⠄⠛⠃⢀⣿⣿⠆⠄⡼⠁⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠄⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⠄⠸⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⡟⢻⣿⣿⣿⣿⣿⣿⣿⣿⣦⣠⣬⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠄⠘⣿⣿⣷⣶⣾⣿⣿⡟⠄⠄⠄⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⣿⣿⣿⡏⠄⣿⣿⣿⣇⠄⠁⠄⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⡇⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⠁⠄⠄⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⣼⣿⣿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣦⡀⠄⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣧⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠄⠄⢠⣿⣿⣿⣿⣿⣿⡇⠄⠄⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢠⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⣶⣿⣿⣿⣿⣿⣿⣿⠄⢿⣿⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠄⠄⣿⣿⣿⣿⣿⣿⣿⠄⠄⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⡆⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠄⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⠄⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠄⢀⣿⣿⣿⣿⣿⣿⣿⣀⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠄⢸⣿⣿⠟⢿⣿⣿⣿⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠛⣿⣿⣿⠄⢸⣿⣿⡇⠄⢿⣿⣿⣿⣿⣿⠙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠄⠄⠄⠄⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠄⠄⣿⣿⠃⠄⠄⢻⣿⣿⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⣿⣿⣿⠄⠄⢻⣿⡇⠄⠘⣿⣿⣿⣿⣿⡀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⠄⠄⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠄⠄⠄⣿⡟⠄⠄⡀⠈⣿⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢹⣿⣿⠄⠄⠈⣿⡇⠄⡄⠘⣿⣿⣿⣿⡇⠄⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⡀⠄⡄⠄⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠄⠄⡄⠄⣿⠃⠄⢸⣧⠄⠸⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⠄⠄⠄⢹⡇⠄⣿⡄⠘⣿⣿⣿⡇⠄⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⠄⠄⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠄⠄⣼⡇⢰⡏⠄⠄⣿⣿⡆⠄⢿⡇⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⠄⢸⡄⠈⠇⢀⣿⣷⠄⠸⣿⣿⣿⠄⣄⠄⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⡇⠄⠄⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⡏⠄⠄⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠄⠄⢰⣿⡇⠈⠁⠄⣸⣿⣿⣷⠄⠘⠁⠄⢻⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⠄⢸⣷⠄⠄⢸⣿⣿⣧⠄⠹⣿⣿⠄⢹⡀⠘⢿⣿⣿⣿⣿⣿⡟⠙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠄⠄⠄⣿⣿⡇⠄⠄⠄⣿⣿⣿⣿⡆⠄⠄⡄⠈⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⠄⢸⣿⣧⡀⢸⣿⣿⣿⣇⠄⢻⣿⡇⢸⣇⠄⠘⣿⣿⡿⠛⠁⡀⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⣿⣿⣿⣿⡇⠄⠉⠻⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⠄⠄⠄⢸⣿⣿⡇⠄⠄⢸⣿⣿⣿⣿⣿⠄⠄⣿⠄⢹⣿⣿⣿⣿⣿⣿⣿⡄⢸⣿⣿⠄⢸⣿⣿⣧⣸⣿⣿⣿⣿⣆⠄⢿⠇⠈⢻⡀⠄⠙⠁⣀⣴⣾⣧⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⣾⣿⣿⣿⣿⣿⣿⠁⢠⣦⣄⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣯⠄⠄⣸⣿⣿⣿⣿⣿⣿⣿⠋⢻⣿⣿⣿⣿⣿⣿⡿⠄⠄⠄⠘⢿⣿⡇⠄⠄⢸⣿⣿⣿⣿⣿⡇⣰⣿⡇⠸⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠄⠄⠠⠄⠁⠄⠄⢸⣿⣿⣿⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⣿⣿⣿⣿⣿⣿⣿⠄⠄⠈⠙⠃⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣗⠄⠄⣿⣿⣿⣿⣿⣿⣿⡏⠄⣸⣿⣿⣿⣿⣿⣿⠇⢠⣶⣶⣤⡀⠈⠃⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠘⣿⣿⣿⣿⣿⣿⡗⠈⣿⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠓⠄⣀⣤⠄⠄⠄⠄⢿⣿⣿⣿⡄⢸⣿⣿⣿⣿⣿⣿⣿⣿⡏⠄⣿⣿⣿⣿⣿⣿⡟⠄⠸⣷⠦⠄⠄⠄⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⡏⠄⣾⣿⣿⣿⣿⣷⣤⡀⠄⠙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣇⠄⠈⢿⣿⣿⣿⣿⣧⠄⣿⡟⠄⢸⣿⣿⣿⣿⣿⣿⠿⠋⠁⣠⣴⣾⣿⣿⣇⢰⣄⠄⠸⣿⣿⣿⣇⠄⢻⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⡇⠄⢠⣀⣠⡀⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⣿⣿⣿⣿⣿⣿⣿⠁⢠⣿⣿⣿⣿⣿⡿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⡀⣤⣀⠄⠙⢿⣿⣿⣿⣿⣿⣿⣦⠄⠘⣿⣿⣿⣿⣿⠄⢹⠃⠄⣼⣿⣿⣿⠛⠉⣀⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠄⢿⣿⣿⣿⡄⠈⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⣷⠄⠾⠟⠋⠁⠄⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠄⣿⣿⣿⣿⣿⣿⡟⠄⣸⣿⣿⣿⣿⣿⠇⠄⠛⠛⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣀⣽⣿⣿⣿⣿⣿⣿⣇⠄⠸⣿⣿⣿⣿⠄⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠄⠘⠿⠿⣿⣷⠄⢹⣿⣿⣿⣿⣿⡿⠃⢸⣿⣿⣿⣿⣿⣿⡿⠄⣤⣤⣶⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⢹⣿⣿⡿⢿⣿⠃⢀⣿⣿⣿⣿⣿⡿⠄⣀⠄⠄⠄⠄⠄⠉⠛⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠄⠙⣿⣿⣿⠄⢠⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠛⠉⠄⠄⠄⠄⠄⢀⡀⠈⣿⣿⣿⣿⣿⡇⠄⢸⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⣿⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⢸⣿⣿⡇⠈⣿⠄⢸⣿⣿⣿⣿⣿⡇⠄⣿⣿⣶⣤⣀⠄⠄⠄⠄⠄⠈⠙⠛⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠘⢿⣿⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠟⠛⠉⠄⠄⠄⠄⠄⠄⢀⡀⠄⠄⣶⣿⣷⠄⢻⣿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⣿⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⣿⣿⡇⠄⡏⠄⢸⣿⣿⣿⣿⣿⠁⢸⣿⣿⣿⣿⣿⣿⣷⣦⣤⣀⡀⠄⠄⠄⠄⠉⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠄⠙⠂⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⠄⠄⠄⠄⠄⠄⣀⣤⣶⣾⣿⣿⣷⡀⠄⢻⣿⣿⡄⠘⣿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⣿⠃⠄⣿⣿⣿⡇⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠄⠄⢻⣿⡇⠄⣿⠄⢸⣿⣿⣿⣿⡿⠄⣸⣿⣿⣿⣿⣿⣿⠿⠛⠉⠁⠄⠄⠄⠄⠄⢀⣀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⣈⣿⣿⣿⣿⣿⣿⣿⣿⣏⠄⠄⠄⠄⠄⠘⠻⣿⣿⣿⣿⣿⣿⣿⣷⠄⠘⣿⣿⣧⠄⢿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⣿⣿⠄⢰⣿⣿⣿⡇⠄⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠄⢸⣿⡇⠄⢸⠄⢸⣿⣿⣿⣿⡇⠄⣿⣿⡿⠟⠋⠁⠄⠄⠄⠄⠄⣀⣠⣴⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⣀⠄⠄⠄⠄⠄⠉⠉⠛⠛⠿⢿⣿⡇⠄⢿⣿⣿⡀⢸⣿⣿⣿⡇⠄⢻⣿⣿⣿⣿⣿⣿⠄⢸⣿⣿⣿⣇⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠄⠸⣿⣿⠄⢸⠄⢸⣿⣿⣿⣿⠃⢰⣿⠁⠄⠄⠄⠄⣀⣤⣴⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣀⡀⠄⠄⠄⠄⠄⠈⠁⠄⠸⣿⣿⣇⠄⠻⣿⣿⡇⠄⢸⣿⣿⣿⣿⣿⣿⠄⣼⣿⣿⣿⣿⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠄⢻⣿⠄⠈⠄⢸⣿⣿⣿⡿⠄⣼⣿⡷⠄⠒⢸⠏⠉⣿⠟⢹⠏⠉⠟⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠋⠉⠛⠛⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠙⡿⠛⠋⠹⠓⡢⠄⠄⡀⠄⡀⠄⢸⣿⣿⡀⠄⣿⣿⡇⠄⢸⣿⣿⣿⣿⣿⡿⠄⣿⣿⣿⣿⣿⡄⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠄⠈⢿⡆⠄⠄⠸⣿⣿⣿⡇⠄⠄⡟⠁⠄⢀⡞⠄⣰⠃⠄⠄⠄⠄⢀⣾⣿⣿⣿⣿⣿⣿⠟⢁⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠈⠻⣿⣿⣿⠟⠁⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠈⢀⣼⠇⠄⠄⢻⣿⡇⠄⣿⣿⡇⠄⢸⣿⣿⣿⣿⡿⠁⢰⣿⣿⣿⣿⣿⡇⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠄⠘⢿⡀⠄⠄⣿⣿⣿⠁⠄⡜⠄⠄⢀⡞⠄⡰⠁⠄⠄⠄⠄⢠⣾⣿⣿⣿⣿⣿⣿⠇⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠘⣿⣿⠄⠄⡀⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⡰⠊⠄⠄⠄⠄⠄⢿⡇⠄⣿⣿⡇⠄⢸⣿⣿⣿⠟⠄⠄⢸⡿⠛⣿⣿⣿⣧⠄⠄⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⠄⠄⠄⠁⠄⠄⢸⣿⣿⠄⠘⠄⠄⢀⡞⢀⣴⠃⢀⣤⣴⣿⣾⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⢻⣿⣤⣾⣥⡀⣀⠄⣠⠂⠄⠄⠄⡠⠊⠄⠄⠄⢀⣴⠄⠄⢸⡇⠄⣿⣿⡇⠄⢸⡿⠃⠄⣴⡇⠄⠈⢀⣼⣿⣿⣿⣿⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠄⠄⠄⠄⠄⠄⠄⠄⢿⣿⠄⢰⣾⣶⣿⣷⣾⣿⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠈⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣎⣀⡄⠄⣠⣶⣿⣿⡆⠄⠄⠇⢠⣿⣿⣧⠄⠈⢀⡄⠄⢻⡇⠄⠄⣾⣿⣿⣿⣿⣿⡆⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⠄⣸⡇⠄⠄⠄⠄⠉⠄⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠄⠄⢀⡀⠈⠄⣀⠄⣾⣿⣷⠄⢸⡇⠄⠄⡿⢿⣿⣿⣿⣿⡇⠄⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠄⠄⢠⣿⠄⠄⣿⣿⣶⣿⣇⠄⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⣀⣀⣀⣤⣤⣀⡀⠄⠄⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠄⢸⣿⣿⣿⣿⠄⠹⠋⣩⠄⢸⣧⠄⠄⢀⣾⣿⣿⣿⣿⣿⠄⠄⠘⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⠄⣸⣿⠄⠄⣿⣌⠛⢿⣿⠄⠄⠄⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⠄⠄⠄⠄⠄⠄⠄⢀⣠⣶⣾⣿⣿⣿⣿⣿⣿⣿⣷⣆⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠉⠁⠄⣼⣿⣿⣿⣿⠄⢰⣿⣿⡄⠈⡏⠄⠄⢾⣿⣿⣿⣿⣿⣿⡆⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⠄⣿⡟⠄⠄⣿⣿⣷⣦⣽⡄⠄⣿⠄⡀⠉⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⠄⠄⠄⠄⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠉⢀⡤⠄⠄⠄⠄⣿⣿⣿⣿⣿⡇⠸⣿⡟⣁⠄⠛⠄⠄⠸⠋⢸⣿⣿⣿⣿⣷⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⠄⠄⠄⢰⣿⡇⠄⠄⣉⣉⣉⠙⣻⡇⠄⠹⠄⢻⠄⢀⠈⠙⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠄⠄⠄⠄⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠸⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠁⠄⠄⠄⠄⢀⡴⠇⠄⠄⣿⣿⣿⣿⣿⡇⠄⣿⣾⡿⠃⠄⠄⠄⠄⣠⣿⣿⣿⣿⣿⣿⡆⠄⠄⣿⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠄⠄⠄⣼⣿⡇⠄⠄⣿⣿⣿⣿⣿⡇⠄⠄⠄⢸⠄⢸⣷⣦⣄⡈⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠄⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⣿⣿⣿⣿⡿⠟⠋⠁⠄⠄⠄⠄⠄⠄⠋⠄⡀⠄⠄⣿⣿⣿⣿⣿⡇⠄⣿⠏⢀⣴⣆⠄⠄⠄⢿⠟⢉⣿⣿⣿⣿⣿⡀⠄⠹⣿⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⡿⠄⠄⠄⢀⣿⣿⡇⠄⠄⣿⣿⣿⣿⣿⡇⠄⠄⠄⢸⠄⢸⣿⣿⣿⣿⣿⣶⠄⠄⠈⠛⠿⢿⣿⣿⣿⣧⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⣿⡿⠿⠋⠁⠄⠄⣠⣿⠁⠄⠄⠄⠄⠄⠰⠋⠄⠄⠄⣿⣿⣿⣿⣿⡇⠄⢁⣴⣿⣿⡿⠄⠄⠄⠄⣠⣾⣿⣿⣿⣿⣿⣧⠄⠄⠙⣿⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⡟⠄⠄⠄⣸⣿⠟⠋⠄⠄⢸⣿⣿⣿⣿⡇⠄⠄⠄⢸⠄⣾⣿⠟⢉⣴⠟⢉⠄⠄⠄⠄⣤⣄⣀⠉⠉⠛⠄⠄⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠛⠛⠋⠁⠄⠄⠄⣀⣤⣴⣾⡿⠋⠁⠄⠄⠄⠄⠄⠄⢠⣶⡇⠄⠄⣿⣿⣿⣿⣿⡇⠄⣿⣿⠟⠉⣀⡆⠄⠄⠄⣿⠟⣻⣿⣿⣿⣿⣿⣆⠄⠄⢹⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⣿⡇⠄⠄⢠⣿⠏⠄⡰⠄⠄⢸⣿⣿⣿⣿⡇⠄⠄⠄⣾⡴⠛⢁⣴⠟⠁⣠⣿⠄⠄⠄⠄⢸⡿⠋⢀⣴⡿⠖⣤⣤⡄⣀⣀⡀⠄⠄⠉⠉⠉⠁⠄⠄⠄⠄⣤⣶⣶⣿⣿⡇⠄⢹⣿⣿⠟⠁⠄⢀⣤⠄⠄⠄⠄⠄⠘⠋⠄⠄⢸⣿⣿⣿⣿⣿⡇⠄⡟⠁⣠⣾⠿⣣⡀⠄⠄⠄⣴⡿⠻⣿⣿⣿⣿⣿⡄⠄⠈⣿⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⡿⠄⠄⠄⣼⣿⠄⠄⢁⠄⠄⢸⣿⣿⣿⣿⡇⠄⢠⡠⠋⣀⣴⠟⠁⣠⣾⡿⠟⠄⠄⠄⠄⠄⢀⣴⡿⠋⢀⣴⠟⠁⣠⣿⣿⡇⠄⠄⠄⠄⠄⣀⣀⣤⣶⣿⣿⣿⣿⣿⣿⡇⠄⢸⠟⠁⠄⢀⣴⠟⠁⡀⠄⠄⠄⠄⢠⡴⠃⠄⢸⣿⣿⣿⣿⣿⡇⠄⣷⡿⠋⣡⣾⣿⡇⠄⠄⢸⠟⢀⣼⣿⣿⣿⣿⣿⣧⠄⠄⢸⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⣿⠇⠄⠄⢰⣿⠏⢀⡴⠛⠄⠄⢻⣿⣿⣿⣿⡿⠄⢈⣠⣾⡟⠁⣠⣾⡿⠋⢀⣴⠄⠄⠄⠄⣾⡿⠋⢀⣴⠟⠁⣠⣾⣿⣿⣿⣷⠄⠄⠄⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠄⠰⡞⢀⣴⠟⠁⡠⠊⠄⠄⠄⠄⠄⢸⣤⣤⠄⣿⣿⣿⣿⣿⣿⡇⠄⢋⣠⣾⣿⡿⠋⣀⠄⠄⠄⣠⣾⣿⣿⣿⣿⣿⣿⣿⡄⠄⠄⣿⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⡿⠄⠄⢠⣿⣿⣾⠟⢀⣴⡇⠄⠸⣿⣿⣿⣿⡇⠄⣸⣿⠋⣠⣾⡿⠋⢀⣤⡿⠿⡇⠄⠄⠄⠋⠄⣰⠟⠁⣠⣾⣿⣿⡿⠟⠛⠁⠄⠄⠄⢸⣿⣿⣿⣿⣿⣿⣿⡿⠛⠁⣠⣶⡄⠄⡀⠙⠳⢤⡞⠄⣠⠞⠁⠄⠄⠄⠄⢸⣿⠄⢸⣿⣿⣿⣿⣿⡇⢰⣿⣿⡿⠋⣠⣾⣿⠄⠄⠄⠹⠋⣻⣿⣿⣿⣿⣿⣿⣧⠄⠄⢸⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⡇⠄⢀⣾⣿⣿⠋⣠⡾⠋⣡⠄⢀⣀⣀⠄⠈⠄⠄⢻⣿⣾⡿⠋⢀⣴⡿⠋⣠⣾⡇⠄⠄⢠⣴⠞⠁⣠⣾⣿⠿⠛⠉⠄⣠⣴⣿⣿⡆⠄⣾⣿⣿⣿⣿⣿⡿⠉⢀⣠⣾⣿⣿⣇⣼⣿⠆⣤⣀⠈⠙⠁⢠⡶⠄⠄⠄⢀⣾⣿⠄⠘⣿⣿⣿⣿⣿⠇⢸⡿⠋⣠⣾⣿⠟⣿⡆⠄⠄⠄⣾⣿⣿⣿⣿⣿⣿⣿⣿⡄⠄⠄⣿⣿
|
||||
⣿⣿⣿⣿⣿⣿⠄⢠⣿⣿⣿⣧⡾⠋⣠⣾⣿⠄⣼⣿⣿⣿⣿⡇⠄⣿⣿⠋⢀⣴⡿⠋⢀⣾⣿⠟⢻⡀⠄⠸⠃⣠⣾⡿⠋⢀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣶⣿⣿⣿⣿⣿⣟⣁⣴⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⣷⣦⣄⣀⠘⠋⠄⣈⠉⠙⠄⢀⡈⠙⠛⠉⣹⠄⣋⣤⣾⣿⢟⣡⣾⣿⣷⣄⢠⣤⣿⣉⣸⣿⣿⣿⣿⣿⣿⣷⠄⠄⢸⣿
|
||||
*/
|
||||
|
||||
const start = new Date()
|
||||
console.log(start);
|
||||
require("./requires")
|
||||
|
||||
// loading functions
|
||||
commands = new Map()
|
||||
|
||||
var path = `${__dirname}/functions`
|
||||
var folders = fs.readdirSync(path).filter(function (file) {
|
||||
return fs.statSync(path+'/'+file).isDirectory();
|
||||
});
|
||||
folders.forEach(element => {
|
||||
var commandFiles = fs.readdirSync(`${__dirname}/functions/${element}`).filter(file => file.endsWith('.js') && !file.startsWith("index"));
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const command = require(`./functions/${element}/${file}`);
|
||||
commands.set(command.name, command);
|
||||
}
|
||||
});
|
||||
|
||||
// General stuff
|
||||
var platform = process.platform;
|
||||
|
||||
// Express
|
||||
const express = require("express");
|
||||
const app = express()
|
||||
const port = 3777
|
||||
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended:true
|
||||
}));
|
||||
|
||||
// For monitoring
|
||||
app.get('/api/v1/health', async (req, res) => {
|
||||
if(await ratelimiter(req, res))return
|
||||
res.json({
|
||||
response:"okay",
|
||||
startup:await commands.get("readableTime").timestamp(start),
|
||||
uptime:await commands.get("readableTime").timeframe(new Date().getTime() - start.getTime())
|
||||
})
|
||||
})
|
||||
|
||||
// Functionality
|
||||
|
||||
/*
|
||||
// This is the code used for the rate limiter and ban system
|
||||
// If you need to do changes, do them here
|
||||
*/
|
||||
|
||||
|
||||
async function ratelimiter(req, res){
|
||||
return new Promise(async (resolve) => {
|
||||
|
||||
let rl = false;
|
||||
await commands.get("ratelimit").function(req).catch(err => {
|
||||
rl = true;
|
||||
switch(err){
|
||||
case 1: res.json({code:1});return
|
||||
case 2: res.json({code:2});return
|
||||
case 3: return
|
||||
};
|
||||
})
|
||||
resolve(rl);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// As for the banhandler and ratelimiter, i wasnt able to come up with a better way to implement it here so that i wont need all these repeating lines
|
||||
// If you have a better way to do it, feel free to tell me
|
||||
// GET
|
||||
app.get('/api/v1/info/:shortcode', async (req, res) => {
|
||||
// Endpoint to get information about a shortcode
|
||||
if(await ratelimiter(req, res))return
|
||||
commands.get("info").function(req, res)
|
||||
})
|
||||
|
||||
app.get('/api/v1/codeList', async (req, res) => {
|
||||
// Endpoint to get information about a shortcode
|
||||
if(await ratelimiter(req, res))return
|
||||
commands.get("codeList").function(req, res)
|
||||
})
|
||||
|
||||
app.get('/api/v1/test', async (req, res) => {
|
||||
// Testing endpoint uwu
|
||||
if(await ratelimiter(req, res))return
|
||||
res.status(418)
|
||||
res.send("I am a teapot, leave me alone")
|
||||
})
|
||||
|
||||
// POST
|
||||
app.post('/api/v1/createUser', async (req, res) => {
|
||||
// Endpoint to create user account
|
||||
if(await ratelimiter(req, res))return
|
||||
commands.get("createUser").function(req, res)
|
||||
})
|
||||
|
||||
app.post('/api/v1/authenticate', async (req, res) => {
|
||||
// Endpoint to create API key
|
||||
if(await ratelimiter(req, res))return
|
||||
commands.get("authenticate").function(req, res)
|
||||
})
|
||||
|
||||
app.post('/api/v1/createCode', async (req, res) => {
|
||||
// Endpoint to create API key
|
||||
if(await ratelimiter(req, res))return
|
||||
commands.get("createCode").function(req, res)
|
||||
})
|
||||
|
||||
// DELETE
|
||||
app.delete('/api/v1/deleteCode', async (req, res) => {
|
||||
// Endpoint to delete shortcodes
|
||||
if(await ratelimiter(req, res))return
|
||||
commands.get("deleteCode").function(req, res)
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Main Page Handler
|
||||
app.use(express.static(__dirname+"/webui"))
|
||||
app.use(express.static(__dirname+"/webui/favicon.ico"))
|
||||
app.use(express.static(__dirname+"/webui/stuff"))
|
||||
app.use(express.static(__dirname+"/webui/pages"))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// REDIRECTION HANDLER
|
||||
app.get('/:shortcode', async (req, res) => {
|
||||
// Endpoint to get information about a shortcode
|
||||
if(await ratelimiter(req, res))return
|
||||
if(!(req.params.shortcode.match(/^\w{3,18}$/))){
|
||||
console.log(req.params.shortcode + " Did not match");
|
||||
return
|
||||
}
|
||||
commands.get("redirect").function(req, res)
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app.listen(port, () => {
|
||||
commands.get("Startup_function").function()
|
||||
console.log(`------------------------------------ Status ------------------------------------ \n` +
|
||||
`${__dirname} \n` +
|
||||
`${platform} \n` +
|
||||
`Listening on port ${port} \n` +
|
||||
`The Startup took ${new Date() - start}ms \n` +
|
||||
`${commands.size} modules loaded`);
|
||||
})
|
||||
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
|
||||
rl.on("line", data =>{
|
||||
const args = data.trim().split(" ");
|
||||
const command = args.shift().toLowerCase();
|
||||
switch(command){
|
||||
case "exit":
|
||||
process.exit(1);
|
||||
break;
|
||||
case "reload":
|
||||
commands.get("reloadCommands").function()
|
||||
break;
|
||||
default:
|
||||
console.log("This is not a recognised command");
|
||||
break;
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
|
||||
// catch uncaught exceptions to prevent process crashes on random bugs
|
||||
process.on("uncaughtException", (err) => {
|
||||
// console.log(process.stderr);
|
||||
console.error(err.stack);
|
||||
// console.error(err.stack.length);
|
||||
// try{
|
||||
// commands.get("queryCommand").function("INSERT INTO tbl_errorlog (dtError) VALUES (?)",[err.stack])
|
||||
// }catch(error){
|
||||
// console.log(error);
|
||||
// }
|
||||
})
|
||||
Generated
+2278
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"colors": "^1.4.0",
|
||||
"express": "^4.18.2",
|
||||
"hashids": "^2.3.0",
|
||||
"mysql": "^2.18.1"
|
||||
},
|
||||
"name": "url_shortener",
|
||||
"version": "1.0.0",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "node main",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "🌺Emily 🍞/🥖🌸",
|
||||
"license": "ISC",
|
||||
"description": ""
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
config = require("./config/main.json")
|
||||
readline = require("readline")
|
||||
mysql = require("mysql")
|
||||
fs = require("fs")
|
||||
colors = require("colors");
|
||||
express = require('express')
|
||||
bodyParser = require("body-parser")
|
||||
bcrypt = require("bcrypt")
|
||||
Hashids = require("hashids/cjs")
|
||||
|
||||
// - Global Variables
|
||||
// -- Shortcodes cache
|
||||
shortcodes = {}
|
||||
// -- BCrypt
|
||||
saltRounds = 10
|
||||
// -- HashIDs
|
||||
hashids = new Hashids("", 6)
|
||||
// -- Ratelimit
|
||||
ratelimit = {}
|
||||
// -- Ip Bans
|
||||
ignorelist = {}
|
||||
@@ -0,0 +1,63 @@
|
||||
# Status codes for API Endpoints
|
||||
## General
|
||||
|Code|Meaning|
|
||||
|---|---|
|
||||
|1|Hit ratelimit|
|
||||
|2|Hit ratelimit too often.<br>You got banned|
|
||||
|||
|
||||
|
||||
## Info
|
||||
|Code|Meaning|
|
||||
|---|---|
|
||||
|10|success|
|
||||
|11|shortcode non-existant|
|
||||
|||
|
||||
|
||||
## createUser
|
||||
|Code|Meaning|
|
||||
|---|---|
|
||||
|20|success|
|
||||
|21|user already exists|
|
||||
|22|error while creating user|
|
||||
|23|Username length must be<br> between 3 and 32 characters|
|
||||
|24|Password and username must not be empty or just spaces|
|
||||
|||
|
||||
|
||||
## authenticate
|
||||
|Code|Meaning|
|
||||
|---|---|
|
||||
|30|success|
|
||||
|31|incorrect credentials|
|
||||
|32|user does not exist|
|
||||
|33|error while creating api key|
|
||||
|||
|
||||
|
||||
## createCode
|
||||
|Code|Meaning|
|
||||
|---|---|
|
||||
|40|success|
|
||||
|41|no api key present|
|
||||
|42|invalid api key|
|
||||
|43|error while creating shortcode|
|
||||
|44|you already have a shortcode for this url|
|
||||
|45|unknown error|
|
||||
|||
|
||||
|
||||
## codeList
|
||||
|Code|Meaning|
|
||||
|---|---|
|
||||
|50|success|
|
||||
|51|no api key present|
|
||||
|52|invalid api key|
|
||||
|||
|
||||
|
||||
## deleteCode
|
||||
|Code|Meaning|
|
||||
|---|---|
|
||||
|60|success|
|
||||
|61|no api key present|
|
||||
|62|invalid api key|
|
||||
|63|error while deleting shortcode|
|
||||
|64|Not your shortcode|
|
||||
|65|unknown error|
|
||||
|||
|
||||
@@ -0,0 +1,64 @@
|
||||
-- Dumping database structure for url_shortener
|
||||
CREATE DATABASE IF NOT EXISTS `url_shortener` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
|
||||
USE `url_shortener`;
|
||||
|
||||
-- Dumping structure for table url_shortener.tbl_access
|
||||
CREATE TABLE IF NOT EXISTS `tbl_access` (
|
||||
`idAccess` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fiShortcode` varchar(18) NOT NULL DEFAULT '0',
|
||||
`dtIpHash` varchar(32) NOT NULL DEFAULT '0',
|
||||
`dtTimestamp` varchar(20) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`idAccess`),
|
||||
KEY `FK_tbl_access_tbl_urls` (`fiShortcode`),
|
||||
CONSTRAINT `FK_tbl_access_tbl_urls` FOREIGN KEY (`fiShortcode`) REFERENCES `tbl_urls` (`idShortcode`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=313 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table url_shortener.tbl_bans
|
||||
CREATE TABLE IF NOT EXISTS `tbl_bans` (
|
||||
`idBan` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`dtIpHash` varchar(32) NOT NULL,
|
||||
`dtTimestamp` varchar(20) DEFAULT NULL,
|
||||
PRIMARY KEY (`idBan`),
|
||||
UNIQUE KEY `dtIpHash` (`dtIpHash`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table url_shortener.tbl_roles
|
||||
CREATE TABLE IF NOT EXISTS `tbl_roles` (
|
||||
`idRole` int(10) unsigned NOT NULL,
|
||||
`dtPermissions` varchar(2048) DEFAULT NULL,
|
||||
PRIMARY KEY (`idRole`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table url_shortener.tbl_urls
|
||||
CREATE TABLE IF NOT EXISTS `tbl_urls` (
|
||||
`idShortcode` varchar(18) NOT NULL,
|
||||
`dtOriginal` varchar(2048) DEFAULT NULL,
|
||||
`dtTimestamp` varchar(20) DEFAULT NULL,
|
||||
`fiUser` int(11) unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`idShortcode`),
|
||||
KEY `FK_tbl_urls_tbl_users` (`fiUser`),
|
||||
CONSTRAINT `FK_tbl_urls_tbl_users` FOREIGN KEY (`fiUser`) REFERENCES `tbl_users` (`idUser`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Data exporting was unselected.
|
||||
|
||||
-- Dumping structure for table url_shortener.tbl_users
|
||||
CREATE TABLE IF NOT EXISTS `tbl_users` (
|
||||
`idUser` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`dtApiKey` varchar(100) DEFAULT NULL,
|
||||
`dtUsername` varchar(32) NOT NULL DEFAULT '0',
|
||||
`dtPassword` varchar(512) DEFAULT NULL,
|
||||
`fiRole` int(11) unsigned DEFAULT NULL,
|
||||
`dtTimestamp` varchar(20) DEFAULT NULL,
|
||||
PRIMARY KEY (`idUser`),
|
||||
UNIQUE KEY `dtUsername` (`dtUsername`),
|
||||
KEY `FK_tbl_users_tbl_roles` (`fiRole`),
|
||||
KEY `dtApiKey` (`dtApiKey`),
|
||||
CONSTRAINT `FK_tbl_users_tbl_roles` FOREIGN KEY (`fiRole`) REFERENCES `tbl_roles` (`idRole`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/webui.iml" filepath="$PROJECT_DIR$/.idea/webui.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+20
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.1" />
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -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>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>URL Shortener by Emily</title>
|
||||
<script src="stuff/jquery-3.7.1.min.js"></script>
|
||||
<script src="stuff/bootstrap.min.js"></script>
|
||||
<link href="stuff/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="stuff/main.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">Url Shortener</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="Navbar-content">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active fw-bold" href="javascript:;" id="nav-home">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:;" id="nav-shortcodes">Shortcodes</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:;" id="nav-profile">Profile</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="profile-stuff">
|
||||
<button class="btn btn-outline-primary me-2 hide">Register</button>
|
||||
<button class="btn btn-outline-primary hide">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="main-content">
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$("#main-content").load("pages/home.html")
|
||||
|
||||
// Navbar handler
|
||||
// Yes, i am fully aware that my code is highly cringe, but its easy to understand, quick to write
|
||||
// and most importantly, it gets the job done
|
||||
// Feel free to suggest cleaner code tho if you know any
|
||||
$("#nav-home").on("click", () => {
|
||||
$("#nav-home").addClass("active").addClass("fw-bold")
|
||||
$("#nav-profile").removeClass("active").removeClass("fw-bold")
|
||||
$("#nav-shortcodes").removeClass("active").removeClass("fw-bold")
|
||||
$("#main-content").load("pages/home.html")
|
||||
})
|
||||
$("#nav-shortcodes").on("click", () => {
|
||||
$("#nav-shortcodes").addClass("active").addClass("fw-bold")
|
||||
$("#nav-profile").removeClass("active").removeClass("fw-bold")
|
||||
$("#nav-home").removeClass("active").removeClass("fw-bold")
|
||||
$("#main-content").load("pages/shortcodes.html")
|
||||
})
|
||||
$("#nav-profile").on("click", () => {
|
||||
$("#nav-profile").addClass("active").addClass("fw-bold")
|
||||
$("#nav-shortcodes").removeClass("active").removeClass("fw-bold")
|
||||
$("#nav-home").removeClass("active").removeClass("fw-bold")
|
||||
$("#main-content").load("pages/profile.html")
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
Page content for home tab
|
||||
-->
|
||||
|
||||
<div>
|
||||
<p>Feel free to check out my <a href="https://git.dommymommy.xyz/emily/url-shortener">Git</a></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#test").html("lol")
|
||||
</script>
|
||||
@@ -0,0 +1,5 @@
|
||||
<!--
|
||||
Page content for profile tab
|
||||
-->
|
||||
|
||||
<img src="stuff/IMG_1620.jpg">
|
||||
@@ -0,0 +1,3 @@
|
||||
<!--
|
||||
Page content for shortcodes tab
|
||||
-->
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
Vendored
+6
File diff suppressed because one or more lines are too long
Vendored
+7
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
.hide{
|
||||
display:none;
|
||||
}
|
||||
Reference in New Issue
Block a user