diff --git a/docs/EN.md b/docs/EN.md
index ce57197..dc342ef 100644
--- a/docs/EN.md
+++ b/docs/EN.md
@@ -162,6 +162,10 @@ Get skins from a specific tab of the site
nameMc.getSkins({ tab: "new", page: 2 })
.then((skins) => console.log(skins))
.catch((error) => console.log(error));
+
+nameMc.getSkins({ tab: "tag" })
+ .then((skins) => console.log(skins))
+ .catch((error) => console.log(error));
```
diff --git a/docs/RU.md b/docs/RU.md
index ba1997a..660f406 100644
--- a/docs/RU.md
+++ b/docs/RU.md
@@ -161,6 +161,10 @@ nameMc.skinHistory({ nickname: "MrZillaGold", page: 2 })
nameMc.getSkins({ tab: "new", page: 2 })
.then((skins) => console.log(skins))
.catch((error) => console.log(error));
+
+nameMc.getSkins({ tab: "tag" })
+ .then((skins) => console.log(skins))
+ .catch((error) => console.log(error));
```
diff --git a/package-lock.json b/package-lock.json
index e2ff840..2c36842 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "namemcwrapper",
- "version": "1.7.2",
+ "version": "1.8.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "namemcwrapper",
- "version": "1.7.2",
+ "version": "1.8.0",
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
@@ -33,6 +33,7 @@
"integrity": "sha512-lYSBC7B4B9hJ7sv0Ojx1BrGhuzCoOIYfLjd+Xpd4rOzdS+a47yi8voV8vFkfjlZR1N5qZO7ixOCbobUdT304PQ==",
"dev": true,
"dependencies": {
+ "chokidar": "^3.4.0",
"commander": "^4.0.1",
"convert-source-map": "^1.1.0",
"fs-readdir-recursive": "^1.1.0",
@@ -47,11 +48,7 @@
"babel-external-helpers": "bin/babel-external-helpers.js"
},
"optionalDependencies": {
- "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents",
- "chokidar": "^3.4.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents"
}
},
"node_modules/@babel/code-frame": {
@@ -94,10 +91,6 @@
},
"engines": {
"node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
}
},
"node_modules/@babel/core/node_modules/semver": {
@@ -149,9 +142,6 @@
"@babel/helper-validator-option": "^7.12.17",
"browserslist": "^4.14.5",
"semver": "^6.3.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
@@ -500,9 +490,6 @@
"@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.12.1",
"@babel/plugin-proposal-optional-chaining": "^7.13.12"
- },
- "peerDependencies": {
- "@babel/core": "^7.13.0"
}
},
"node_modules/@babel/plugin-proposal-async-generator-functions": {
@@ -618,9 +605,6 @@
"@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.12.1",
"@babel/plugin-syntax-optional-chaining": "^7.8.3"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-private-methods": {
@@ -743,9 +727,6 @@
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-top-level-await": {
@@ -1163,9 +1144,6 @@
"babel-plugin-polyfill-regenerator": "^0.1.2",
"core-js-compat": "^3.9.0",
"semver": "^6.3.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-env/node_modules/semver": {
@@ -1555,19 +1533,6 @@
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^4.0.0",
- "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
@@ -1581,10 +1546,6 @@
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
@@ -1594,10 +1555,6 @@
"dev": true,
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
@@ -1611,10 +1568,6 @@
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
@@ -1647,13 +1600,6 @@
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "*"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/scope-manager": {
@@ -1667,10 +1613,6 @@
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": {
@@ -1680,10 +1622,6 @@
"dev": true,
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": {
@@ -1702,15 +1640,6 @@
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": {
@@ -1724,10 +1653,6 @@
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/experimental-utils/node_modules/semver": {
@@ -1758,18 +1683,6 @@
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
@@ -1783,10 +1696,6 @@
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
@@ -1796,10 +1705,6 @@
"dev": true,
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
@@ -1818,15 +1723,6 @@
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
@@ -1840,10 +1736,6 @@
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/semver": {
@@ -2342,7 +2234,13 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
"dev": true,
- "optional": true
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
},
"node_modules/chokidar/node_modules/readdirp": {
"version": "3.5.0",
@@ -4309,10 +4207,6 @@
},
"engines": {
"node": ">= 10.12.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mochajs"
}
},
"node_modules/mocha/node_modules/debug": {
diff --git a/package.json b/package.json
index 31d78e3..d5e7247 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "namemcwrapper",
- "version": "1.7.2",
+ "version": "1.8.0",
"description": "ES6 Promise based wrapper for NameMC.com",
"main": "./dist/NameMC.js",
"exports": {
diff --git a/src/DataParser.ts b/src/DataParser.ts
index 8987166..702a758 100644
--- a/src/DataParser.ts
+++ b/src/DataParser.ts
@@ -2,9 +2,9 @@ import cheerio from "cheerio";
import { WrapperError } from "./WrapperError";
-import { profileSkinsRegExp, escapeColorsClasses, escapeHtml } from "./utils";
+import { profileSkinsRegExp, escapeColorsClasses, escapeHtml, skinRegExp } from "./utils";
-import { ISkin, ICape, ICapeResponse, IRender, IGetEndpointOptions, IGetRendersOptions, ISkinResponse, ICapeInfo, IServerPreview, IServer, Hash, BasePlayerInfo, IExtendedSkin, Model } from "./interfaces";
+import { ISkin, IExtendedSkin, INamedSkin, ICape, ICapeResponse, IRender, IGetEndpointOptions, IGetRendersOptions, ISkinResponse, ICapeInfo, IServerPreview, IServer, Hash, BasePlayerInfo, Model } from "./interfaces";
import TagElement = cheerio.TagElement;
import Root = cheerio.Root;
@@ -14,25 +14,31 @@ export abstract class DataParser {
abstract getRenders(options: IGetRendersOptions): IRender;
abstract getCapeInfo(hash: Hash): ICapeInfo;
- protected parseSkins(data: string): ISkin[] {
+ protected parseSkins(data: string): (ISkin | INamedSkin)[] {
const $ = cheerio.load(data);
const skins = $("div.card-body.position-relative.text-center.checkered.p-1")
.map((index, card) => {
+ const cardLinkHash = (card.parent as TagElement).attribs.href.replace(skinRegExp, "$1");
+ const cardHeader = ((card.parent as TagElement).children as TagElement[]).filter(({ name, attribs: { class: className = "" } = {} }) => name === "div" && className.includes("card-header"))[0];
+ const cardName = cardHeader ? cardHeader.children[0]?.data : null;
+
const $ = cheerio.load(card);
const [skin] = $("div > img.drop-shadow") // @ts-ignore
.map((index, { attribs: { src } }) => {
const isValidSkin = this.checkSkinLink(src);
- if (isValidSkin) {
- return isValidSkin;
- }
+ return {
+ ...isValidSkin,
+ hash: cardLinkHash
+ };
})
.get();
return {
...skin,
+ name: cardName,
rating: this.parseSkinRating($)
};
})
@@ -326,12 +332,14 @@ export abstract class DataParser {
switch(type) {
case "skin": {
- const model = (response as ISkinResponse).model;
+ const name = (response as ISkinResponse).name || null;
+ const model = (response as ISkinResponse).model || "unknown";
const rating = (response as ISkinResponse).rating ?? 0;
return {
url,
hash,
+ name,
model,
rating,
renders: this.getRenders({
@@ -367,10 +375,19 @@ export abstract class DataParser {
}
private parseSkinRating = ($: Root): number => {
- const { children: [{ data: rating }] } = $(".position-absolute.bottom-0.right-0.text-muted")
- .get(0);
+ const ratingElement = $(".position-absolute.bottom-0.right-0.text-muted")
+ .get(0)
+ .children;
+
+ const rating = (
+ ratingElement.length > 1 ?
+ ratingElement[1]
+ :
+ ratingElement[0]
+ )
+ .data;
- return Number(rating.slice(1));
+ return Number(rating.replace(/(?:[^\d]+)([\d]+)/, "$1"));
};
private parseSkinTime = ($: Root): number => {
diff --git a/src/NameMC.ts b/src/NameMC.ts
index ce2d719..0dc3e87 100644
--- a/src/NameMC.ts
+++ b/src/NameMC.ts
@@ -6,7 +6,7 @@ import { WrapperError } from "./WrapperError";
import { nameRegExp, profileRegExp, skinRegExp, capes, getUUID } from "./utils";
-import { IRender, IOptions, ISkin, IExtendedSkin, ICape, ICapeInfo, Transformation, ITransformSkinOptions, ICheckServerLikeOptions, IFriend, IGetSkinsOptions, IServerPreview, IGetEndpointOptions, IPlayer, IGetSkinHistoryOptions, IGetRendersOptions, IServer, Tab, Section, Nickname, CapeHash, BasePlayerInfo } from "./interfaces";
+import { IRender, IOptions, ISkin, IExtendedSkin, ICape, ICapeInfo, Transformation, ITransformSkinOptions, ICheckServerLikeOptions, IFriend, IGetSkinsOptions, IServerPreview, IGetEndpointOptions, IPlayer, IGetSkinHistoryOptions, IGetRendersOptions, IServer, Tab, Section, Nickname, CapeHash, BasePlayerInfo, INamedSkin } from "./interfaces";
export class NameMC extends DataParser {
@@ -243,7 +243,9 @@ export class NameMC extends DataParser {
/**
* Get skins from a specific tab of the site
*/
- getSkins({ tab = "trending", page = 1, section = "weekly" }: IGetSkinsOptions = {}): Promise {
+ getSkins(options: IGetSkinsOptions & ({ tab: "trending" | "tag" | "new" })): Promise
+ getSkins(options: IGetSkinsOptions & ({ tab: "trending"; section: "top"; } | { tab: "random" })): Promise
+ getSkins({ tab = "trending", page = 1, section = "weekly" }: IGetSkinsOptions = {}): Promise {
const tabs: Tab[] = ["trending", "new", "random", "tag"];
const sections: Section[] = ["daily", "weekly", "monthly", "top"];
diff --git a/src/interfaces/NameMC/response.ts b/src/interfaces/NameMC/response.ts
index c1b2946..60221e4 100644
--- a/src/interfaces/NameMC/response.ts
+++ b/src/interfaces/NameMC/response.ts
@@ -5,9 +5,10 @@ export interface IResponse {
}
export interface ISkinResponse extends IResponse {
+ type: "skin";
model: Model;
+ name?: string;
rating?: number;
- type: "skin";
}
export interface ICapeResponse extends IResponse {
diff --git a/src/interfaces/NameMC/skin.ts b/src/interfaces/NameMC/skin.ts
index 37d1fdf..b864105 100644
--- a/src/interfaces/NameMC/skin.ts
+++ b/src/interfaces/NameMC/skin.ts
@@ -1,7 +1,7 @@
import { IRender } from "./render";
import { Nickname } from "./nickname";
-export type Model = "classic" | "slim";
+export type Model = "classic" | "slim" | "unknown";
export type Hash = string;
export interface ISkin {
@@ -11,6 +11,11 @@ export interface ISkin {
hash: Hash;
model: Model;
rating: number;
+ name: string | null;
+}
+
+export interface INamedSkin extends ISkin {
+ name: string;
}
export interface IExtendedSkin extends ISkin {
diff --git a/src/utils.ts b/src/utils.ts
index 71ac689..a2d50bc 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -9,7 +9,7 @@ import Element = cheerio.Element;
export const nameRegExp = /^(?:(?[A-Za-z0-9_]{1,16})|(?[0-9a-f]{8}-?[0-9a-f]{4}-?[0-5][0-9a-f]{3}-?[089ab][0-9a-f]{3}-?[0-9a-f]{12}))$/;
export const profileRegExp = /[^]+\/profile\/[^]+/;
export const profileSkinsRegExp = /\/minecraft-skins\/profile\/([^]+)/;
-export const skinRegExp = /[^]+\/skin\/([^]+)/;
+export const skinRegExp = /(?:[^]+)?\/skin\/([^]+)/;
export const capes: CapesMap = new Map([
["1981aad373fa9754", "MineCon 2016"],
diff --git a/test/tests.mjs b/test/tests.mjs
index 53865ac..c7496a1 100644
--- a/test/tests.mjs
+++ b/test/tests.mjs
@@ -44,19 +44,35 @@ describe("Skins", () => {
describe("getSkins();", () => {
it("Get skins and check array size", async () => {
const skins = await nameMc.getSkins();
-
+
+ assert.strictEqual(skins.length, 30);
+ });
+
+ it("Get skins tags and check array size", async () => {
+ const skins = await nameMc.getSkins({
+ tab: "tag"
+ });
+
assert.strictEqual(skins.length, 30);
});
-
+
it("Get skins with custom tag and check array size", async () => {
const skins = await nameMc.getSkins({
tab: "tag",
section: "girl"
});
-
+
assert.strictEqual(skins.length, 30);
});
});
+
+ describe("getSkins();", () => {
+ it("Get skins and check tags array size", async () => {
+ const skin = await nameMc.getSkin("a4eaf5f46753cf75");
+
+ assert.ok(skin.tags.length > 1);
+ });
+ });
});
describe("Capes", () => {