mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-21 00:59:29 +00:00
Sart to expose modern skins via graphql
This commit is contained in:
parent
8c222d39c7
commit
72bd7a2a53
6 changed files with 206 additions and 11 deletions
32
packages/skin-database/api/graphql/ModernSkinsConnection.ts
Normal file
32
packages/skin-database/api/graphql/ModernSkinsConnection.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import SkinModel from "../../data/SkinModel";
|
||||
import { knex } from "../../db";
|
||||
import ModernSkinResolver from "./resolvers/ModernSkinResolver";
|
||||
|
||||
|
||||
export default class ModernSkinsConnection {
|
||||
_first: number;
|
||||
_offset: number;
|
||||
constructor(first: number, offset: number) {
|
||||
this._first = first;
|
||||
this._offset = offset;
|
||||
}
|
||||
_getQuery() {
|
||||
let query = knex("skins").where({ skin_type: 2 });
|
||||
return query;
|
||||
}
|
||||
|
||||
async count() {
|
||||
const count = await this._getQuery().count("*", { as: "count" });
|
||||
return count[0].count;
|
||||
}
|
||||
|
||||
async nodes(_args, ctx) {
|
||||
const skins = await this._getQuery()
|
||||
.select()
|
||||
.limit(this._first)
|
||||
.offset(this._offset);
|
||||
return skins.map((skin) => {
|
||||
return new ModernSkinResolver(new SkinModel(ctx, skin));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import SkinModel from "../../../data/SkinModel";
|
||||
import ArchiveFileResolver from "./ArchiveFileResolver";
|
||||
import { NodeResolver, toId } from "./NodeResolver";
|
||||
|
||||
export default class ModernSkinResolver implements NodeResolver {
|
||||
_model: SkinModel;
|
||||
constructor(model: SkinModel) {
|
||||
this._model = model;
|
||||
}
|
||||
__typename = "ModernSkin";
|
||||
async id() {
|
||||
return toId(this.__typename, this.md5());
|
||||
}
|
||||
md5() {
|
||||
return this._model.getMd5();
|
||||
}
|
||||
filename() {
|
||||
return this._model.getFileName();
|
||||
}
|
||||
download_url() {
|
||||
return this._model.getSkinUrl();
|
||||
}
|
||||
async archive_files() {
|
||||
const files = await this._model.getArchiveFiles();
|
||||
return files.map((file) => new ArchiveFileResolver(file));
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ import ArchiveFileModel from "../../../data/ArchiveFileModel";
|
|||
import ArchiveFileResolver from "./ArchiveFileResolver";
|
||||
import DatabaseStatisticsResolver from "./DatabaseStatisticsResolver";
|
||||
import { fromId } from "./NodeResolver";
|
||||
import ModernSkinsConnection from "../ModernSkinsConnection";
|
||||
import ModernSkinResolver from "./ModernSkinResolver";
|
||||
|
||||
// These keys are already in the web client, so they are not secret at all.
|
||||
const client = algoliasearch("HQ9I5Z6IM5", "6466695ec3f624a5fccf46ec49680e51");
|
||||
|
|
@ -33,6 +35,13 @@ class RootResolver extends MutationResolver {
|
|||
}
|
||||
return new SkinResolver(skin);
|
||||
}
|
||||
case "ModernSkin": {
|
||||
const skin = await SkinModel.fromMd5(ctx, localId);
|
||||
if (skin == null) {
|
||||
return null;
|
||||
}
|
||||
return new ModernSkinResolver(skin);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -41,7 +50,11 @@ class RootResolver extends MutationResolver {
|
|||
if (skin == null) {
|
||||
return null;
|
||||
}
|
||||
return new SkinResolver(skin);
|
||||
if (skin.getSkinType() === "MODERN") {
|
||||
return new ModernSkinResolver(skin);
|
||||
} else {
|
||||
return new SkinResolver(skin);
|
||||
}
|
||||
}
|
||||
async fetch_tweet_by_url({ url }, { ctx }) {
|
||||
const tweet = await TweetModel.fromAnything(ctx, url);
|
||||
|
|
@ -90,6 +103,14 @@ class RootResolver extends MutationResolver {
|
|||
}
|
||||
return new SkinsConnection(first, offset, sort, filter);
|
||||
}
|
||||
|
||||
async modern_skins({ first, offset }) {
|
||||
if (first > 1000) {
|
||||
throw new Error("Maximum limit is 1000");
|
||||
}
|
||||
return new ModernSkinsConnection(first, offset);
|
||||
}
|
||||
|
||||
async skin_to_review(_args, { ctx }) {
|
||||
if (!ctx.authed()) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -7,10 +7,77 @@ interface Node {
|
|||
id: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
A Winamp skin. Could be modern or classic.
|
||||
|
||||
**Note**: At some point in the future, this might be renamed to `Skin`.
|
||||
"""
|
||||
interface GenericSkin {
|
||||
"""
|
||||
GraphQL ID of the skin
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
MD5 hash of the skin's file
|
||||
"""
|
||||
md5: String
|
||||
|
||||
"""
|
||||
URL to download the skin
|
||||
"""
|
||||
download_url: String
|
||||
|
||||
"""
|
||||
Filename of skin when uploaded to the Museum. Note: In some cases a skin
|
||||
has been uploaded under multiple names. Here we just pick one.
|
||||
"""
|
||||
filename: String
|
||||
|
||||
"""
|
||||
List of files contained within the skin's .wsz/.wal archive
|
||||
"""
|
||||
archive_files: [ArchiveFile]
|
||||
}
|
||||
|
||||
"""
|
||||
A "modern" Winamp skin. These skins use the `.wal` file extension and are free-form.
|
||||
|
||||
Most functionality in the Winamp Skin Museum is centered around "classic" skins,
|
||||
which are currently called just `Skin` in this schema.
|
||||
"""
|
||||
type ModernSkin implements GenericSkin & Node {
|
||||
"""
|
||||
GraphQL ID of the skin
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
MD5 hash of the skin's file
|
||||
"""
|
||||
md5: String
|
||||
|
||||
"""
|
||||
URL to download the skin
|
||||
"""
|
||||
download_url: String
|
||||
|
||||
"""
|
||||
Filename of skin when uploaded to the Museum. Note: In some cases a skin
|
||||
has been uploaded under multiple names. Here we just pick one.
|
||||
"""
|
||||
filename: String
|
||||
|
||||
"""
|
||||
List of files contained within the skin's .wsz archive
|
||||
"""
|
||||
archive_files: [ArchiveFile]
|
||||
}
|
||||
|
||||
"""
|
||||
A classic Winamp skin
|
||||
"""
|
||||
type Skin implements Node {
|
||||
type Skin implements GenericSkin & Node {
|
||||
"""
|
||||
GraphQL ID of the skin
|
||||
"""
|
||||
|
|
@ -89,6 +156,21 @@ type Skin implements Node {
|
|||
reviews: [Review]
|
||||
}
|
||||
|
||||
"""
|
||||
A collection of "modern" Winamp skins
|
||||
"""
|
||||
type ModernSkinsConnection {
|
||||
"""
|
||||
The total number of skins matching the filter
|
||||
"""
|
||||
count: Int
|
||||
|
||||
"""
|
||||
The list of skins
|
||||
"""
|
||||
nodes: [ModernSkin]
|
||||
}
|
||||
|
||||
"""
|
||||
The judgement made about a skin by a moderator
|
||||
"""
|
||||
|
|
@ -523,7 +605,7 @@ type Query {
|
|||
"""
|
||||
Get a skin by its MD5 hash
|
||||
"""
|
||||
fetch_skin_by_md5(md5: String!): Skin
|
||||
fetch_skin_by_md5(md5: String!): GenericSkin
|
||||
|
||||
"""
|
||||
Get a tweet by its URL
|
||||
|
|
@ -552,7 +634,7 @@ type Query {
|
|||
): InternetArchiveItem
|
||||
|
||||
"""
|
||||
All skins in the database
|
||||
All classic skins in the database
|
||||
|
||||
**Note:** We don't currently support combining sorting and filtering.
|
||||
"""
|
||||
|
|
@ -563,6 +645,11 @@ type Query {
|
|||
filter: SkinsFilterOption
|
||||
): SkinsConnection
|
||||
|
||||
"""
|
||||
All modern skins in the database
|
||||
"""
|
||||
modern_skins(first: Int = 10, offset: Int = 0): ModernSkinsConnection
|
||||
|
||||
"""
|
||||
Search the database using the Algolia search index used by the Museum.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getScreenshotUrl, getSkinUrl } from "./skins";
|
||||
import {
|
||||
TweetStatus,
|
||||
SkinRow,
|
||||
|
|
@ -27,7 +26,7 @@ export const IS_NOT_README =
|
|||
/(genex\.txt)|(genexinfo\.txt)|(gen_gslyrics\.txt)|(region\.txt)|(pledit\.txt)|(viscolor\.txt)|(winampmb\.txt)|("gen_ex help\.txt)|(mbinner\.txt)$/i;
|
||||
|
||||
export default class SkinModel {
|
||||
constructor(readonly ctx: UserContext, readonly row: SkinRow) {}
|
||||
constructor(readonly ctx: UserContext, readonly row: SkinRow) { }
|
||||
|
||||
static async fromMd5(
|
||||
ctx: UserContext,
|
||||
|
|
@ -163,13 +162,16 @@ export default class SkinModel {
|
|||
throw new Error(`Could not find file for skin with md5 ${this.getMd5()}`);
|
||||
}
|
||||
const filename = files[0].getFileName();
|
||||
if (!filename.match(/\.(zip)|(wsz)$/i)) {
|
||||
throw new Error("Expected filename to end with zip or wsz.");
|
||||
if (!filename.match(/\.(zip)|(wsz)|(wal)$/i)) {
|
||||
throw new Error("Expected filename to end with zip, wsz or wal.");
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
async getScreenshotFileName(): Promise<string> {
|
||||
if (this.getSkinType() === "MODERN") {
|
||||
throw new Error("Modern skins do not have screenshots yet.");
|
||||
}
|
||||
const skinFilename = await this.getFileName();
|
||||
return skinFilename.replace(/\.(wsz|zip)$/, ".png");
|
||||
}
|
||||
|
|
@ -197,16 +199,31 @@ export default class SkinModel {
|
|||
}
|
||||
|
||||
getMuseumUrl(): string {
|
||||
if (this.getSkinType() === "MODERN") {
|
||||
throw new Error("Modern skins do not render in the museum.");
|
||||
}
|
||||
return `https://skins.webamp.org/skin/${this.row.md5}`;
|
||||
}
|
||||
getWebampUrl(): string {
|
||||
if (this.getSkinType() === "MODERN") {
|
||||
throw new Error("Modern skins do not render in Webamp.");
|
||||
}
|
||||
return `https://webamp.org?skinUrl=${this.getSkinUrl()}`;
|
||||
}
|
||||
getScreenshotUrl(): string {
|
||||
return getScreenshotUrl(this.row.md5);
|
||||
if (this.getSkinType() === "MODERN") {
|
||||
throw new Error("Modern skins do not have screenshots.");
|
||||
}
|
||||
return Skins.getScreenshotUrl(this.row.md5);
|
||||
}
|
||||
getSkinUrl(): string {
|
||||
return getSkinUrl(this.row.md5);
|
||||
switch (this.getSkinType()) {
|
||||
case "CLASSIC":
|
||||
return Skins.getSkinUrl(this.row.md5);
|
||||
case "MODERN":
|
||||
return Skins.getModernSkinUrl(this.row.md5);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getAverageColor(): string {
|
||||
|
|
@ -215,10 +232,11 @@ export default class SkinModel {
|
|||
|
||||
getBuffer = mem(async (): Promise<Buffer> => {
|
||||
if (process.env.LOCAL_FILE_CACHE) {
|
||||
const ext = this.getSkinType() === "CLASSIC" ? ".wsz" : ".wal";
|
||||
const skinPath = path.join(
|
||||
process.env.LOCAL_FILE_CACHE,
|
||||
"skins",
|
||||
this.getMd5() + ".wsz"
|
||||
this.getMd5() + ext
|
||||
);
|
||||
return fs.readFile(skinPath);
|
||||
} else {
|
||||
|
|
@ -231,6 +249,9 @@ export default class SkinModel {
|
|||
});
|
||||
|
||||
getScreenshotBuffer = mem(async (): Promise<Buffer> => {
|
||||
if (this.getSkinType() === "MODERN") {
|
||||
throw new Error("Modern skins do not have screenshots.");
|
||||
}
|
||||
const response = await fetch(this.getScreenshotUrl());
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
|
|
@ -249,6 +270,9 @@ export default class SkinModel {
|
|||
async withScreenshotTempFile(
|
||||
cb: (file: string) => Promise<void>
|
||||
): Promise<void> {
|
||||
if (this.getSkinType() === "MODERN") {
|
||||
throw new Error("Modern skins do not have screenshots.");
|
||||
}
|
||||
const screenshotFilename = await this.getScreenshotFileName();
|
||||
return withUrlAsTempFile(this.getScreenshotUrl(), screenshotFilename, cb);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ export function getSkinUrl(md5: string): string {
|
|||
return `https://cdn.webampskins.org/skins/${md5}.wsz`;
|
||||
}
|
||||
|
||||
export function getModernSkinUrl(md5: string): string {
|
||||
return `https://cdn.webampskins.org/skins/${md5}.wal`;
|
||||
}
|
||||
|
||||
export function getScreenshotUrl(md5: string): string {
|
||||
return `https://cdn.webampskins.org/screenshots/${md5}.png`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue