diff --git a/README.md b/README.md index fc414b4..6f30350 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - A strictly typed Roblox API wrapper which supports both classic (BEDEV & BEDEV2) and OpenCloud endpoints. + A strictly typed Roblox API wrapper which supports both classic and OpenCloud endpoints. - - - diff --git a/build.ts b/build.ts index 9264b8f..f6bff8b 100644 --- a/build.ts +++ b/build.ts @@ -1,5 +1,4 @@ import * as esbuild from 'esbuild'; -import esbuildPluginTsc from "esbuild-plugin-tsc" const settings = { entryPoints: ["src/**/*"], @@ -8,12 +7,7 @@ const settings = { //keepNames: true, format: 'cjs', platform: 'node', - target: ['node16'], - plugins: [ - esbuildPluginTsc({ - force: true - }), - ], + target: ['node18', 'node16', 'node20', 'node22'] } satisfies esbuild.BuildOptions await esbuild.build(settings); diff --git a/bun.lockb b/bun.lockb index d3606d5..7ec8d07 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/graph.png b/graph.png deleted file mode 100644 index 06cc7a0..0000000 Binary files a/graph.png and /dev/null differ diff --git a/package.json b/package.json index ded8b4e..09337e9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "openblox", "description": "Roblox API Wrapper For Both Classic And OpenCloud APIs.", "type": "commonjs", - "version": "1.0.28", + "version": "1.0.29", "license": "MIT", "bugs": { "url": "https://github.com/MightyPart/openblox/issues" @@ -22,14 +22,21 @@ "exports_DEV": { ".": "./src/index.ts", "./config": "./src/config/index.ts", + "./classic": "./src/apis/classic/index.ts", "./classic/*": "./src/apis/classic/*/index.ts", + "./cloud": "./src/apis/cloud/index.ts", "./cloud/*": "./src/apis/cloud/*/index.ts", + "./cache/adapters": "./src/cache/cacheAdapters/index.ts", - "./http": "./src/http/index.ts", + "./http": "./src/http/http.utils.ts", "./types": "./src/types.ts", - "./queries/cloud": "./src/queries/cloud/index.ts" + + "./queries/cloud": "./src/queries/cloud/index.ts", + "./queries/classic": "./src/queries/classic/index.ts", + + "./helpers": "./src/helpers/index.ts" }, "exports": { ".": { @@ -44,6 +51,7 @@ "types": "./dist/apis/classic/index.d.ts", "default": "./dist/apis/classic/index.js" }, + "./classic/*": { "types": "./dist/apis/classic/*/index.d.ts", "default": "./dist/apis/classic/*/index.js" @@ -52,10 +60,12 @@ "types": "./dist/apis/cloud/index.d.ts", "default": "./dist/apis/cloud/index.js" }, + "./cloud/*": { "types": "./dist/apis/cloud/*/index.d.ts", "default": "./dist/apis/cloud/*/index.js" }, + "./cache/adapters": { "types": "./dist/cache/cacheAdapters/index.d.ts", "default": "./dist/cache/cacheAdapters/index.js" @@ -68,37 +78,42 @@ "types": "./dist/types.d.ts", "default": "./dist/types.ts" }, + "./queries/cloud": { "types": "./dist/queries/cloud/index.d.ts", "default": "./dist/queries/cloud/index.js" + }, + "./queries/classic": { + "types": "./dist/queries/classic/index.d.ts", + "default": "./dist/queries/classic/index.js" + }, + + "./helpers": { + "types": "./dist/helpers/index.d.ts", + "default": "./dist/helpers/index.js" } }, "dependencies": { - "delete-cli": "^0.1.3", "lodash": "^4.17.21", - "parse-roblox-errors": "^1.1.10", - "typeforge": "^0.0.21" + "parse-roblox-errors": "^1.1.10" }, "devDependencies": { "@types/lodash": "^4.17.0", - "@types/node": "^20.12.12", + "@types/node": "^22.1.0", "esbuild": "^0.21.5", - "esbuild-plugin-tsc": "^0.4.0", "prettier": "^3.2.5", "tablemark": "^3.1.0", "ts-arithmetic": "^0.1.1", - "ts-morph": "^22.0.0" + "ts-morph": "^22.0.0", + "typeforge": "^0.0.21", + "delete-cli": "^0.1.3" }, "peerDependencies": { "typescript": "^5.0.0" }, "scripts": { - "build:code": "delete dist && tsc", + "build:code": "delete dist && bun run ./build.ts && tsc --emitDeclarationOnly", "build:docs": "bun run ./docs/buildDocs.ts", - "build": "bun run build:docs && bun run build:code", - "graph:src": "madge ./src --image ./graph.png", - "graph:src:circular": "madge --circular ./src --image ./graph.png", - "graph:dist": "madge ./dist --image ./graph.png", - "graph:dist:circular": "madge --circular ./dist --image ./graph.png" + "build": "bun run build:docs && bun run build:code" } } diff --git a/src/index.ts b/src/index.ts index 5709f14..e69de29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +0,0 @@ -export * from "./apis/apiGroup" \ No newline at end of file diff --git a/src/queries/cloud/index.ts b/src/queries/cloud/index.ts new file mode 100644 index 0000000..de56484 --- /dev/null +++ b/src/queries/cloud/index.ts @@ -0,0 +1,2 @@ +export { Users } from "./usersQuery" +//export { Group } from "./groupQuery" \ No newline at end of file diff --git a/src/queries/cloud/usersQuery.ts b/src/queries/cloud/usersQuery.ts new file mode 100644 index 0000000..e960da7 --- /dev/null +++ b/src/queries/cloud/usersQuery.ts @@ -0,0 +1,158 @@ +// [ Modules ] /////////////////////////////////////////////////////////////////// +import { UsersApi } from "../../apis/cloud" +////////////////////////////////////////////////////////////////////////////////// + + +// [ Types ] ///////////////////////////////////////////////////////////////////// +import { ArrayNonEmpty, ArrayToUnion, Identifier, ObjectKeepKeys, ObjectPrettify, ObjectPrettifyDeep, UnionPrettify, UnionToArray } from "typeforge" +import { PrettifiedUserInfoData, UserThumbnailSize } from "../../apis/cloud/users/users.types" +import { ArrayNonEmptyIfConst, SecureUrl } from "../../utils/utils.types" + + +type UnionKeepTypes = U extends ToKeep ? U : never +type UnionRemoveTypes = U extends ToKeep ? never : U + + +type UserInfoField = ArrayToUnion +type UserThumbnailField = "thumbnail" | `thumbnail/${UserThumbnailSize}` | `thumbnail/${UserThumbnailSize}/${"PNG" | "JPEG"}` | `thumbnail/${UserThumbnailSize}/${"PNG" | "JPEG"}/${"ROUND" | "SQUARE"}` +type UsersField = UnionPrettify + + +type CleanObject< + Obj extends Record, Field extends string, + _ExludedIrrelevantKeys = ObjectPrettify>>, + // @ts-ignore | `UnionToArray` is an array. `_ExludedIrrelevantKeys[Field]` is typesafe. + _MaybeNoObject = UnionToArray["length"] extends 1 ? _ExludedIrrelevantKeys[Field] : _ExludedIrrelevantKeys +> = _MaybeNoObject +////////////////////////////////////////////////////////////////////////////////// + + +// [ Variables ] ///////////////////////////////////////////////////////////////// +const UsersApi_userInfo = UsersApi.userInfo +const UsersApi_userThumbnail = UsersApi.userThumbnail + +const userInfoFields = [ "createTime", "id", "name", "displayName", "about", "locale", "premium", "idVerified", "socialNetworkProfiles" ] as const +////////////////////////////////////////////////////////////////////////////////// + + +// [ Private Functions ] ///////////////////////////////////////////////////////// +const shellFn = (...args: any) => null + +function uniq_fast(a: any[]) { + var seen: any = {}; + var out = []; + var len = a.length; + var j = 0; + for(var i = 0; i < len; i++) { + var item = a[i]; + if(seen[item] !== 1) { + seen[item] = 1; + out[j++] = item; + } + } + return out; +} + + +// Users Info -------------------------------------------------------------------- +const getUserInfoSingle_forId = async (userId: Identifier, data: any, fields: ArrayNonEmpty) => { + const { data:userInfo } = await UsersApi_userInfo({ userId }) + data[userId] = userInfo[fields[0]] +} + +const getUserInfoMulti_forId = async (userId: Identifier, data: any, fields: ArrayNonEmpty) => { + const data_userId = data[userId] + const { data:userInfo } = await UsersApi_userInfo({ userId }); + (fields as ArrayNonEmpty).forEach(field => data_userId[field] = userInfo[field]) +} + +const getUsersInfo_forIds = ( + fields: ArrayNonEmpty, getUserInfo_forId: typeof getUserInfoSingle_forId | typeof getUserInfoMulti_forId +) => ( + async (userIds: ArrayNonEmptyIfConst, data: any) => { + await Promise.all(userIds.map(userId => getUserInfo_forId(userId, data, fields))) + } +) +// ------------------------------------------------------------------------------- + + +// User Thumbnails --------------------------------------------------------------- +const getUserThumbnailSingle_forId = async ( + userId: Identifier, data: any, data_userId: any, thumbnail: string, + size?: UserThumbnailSize, format?: "PNG" | "JPEG", shape?: "ROUND" | "SQUARE" +) => { + const { data:userThumbnail } = await UsersApi_userThumbnail({ userId, size, format, shape }) + if (!userThumbnail.done) return + data[userId] = userThumbnail.response.imageUri +} + +const getUserThumbnailMulti_forId = async ( + userId: Identifier, data: any, data_userId: any, thumbnail: string, + size?: UserThumbnailSize, format?: "PNG" | "JPEG", shape?: "ROUND" | "SQUARE" +) => { + const { data:userThumbnail } = await UsersApi_userThumbnail({ userId, size, format, shape }) + if (!userThumbnail.done) return + data_userId[thumbnail] = userThumbnail.response.imageUri +} + +const getUsersThumbnail_forIds = async ( + userIds: ArrayNonEmptyIfConst, data: any, getUserThumbnail_forId: typeof getUserThumbnailMulti_forId | typeof getUserThumbnailSingle_forId, + thumnail: string, size?: UserThumbnailSize, format?: "PNG" | "JPEG", shape?: "ROUND" | "SQUARE" +) => { + return await Promise.all(userIds.map(userId => getUserThumbnail_forId(userId, data, data[userId], thumnail, size, format, shape))) +} + +const getUsersThumbnails_forIds = ( + fields: string[][], getUserThumbnail_forId: typeof getUserThumbnailMulti_forId | typeof getUserThumbnailSingle_forId +) => ( + async (userIds: ArrayNonEmptyIfConst, data: any) => { + await Promise.all(fields.map(field => getUsersThumbnail_forIds( + userIds, data, getUserThumbnail_forId, field?.[0] as any as string, + field?.[2] as any as UserThumbnailSize, field?.[3] as any as "PNG" | "JPEG", field?.[4] as any as "ROUND" | "SQUARE", + ))) + } +) +// ------------------------------------------------------------------------------- +////////////////////////////////////////////////////////////////////////////////// + + +export const Users = { + get: (fields: ArrayNonEmptyIfConst) => { + fields = uniq_fast(fields) as ArrayNonEmptyIfConst + const isSingleField = (fields as Field[]).length == 1 + + const usersInfoFields = + (fields as Field[]).filter(field => userInfoFields.includes(field as UserInfoField)) as any as ArrayNonEmpty + const thisGetUsersInfo_forIds = usersInfoFields.length + ? getUsersInfo_forIds(usersInfoFields, isSingleField ? getUserInfoSingle_forId : getUserInfoMulti_forId) : shellFn + + const userThumbnailFields = (fields as Field[]).filter(field => field.startsWith("thumbnail")).map(field => [ field, ...field.split("/") ]) + const thisGetUsersThumbnails_forIds = userThumbnailFields.length + ? getUsersThumbnails_forIds(userThumbnailFields, isSingleField ? getUserThumbnailSingle_forId : getUserThumbnailMulti_forId) : shellFn + + const createData = isSingleField + ? (userIds: ArrayNonEmptyIfConst) => ({}) + : (userIds: ArrayNonEmptyIfConst) => { const data: any = {}; userIds.forEach(userId => data[userId] = {}); return data } + + return { + forIds: async (userIds: ArrayNonEmptyIfConst): Promise<{ + data: ObjectPrettifyDeep<{ + [Id in UserId]: CleanObject< + PrettifiedUserInfoData & + { [Key in UnionKeepTypes]: SecureUrl }, + Field + > + }> + }> => { + const data: any = createData(userIds) + + await Promise.all([ + thisGetUsersInfo_forIds(userIds, data), + thisGetUsersThumbnails_forIds(userIds, data) + ]) + + return { data } + } + } + } +} \ No newline at end of file