mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 10:15:31 +00:00
Server changes for skin museum
This commit is contained in:
parent
55e9bd102e
commit
6343a2156a
6 changed files with 192 additions and 37 deletions
|
|
@ -5,6 +5,19 @@ import * as S3 from "./s3";
|
|||
import Shooter from "./shooter";
|
||||
import _temp from "temp";
|
||||
import * as Analyser from "./analyser";
|
||||
import { SkinType } from "./types";
|
||||
import JSZip from "jszip";
|
||||
|
||||
export async function getSkinType(file: Buffer): Promise<SkinType> {
|
||||
const zip = await JSZip.loadAsync(file);
|
||||
if (zip.file(/main\.bmp$/i).length > 0) {
|
||||
return "CLASSIC";
|
||||
}
|
||||
if (zip.file(/skin\.xml$/i).length > 0) {
|
||||
return "MODERN";
|
||||
}
|
||||
throw new Error("Not a skin");
|
||||
}
|
||||
|
||||
// TODO Move this into the function so that we clean up on each run?
|
||||
const temp = _temp.track();
|
||||
|
|
@ -17,7 +30,7 @@ const temp = _temp.track();
|
|||
// Construct IA Webamp UR
|
||||
type Result =
|
||||
| { md5: string; status: "FOUND" }
|
||||
| { md5: string; status: "ADDED"; averageColor: string };
|
||||
| { md5: string; status: "ADDED"; averageColor?: string; skinType: SkinType };
|
||||
|
||||
export async function addSkinFromBuffer(
|
||||
buffer: Buffer,
|
||||
|
|
@ -29,6 +42,39 @@ export async function addSkinFromBuffer(
|
|||
if (exists) {
|
||||
return { md5, status: "FOUND" };
|
||||
}
|
||||
|
||||
// Note: This will thrown on invalid skins.
|
||||
const skinType = await getSkinType(buffer);
|
||||
|
||||
switch (skinType) {
|
||||
case "CLASSIC":
|
||||
return addClassicSkinFromBuffer(buffer, md5, filePath, uploader);
|
||||
case "MODERN":
|
||||
return addModernSkinFromBuffer(buffer, md5, filePath, uploader);
|
||||
}
|
||||
}
|
||||
|
||||
async function addModernSkinFromBuffer(
|
||||
buffer: Buffer,
|
||||
md5: string,
|
||||
filePath: string,
|
||||
uploader: string
|
||||
): Promise<Result> {
|
||||
const tempFile = temp.path({ suffix: ".wal" });
|
||||
fs.writeFileSync(tempFile, buffer);
|
||||
await S3.putSkin(md5, buffer, "wal");
|
||||
|
||||
await Skins.addSkin({ md5, filePath, uploader, modern: true });
|
||||
|
||||
return { md5, status: "ADDED", skinType: "MODERN" };
|
||||
}
|
||||
|
||||
async function addClassicSkinFromBuffer(
|
||||
buffer: Buffer,
|
||||
md5: string,
|
||||
filePath: string,
|
||||
uploader: string
|
||||
): Promise<Result> {
|
||||
const tempFile = temp.path({ suffix: ".wsz" });
|
||||
fs.writeFileSync(tempFile, buffer);
|
||||
const tempScreenshotPath = temp.path({ suffix: ".png" });
|
||||
|
|
@ -41,7 +87,9 @@ export async function addSkinFromBuffer(
|
|||
|
||||
const averageColor = await Analyser.getColor(tempScreenshotPath);
|
||||
await S3.putScreenshot(md5, fs.readFileSync(tempScreenshotPath));
|
||||
await S3.putSkin(md5, buffer);
|
||||
await Skins.addSkin({ md5, filePath, uploader, averageColor });
|
||||
return { md5, status: "ADDED", averageColor };
|
||||
await S3.putSkin(md5, buffer, "wsz");
|
||||
await Skins.addSkin({ md5, filePath, uploader, averageColor, modern: false });
|
||||
|
||||
await Skins.updateSearchIndex(md5);
|
||||
return { md5, status: "ADDED", averageColor, skinType: "CLASSIC" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,10 +56,26 @@ function getWebampUrl(md5: string): string {
|
|||
return `https://webamp.org?skinUrl=${getSkinUrl(md5)}`;
|
||||
}
|
||||
|
||||
export async function addSkin({ md5, filePath, uploader, averageColor }) {
|
||||
function getMuseumUrl(md5: string): string {
|
||||
return `https://skins.webamp.org/skin/${md5}`;
|
||||
}
|
||||
|
||||
export async function addSkin({
|
||||
md5,
|
||||
filePath,
|
||||
uploader,
|
||||
averageColor,
|
||||
modern,
|
||||
}: {
|
||||
md5: string;
|
||||
filePath: string;
|
||||
uploader: string;
|
||||
averageColor?: string;
|
||||
modern: boolean;
|
||||
}) {
|
||||
skins_CONVERTED.insert({
|
||||
md5,
|
||||
type: "CLASSIC",
|
||||
type: modern ? "MODERN" : "CLASSIC",
|
||||
filePaths: [filePath],
|
||||
uploader,
|
||||
averageColor,
|
||||
|
|
@ -67,7 +83,7 @@ export async function addSkin({ md5, filePath, uploader, averageColor }) {
|
|||
await knex("skins").insert(
|
||||
{
|
||||
md5,
|
||||
skin_type: SKIN_TYPE.CLASSIC,
|
||||
skin_type: modern ? SKIN_TYPE.MODERN : SKIN_TYPE.CLASSIC,
|
||||
average_color: averageColor,
|
||||
},
|
||||
[]
|
||||
|
|
@ -114,6 +130,7 @@ export async function skinExists(md5: string): Promise<boolean> {
|
|||
}
|
||||
|
||||
// TODO: SQLITE
|
||||
// TODO: Handle modern skins
|
||||
export async function getSkinByMd5_DEPRECATED(
|
||||
md5: string
|
||||
): Promise<SkinRecord | null> {
|
||||
|
|
@ -147,6 +164,7 @@ export async function getSkinByMd5_DEPRECATED(
|
|||
imageHash: _skin.imageHash,
|
||||
nsfwPredictions: _skin.nsfwPredictions,
|
||||
approved: _skin.approved,
|
||||
rejected: _skin.rejected,
|
||||
averageColor: _skin.averageColor,
|
||||
emails: _skin.emails,
|
||||
tweetStatus,
|
||||
|
|
@ -155,6 +173,7 @@ export async function getSkinByMd5_DEPRECATED(
|
|||
fileNames: getFilenames(_skin),
|
||||
canonicalFilename: getCanonicalFilename(_skin),
|
||||
webampUrl: getWebampUrl(_skin.md5),
|
||||
museumUrl: getMuseumUrl(_skin.md5),
|
||||
internetArchiveItemName,
|
||||
internetArchiveUrl,
|
||||
};
|
||||
|
|
@ -495,22 +514,31 @@ export async function getMuseumPageSql({
|
|||
> {
|
||||
const skins = await knex.raw(
|
||||
`
|
||||
SELECT skins.md5,
|
||||
skins.average_color,
|
||||
skin_reviews.review = 'NSFW' AS nsfw,
|
||||
skin_reviews.review = 'APPROVED' AS approved,
|
||||
skin_reviews.review = 'REJECTED' AS rejected,
|
||||
tweets.likes
|
||||
SELECT skins.md5,
|
||||
skins.average_color,
|
||||
skin_reviews.review = 'NSFW' AS nsfw,
|
||||
skin_reviews.review = 'APPROVED' AS approved,
|
||||
skin_reviews.review = 'REJECTED' AS rejected,
|
||||
tweets.likes,
|
||||
CASE skins.md5
|
||||
WHEN "5e4f10275dcb1fb211d4a8b4f1bda236" THEN 0 -- Base
|
||||
WHEN "cd251187a5e6ff54ce938d26f1f2de02" THEN 1 -- Winamp3 Classified
|
||||
WHEN "b0fb83cc20af3abe264291bb17fb2a13" THEN 2 -- Winamp5 Classified
|
||||
WHEN "d6010aa35bed659bc1311820daa4b341" THEN 3 -- Bento Classified
|
||||
ELSE 1000
|
||||
END priority
|
||||
FROM skins
|
||||
LEFT JOIN tweets
|
||||
ON tweets.skin_md5 = skins.md5
|
||||
LEFT JOIN skin_reviews
|
||||
ON skin_reviews.skin_md5 = skins.md5
|
||||
LEFT JOIN tweets
|
||||
ON tweets.skin_md5 = skins.md5
|
||||
LEFT JOIN skin_reviews
|
||||
ON skin_reviews.skin_md5 = skins.md5
|
||||
WHERE skin_type = 1
|
||||
ORDER BY tweets.likes DESC,
|
||||
nsfw ASC,
|
||||
approved DESC,
|
||||
rejected ASC
|
||||
ORDER BY
|
||||
priority ASC,
|
||||
tweets.likes DESC,
|
||||
nsfw ASC,
|
||||
approved DESC,
|
||||
rejected ASC
|
||||
LIMIT ? offset ?`,
|
||||
[first, offset]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ export async function postSkin({
|
|||
canonicalFilename,
|
||||
screenshotUrl,
|
||||
skinUrl,
|
||||
webampUrl,
|
||||
averageColor,
|
||||
emails,
|
||||
tweetUrl,
|
||||
|
|
@ -54,12 +53,13 @@ export async function postSkin({
|
|||
internetArchiveItemName,
|
||||
readmeText,
|
||||
nsfw,
|
||||
museumUrl,
|
||||
} = skin;
|
||||
const title = _title ? _title(canonicalFilename) : canonicalFilename;
|
||||
|
||||
const embed = new RichEmbed()
|
||||
.setTitle(title)
|
||||
.addField("Try Online", `[webamp.org](${webampUrl})`, true)
|
||||
.addField("Try Online", `[skins.webamp.org](${museumUrl})`, true)
|
||||
.addField("Download", `[${canonicalFilename}](${skinUrl})`, true)
|
||||
.addField("Md5", md5, true);
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ export async function postSkin({
|
|||
|
||||
// @ts-ignore WAT?
|
||||
const msg = await dest.send(embed);
|
||||
if (tweetStatus === "TWEETED") {
|
||||
if (tweetStatus !== "UNREVIEWED") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -183,3 +183,32 @@ function getPrettyTwitterStatus(status: TweetStatus): string {
|
|||
return "Tweeted 🐦";
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendAlreadyReviewed({
|
||||
md5,
|
||||
dest,
|
||||
}: {
|
||||
md5: string;
|
||||
dest: TextChannel | DMChannel | GroupDMChannel;
|
||||
}) {
|
||||
const skin = await Skins.getSkinByMd5_DEPRECATED(md5);
|
||||
if (skin == null) {
|
||||
console.warn("Could not find skin for md5", { md5, alert: true });
|
||||
logger.warn("Could not find skin for md5", { md5, alert: true });
|
||||
return;
|
||||
}
|
||||
const { canonicalFilename, museumUrl, tweetStatus, nsfw } = skin;
|
||||
|
||||
const embed = new RichEmbed()
|
||||
.setTitle(
|
||||
`Someone flagged "${canonicalFilename}", but it's already been reviwed.`
|
||||
)
|
||||
.addField("Status", getPrettyTwitterStatus(tweetStatus), true)
|
||||
.addField("Museum", `[${canonicalFilename}](${museumUrl})`, true);
|
||||
|
||||
if (nsfw) {
|
||||
embed.addField("NSFW", `🔞`, true);
|
||||
}
|
||||
|
||||
dest.send(embed);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ app.get("/skins/", async (req, res) => {
|
|||
});
|
||||
|
||||
app.post("/skins/missing", async (req, res) => {
|
||||
console.log("Checking for missing skins.");
|
||||
const missing = [];
|
||||
const found = [];
|
||||
for (const md5 of req.body.hashes) {
|
||||
|
|
@ -89,25 +90,55 @@ app.post("/skins/missing", async (req, res) => {
|
|||
found.push(md5);
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
`${found.length} skins are found and ${missing.length} are missing.`
|
||||
);
|
||||
res.json({ missing, found });
|
||||
});
|
||||
|
||||
app.post("/skins/", async (req, res) => {
|
||||
const client = new Discord.Client();
|
||||
await client.login(config.discordToken);
|
||||
const dest = client.channels.get(config.SKIN_UPLOADS_CHANNEL_ID);
|
||||
|
||||
const files = req.files;
|
||||
if (files == null) {
|
||||
dest.send("Someone hit the upload endpoint with no files attached.");
|
||||
res.status(500).send({ error: "No file supplied" });
|
||||
return;
|
||||
}
|
||||
const upload = req.files.skin;
|
||||
if (upload == null) {
|
||||
dest.send("Someone hit the upload endpoint with no files attached.");
|
||||
res.status(500).send({ error: "No file supplied" });
|
||||
return;
|
||||
}
|
||||
const result = await addSkinFromBuffer(upload.data, upload.name, "Web API");
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = await addSkinFromBuffer(upload.data, upload.name, "Web API");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dest.send(`Encountered an error uploading a skin: ${e.message}`);
|
||||
res.status(500).send({ error: `Error adding skin: ${e.message}` });
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.status === "ADDED") {
|
||||
console.log(`Updating index for ${result.md5}.`);
|
||||
await Skins.updateSearchIndex(result.md5);
|
||||
// await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
if (result.skinType === "CLASSIC") {
|
||||
console.log(`Going to post new skin to discord: ${result.md5}`);
|
||||
// Don't await
|
||||
Utils.postSkin({
|
||||
md5: result.md5,
|
||||
title: (filename) => `New skin uploaded: ${filename}`,
|
||||
dest,
|
||||
});
|
||||
} else if (result.skinType === "MODERN") {
|
||||
dest.send(
|
||||
`Someone uploaded a new modern skin: ${upload.name} (${result.md5})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ ...result, filename: upload.name });
|
||||
|
|
@ -139,12 +170,19 @@ app.post("/skins/:md5/report", async (req, res) => {
|
|||
await client.login(config.discordToken);
|
||||
const dest = client.channels.get(config.NSFW_SKIN_CHANNEL_ID);
|
||||
|
||||
// Don't await
|
||||
Utils.postSkin({
|
||||
md5,
|
||||
title: (filename) => `Review: ${filename}`,
|
||||
dest,
|
||||
});
|
||||
const skin = await Skins.getSkinByMd5_DEPRECATED(md5);
|
||||
|
||||
if (skin.tweetStatus === "UNREVIEWED") {
|
||||
// Don't await
|
||||
Utils.postSkin({
|
||||
md5,
|
||||
title: (filename) => `Review: ${filename}`,
|
||||
dest,
|
||||
});
|
||||
} else {
|
||||
Utils.sendAlreadyReviewed({ md5, dest });
|
||||
}
|
||||
|
||||
res.send("The skin has been reported and will be reviewed shortly.");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@ AWS.config.update({ region: "us-west-2" });
|
|||
|
||||
const s3 = new AWS.S3();
|
||||
|
||||
function putSkin(md5, buffer) {
|
||||
function putSkin(md5, buffer, ext = "wsz") {
|
||||
return new Promise((resolve, rejectPromise) => {
|
||||
const bucketName = "cdn.webampskins.org";
|
||||
const key = `skins/${md5}.wsz`;
|
||||
const key = `skins/${md5}.${ext}`;
|
||||
s3.putObject(
|
||||
{ Bucket: bucketName, Key: key, Body: buffer, ACL: "public-read" },
|
||||
{
|
||||
Bucket: bucketName,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ACL: "public-read",
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
rejectPromise(err);
|
||||
|
|
@ -26,7 +31,12 @@ function putScreenshot(md5, buffer) {
|
|||
const bucketName = "cdn.webampskins.org";
|
||||
const key = `screenshots/${md5}.png`;
|
||||
s3.putObject(
|
||||
{ Bucket: bucketName, Key: key, Body: buffer, ACL: "public-read" },
|
||||
{
|
||||
Bucket: bucketName,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ACL: "public-read",
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
rejectPromise(err);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { NsfwPrediction } from "./nsfwImage";
|
||||
|
||||
export type TweetStatus = "APPROVED" | "REJECTED" | "TWEETED" | "UNREVIEWED";
|
||||
export type SkinType = "MODERN" | "CLASSIC";
|
||||
|
||||
export type DBSkinRecord = {
|
||||
md5: string;
|
||||
|
|
@ -35,6 +36,7 @@ export type SkinRecord = {
|
|||
skinUrl: string;
|
||||
canonicalFilename: string | null;
|
||||
webampUrl: string;
|
||||
museumUrl: string;
|
||||
tweeted?: boolean;
|
||||
rejected?: boolean;
|
||||
approved?: boolean;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue