merge from local

This commit is contained in:
SrGooglo 2024-10-29 21:31:02 +00:00
parent fac6a9f0d1
commit 4ebe77a0d7
21 changed files with 490 additions and 370 deletions

View File

@ -1,6 +1,6 @@
{
"name": "comty.js",
"version": "0.60.3",
"version": "0.60.6",
"main": "./dist/index.js",
"author": "RageStudio <support@ragestudio.net>",
"scripts": {
@ -22,6 +22,6 @@
"socket.io-client": "^4.6.1"
},
"devDependencies": {
"@ragestudio/hermes": "^0.1.0"
"@ragestudio/hermes": "^1.0.0"
}
}

View File

@ -8,6 +8,7 @@ export default async (data, callback) => {
return false
}
if (data.response) {
if (data.response.status === 401) {
// check if the server issue a refresh token on data
if (data.response.data.expired) {
@ -26,9 +27,6 @@ export default async (data, callback) => {
return await callback()
}
}
if (data.response.status === 403) {
}
}
}

View File

@ -74,13 +74,13 @@ export async function createWebsockets() {
})
instance.on("disconnect", () => {
//console.debug(`[WS-API][${key}] Disconnected`)
console.debug(`[WS-API][${key}] Disconnected`)
globalThis.__comty_shared_state.eventBus.emit(`${key}:disconnected`)
})
instance.on("reconnect", () => {
// console.debug(`[WS-API][${key}] Reconnected`)
console.debug(`[WS-API][${key}] Reconnected`)
globalThis.__comty_shared_state.eventBus.emit(`${key}:reconnected`)
@ -88,13 +88,13 @@ export async function createWebsockets() {
})
instance.on("error", (error) => {
//console.error(`[WS-API][${key}] Error`, error)
console.error(`[WS-API][${key}] Error`, error)
globalThis.__comty_shared_state.eventBus.emit(`${key}:error`, error)
})
instance.onAny((event, ...args) => {
//console.debug(`[WS-API][${key}] Event (${event})`, ...args)
console.debug(`[WS-API][${key}] Event (${event})`, ...args)
globalThis.__comty_shared_state.eventBus.emit(`${key}:${event}`, ...args)
})

View File

@ -51,7 +51,7 @@ export default class AuthModel {
static async logout() {
await SessionModel.destroyCurrentSession()
SessionModel.removeToken()
await SessionModel.removeToken()
__comty_shared_state.eventBus.emit("auth:logout_success")
}
@ -92,6 +92,29 @@ export default class AuthModel {
return response
}
/**
* Verifies the given token and returns the user data associated with it.
*
* @param {string} [token] - The token to verify. If not provided, the stored token is used.
* @return {Promise<Object>} A Promise that resolves with the user data if the token is valid, or false if the token is invalid.
* @throws {Error} Throws an error if there was an issue with the request.
*/
static async authToken(token) {
if (!token) {
token = await SessionModel.token
}
const response = await request({
method: "POST",
url: "/auth/token",
data: {
token: token
}
})
return response.data
}
/**
* Validates the existence of a username by making a GET request to the `/auth/{username}/exists` endpoint.
*
@ -165,4 +188,44 @@ export default class AuthModel {
return data
}
/**
* Activates a user account using the provided activation code.
*
* @param {string} user_id - The ID of the user to activate.
* @param {string} code - The activation code sent to the user's email.
* @return {Promise<Object>} A promise that resolves with the response data after activation.
* @throws {Error} Throws an error if the activation process fails.
*/
static async activateAccount(user_id, code) {
const { data } = await request({
method: "post",
url: "/auth/activate",
data: {
code: code,
user_id: user_id,
}
})
return data
}
/**
* Resends the activation code to the user.
*
* @return {Promise<Object>} A promise that resolves with the response data after sending the activation code.
* @throws {Error} Throws an error if the resend activation code process fails.
* @param user_id
*/
static async resendActivationCode(user_id) {
const { data } = await request({
method: "post",
url: "/auth/resend-activation-code",
data: {
user_id: user_id,
}
})
return data
}
}

View File

@ -1,7 +1,13 @@
import request from "../../request"
import SessionModel from "../session"
export default class ChatsService {
/**
* Retrieves the chat history for a given chat ID.
*
* @param {string} chat_id - The ID of the chat.
* @return {Promise<Object>} The chat history data.
* @throws {Error} If the chat_id is not provided.
*/
static async getChatHistory(chat_id) {
if (!chat_id) {
throw new Error("chat_id is required")
@ -15,6 +21,11 @@ export default class ChatsService {
return data
}
/**
* Retrieves the recent chats for the current user.
*
* @return {Promise<Object>} The chat history data.
*/
static async getRecentChats() {
const { data } = await request({
method: "GET",

View File

@ -0,0 +1,14 @@
import request from "../../../request"
export default async ({ limit = 100, offset = 0 } = {}) => {
const response = await request({
method: "GET",
url: "/music/my/folder",
params: {
limit: limit,
offset: offset
}
})
return response.data
}

View File

@ -0,0 +1,33 @@
import request from "../../../request"
const typeToNamespace = {
track: "tracks",
//playlist: "playlists",
//release: "releases",
}
export default async (type, track_id) => {
if (!type) {
throw new Error("type is required")
}
if (!track_id) {
throw new Error("track_id is required")
}
type = type.toLowerCase()
type = typeToNamespace[type]
if (!type) {
throw new Error(`Unsupported type: ${type}`)
}
const response = await request({
method: "GET",
url: `/music/${type}/${track_id}/is_favourite`,
})
// @ts-ignore
return response.data
}

View File

@ -1,10 +1,10 @@
import request from "../../request"
import pmap from "p-map"
import SyncModel from "../sync"
import Getters from "./getters"
import Setters from "./setters"
export default class MusicModel {
public static Getters = Getters
public static Setters = Setters
/**
* Performs a search based on the provided keywords, with optional parameters for limiting the number of results and pagination.
*
@ -96,248 +96,23 @@ export default class MusicModel {
public static getTrackLyrics = Getters.trackLyrics
//!INCOMPLETE
public static putTrackLyrics = Setters.putTrackLyrics
/**
* Retrieves favorite tracks based on specified parameters.
* Create or modify a track.
*
* @param {Object} options - The options for retrieving favorite tracks.
* @param {boolean} options.useTidal - Whether to use Tidal for retrieving tracks. Defaults to false.
* @param {number} options.limit - The maximum number of tracks to retrieve.
* @param {number} options.offset - The offset from which to start retrieving tracks.
* @return {Promise<Object>} - An object containing the total length of the tracks and the retrieved tracks.
* @param {object} TrackManifest - The track manifest.
* @return {Promise<Object>} The result track data.
*/
static async getFavoriteTracks({ useTidal = false, limit, offset }) {
let result = []
let limitPerRequesters = limit
if (useTidal) {
limitPerRequesters = limitPerRequesters / 2
}
const requesters = [
async () => {
let { data } = await request({
method: "GET",
url: `/music/tracks/liked`,
params: {
limit: limitPerRequesters,
offset,
},
})
return data
},
async () => {
if (!useTidal) {
return {
total_length: 0,
tracks: [],
}
}
const tidalResult = await SyncModel.tidalCore.getMyFavoriteTracks({
limit: limitPerRequesters,
offset,
})
return tidalResult
},
]
result = await pmap(
requesters,
async requester => {
const data = await requester()
return data
},
{
concurrency: 3,
},
)
let total_length = 0
result.forEach(result => {
total_length += result.total_length
})
let tracks = result.reduce((acc, cur) => {
return [...acc, ...cur.tracks]
}, [])
tracks = tracks.sort((a, b) => {
return b.liked_at - a.liked_at
})
return {
total_length,
tracks,
}
}
public static putTrack = Setters.putTrack
/**
* Retrieves favorite playlists based on the specified parameters.
* Create or modify a release.
*
* @param {Object} options - The options for retrieving favorite playlists.
* @param {number} options.limit - The maximum number of playlists to retrieve. Default is 50.
* @param {number} options.offset - The offset of playlists to retrieve. Default is 0.
* @param {Object} options.services - The services to include for retrieving playlists. Default is an empty object.
* @param {string} options.keywords - The keywords to filter playlists by.
* @return {Promise<Object>} - An object containing the total length of the playlists and the playlist items.
* @param {object} ReleaseManifest - The release manifest.
* @return {Promise<Object>} The result release data.
*/
static async getFavoritePlaylists({ limit = 50, offset = 0, services = {}, keywords } = {}) {
let result = []
let limitPerRequesters = limit
const requesters = [
async () => {
return await MusicModel.getMyReleases(keywords)
},
]
if (services["tidal"] === true) {
limitPerRequesters = limitPerRequesters / (requesters.length + 1)
requesters.push(async () => {
const _result = await SyncModel.tidalCore.getMyFavoritePlaylists({
limit: limitPerRequesters,
offset,
})
return _result
})
}
result = await pmap(
requesters,
async requester => {
const data = await requester()
return data
},
{
concurrency: 3,
},
)
// calculate total length
let total_length = 0
result.forEach(result => {
total_length += result.total_length
})
// reduce items
let items = result.reduce((acc, cur) => {
return [...acc, ...cur.items]
}, [])
// sort by created_at
items = items.sort((a, b) => {
return new Date(b.created_at) - new Date(a.created_at)
})
return {
total_length: total_length,
items,
}
}
/**
* Creates a new playlist.
*
* @param {object} payload - The payload containing the data for the new playlist.
* @return {Promise<Object>} The new playlist data.
*/
static async newPlaylist(payload) {
const { data } = await request({
method: "POST",
url: `/playlists/new`,
data: payload,
})
return data
}
/**
* Updates a playlist item in the specified playlist.
*
* @param {string} playlist_id - The ID of the playlist to update.
* @param {object} item - The updated playlist item to be added.
* @return {Promise<Object>} - The updated playlist item.
*/
static async putPlaylistItem(playlist_id, item) {
const response = await request({
method: "PUT",
url: `/playlists/${playlist_id}/items`,
data: item,
})
return response.data
}
/**
* Delete a playlist item.
*
* @param {string} playlist_id - The ID of the playlist.
* @param {string} item_id - The ID of the item to delete.
* @return {Promise<Object>} The data returned by the server after the item is deleted.
*/
static async deletePlaylistItem(playlist_id, item_id) {
const response = await request({
method: "DELETE",
url: `/playlists/${playlist_id}/items/${item_id}`,
})
return response.data
}
/**
* Deletes a playlist.
*
* @param {number} playlist_id - The ID of the playlist to be deleted.
* @return {Promise<Object>} The response data from the server.
*/
static async deletePlaylist(playlist_id) {
const response = await request({
method: "DELETE",
url: `/playlists/${playlist_id}`,
})
return response.data
}
/**
* Execute a PUT request to update or create a release.
*
* @param {object} payload - The payload data.
* @return {Promise<Object>} The response data from the server.
*/
static async putRelease(payload) {
const response = await request({
method: "PUT",
url: `/releases/release`,
data: payload
})
return response.data
}
public static putRelease = Setters.putRelease
/**
* Deletes a release by its ID.
@ -345,72 +120,30 @@ export default class MusicModel {
* @param {string} id - The ID of the release to delete.
* @return {Promise<Object>} - A Promise that resolves to the data returned by the API.
*/
static async deleteRelease(id) {
const response = await request({
method: "DELETE",
url: `/releases/${id}`
})
return response.data
}
public static deleteRelease = Setters.deleteRelease
/**
* Refreshes the track cache for a given track ID.
* Retrieves the favourite tracks of the current user.
*
* @param {string} track_id - The ID of the track to refresh the cache for.
* @throws {Error} If track_id is not provided.
* @return {Promise<Object>} The response data from the API call.
* @return {Promise<Object>} The favorite tracks data.
*/
static async refreshTrackCache(track_id) {
if (!track_id) {
throw new Error("Track ID is required")
}
const response = await request({
method: "POST",
url: `/tracks/${track_id}/refresh-cache`,
})
return response.data
}
public static getFavouriteTracks = null
/**
* Toggles the like status of a track.
* Retrieves the favourite tracks/playlists/releases of the current user.
*
* @param {Object} manifest - The manifest object containing track information.
* @param {boolean} to - The like status to toggle (true for like, false for unlike).
* @throws {Error} Throws an error if the manifest is missing.
* @return {Object} The response data from the API.
* @return {Promise<Object>} The favorite playlists data.
*/
static async toggleTrackLike(manifest, to) {
if (!manifest) {
throw new Error("Manifest is required")
}
public static getFavouriteFolder = Getters.favouriteFolder
console.log(`Toggling track ${manifest._id} like status to ${to}`)
/**
* Toggles the favourite status of a track, playlist or folder.
*
* @param {string} track_id - The ID of the track to toggle the favorite status.
* @throws {Error} If the track_id is not provided.
* @return {Promise<Object>} The response data after toggling the favorite status.
*/
public static toggleItemFavourite = Setters.toggleItemFavourite
const track_id = manifest._id
switch (manifest.service) {
case "tidal": {
const response = await SyncModel.tidalCore.toggleTrackLike({
track_id,
to,
})
return response
}
default: {
const response = await request({
method: to ? "POST" : "DELETE",
url: `/tracks/${track_id}/like`,
params: {
service: manifest.service
}
})
return response.data
}
}
}
public static isItemFavourited = Getters.isItemFavourited
}

View File

@ -0,0 +1,11 @@
import request from "../../../request"
export default async (release_id) => {
const response = await request({
method: "delete",
url: `/music/releases/${release_id}`,
})
// @ts-ignore
return response.data
}

View File

@ -0,0 +1,36 @@
async function exportObjs() {
if (window) {
let paths = {
...import.meta.glob("./**.ts"),
...import.meta.glob("./**.js"),
}
let fns = {}
for (const path in paths) {
const name = path.split("/").pop().replace(".ts", "").replace(".js", "")
const fn = await paths[path]()
fns[name] = fn.default
}
return fns
} else {
let objs = {}
const dirs = fs.readdirSync(__dirname).filter(file => file !== "index.js")
const fs = require("fs")
const path = require("path")
dirs.forEach((file) => {
const model = require(path.join(__dirname, file)).default
objs[file.replace(".js", "")] = model
})
return objs
}
}
export default await exportObjs()

View File

@ -0,0 +1,12 @@
import request from "../../../request"
export default async (release) => {
const response = await request({
method: "PUT",
url: "/music/releases",
data: release,
})
// @ts-ignore
return response.data
}

View File

@ -0,0 +1,12 @@
import request from "../../../request"
export default async (track) => {
const response = await request({
method: "PUT",
url: "/music/tracks",
data: track,
})
// @ts-ignore
return response.data
}

View File

@ -0,0 +1,12 @@
import request from "../../../request"
export default async (track_id, data) => {
const response = await request({
method: "put",
url: `/music/lyrics/${track_id}`,
data: data,
})
// @ts-ignore
return response.data
}

View File

@ -0,0 +1,36 @@
import request from "../../../request"
const typeToNamespace = {
track: "tracks",
//playlist: "playlists",
//release: "releases",
}
export default async (type, track_id, to) => {
if (!type) {
throw new Error("type is required")
}
if (!track_id) {
throw new Error("track_id is required")
}
type = type.toLowerCase()
type = typeToNamespace[type]
if (!type) {
throw new Error(`Unsupported type: ${type}`)
}
const response = await request({
method: "post",
url: `/music/${type}/${track_id}/favourite`,
data: {
to: to,
}
})
// @ts-ignore
return response.data
}

View File

@ -71,10 +71,14 @@ export default class NFCModel {
throw new Error("Payload is required")
}
if (window) {
payload.origin = window.location.host
}
const { data } = await request({
method: "POST",
url: `/nfc/tag/register/${serial}`,
data: payload
data: payload,
})
return data

View File

@ -0,0 +1,17 @@
import request from "../../request"
export default class PaymentsModel {
/**
* Fetches the current balance from the server.
*
* @return {object} The balance data received from the server.
*/
static async fetchBalance() {
const response = await request({
method: "GET",
url: "/payments/balance",
})
return response.data.balance
}
}

View File

@ -254,4 +254,65 @@ export default class Post {
}
static deletePost = Post.delete
/**
* Votes for a poll with the given post ID and option ID.
*
* @param {Object} options - The options for voting.
* @param {string} options.post_id - The ID of the post to vote for.
* @param {string} options.option_id - The ID of the option to vote for.
* @throws {Error} If the post_id or option_id is not provided.
* @return {Promise<Object>} The response data after voting.
*/
static async votePoll({ post_id, option_id }) {
if (!post_id) {
throw new Error("post_id is required")
}
if (!option_id) {
throw new Error("option_id is required")
}
const { data } = await request({
method: "POST",
url: `/posts/${post_id}/vote_poll/${option_id}`,
})
return data
}
/**
* Deletes a vote for a poll with the given post ID and option ID.
*
* @param {Object} options - The options for deleting a vote.
* @param {string} options.post_id - The ID of the post to delete the vote from.
* @param {string} options.option_id - The ID of the option to delete the vote from.
* @throws {Error} If the post_id or option_id is not provided.
* @return {Promise<Object>} The response data after deleting the vote.
*/
static async deleteVotePoll({ post_id, option_id }) {
if (!post_id) {
throw new Error("post_id is required")
}
if (!option_id) {
throw new Error("option_id is required")
}
const { data } = await request({
method: "DELETE",
url: `/posts/${post_id}/vote_poll/${option_id}`,
})
return data
}
static async getTrendings() {
const { data } = await request({
method: "GET",
url: `/posts/trendings`,
})
return data
}
}

View File

@ -119,25 +119,6 @@ export default class Session {
return response.data
}
/**
* Retrieves the token validation data from the server.
*
* @return {Promise<Object>} The token validation data.
*/
static async getTokenValidation() {
const session = await Session.token
const response = await request({
method: "get",
url: "/sessions/validate",
data: {
session: session
}
})
return response.data
}
/**
* Destroys the current session by deleting it from the server.
*

View File

@ -0,0 +1,96 @@
import axios from "axios"
import SessionService from "../session"
export default class Streaming {
static apiHostname = process.env.NODE_ENV === "production" ? "https://live.ragestudio.net" : "https://fr01.ragestudio.net:8035"
static get base() {
const baseInstance = axios.create({
baseURL: Streaming.apiHostname,
headers: {
"Accept": "application/json",
"ngrok-skip-browser-warning": "any"
}
})
if (SessionService.token) {
baseInstance.defaults.headers.common["Authorization"] = `Bearer ${SessionService.token}`
}
return baseInstance
}
static async serverInfo() {
const { data } = await Streaming.base({
method: "get",
})
return {
...data,
hostname: Streaming.apiHostname
}
}
static async getOwnProfiles() {
const { data } = await Streaming.base({
method: "get",
url: "/streaming/profiles/self",
})
return data
}
static async getProfile({ profile_id }) {
if (!profile_id) {
return null
}
const { data } = await Streaming.base({
method: "get",
url: `/streaming/profiles/${profile_id}`,
})
return data
}
static async getStream({ profile_id }) {
if (!profile_id) {
return null
}
const { data } = await Streaming.base({
method: "get",
url: `/streaming/${profile_id}`,
})
return data
}
static async deleteProfile({ profile_id }) {
if (!profile_id) {
return null
}
const { data } = await Streaming.base({
method: "delete",
url: `/streaming/profiles/${profile_id}`,
})
return data
}
static async createOrUpdateStream(update) {
const { data } = await Streaming.base({
method: "put",
url: `/streaming/profiles/self`,
data: update,
})
return data
}
static async getConnectionStatus({ profile_id }) {
console.warn("getConnectionStatus() | Not implemented")
return false
}
}

View File

@ -48,9 +48,7 @@ export default class User {
const response = await request({
method: "POST",
url: "/users/self/update",
data: {
update: payload,
},
data: payload,
})
return response.data

View File

@ -1,22 +1,6 @@
function getCurrentHostname() {
if (typeof window === "undefined") {
return "localhost"
}
return window?.location?.hostname ?? "localhost"
}
function getCurrentProtocol() {
if (typeof window === "undefined") {
return "http"
}
return window?.location?.protocol ?? "http:"
}
const envOrigins = {
"development": `${getCurrentProtocol()}//${getCurrentHostname()}:9000`,
"indev": "https://indev_api.comty.app",
"development": `${location.origin}/api`,
"indev": "https://indev.comty.app/api",
"production": "https://api.comty.app",
}
@ -38,6 +22,14 @@ export default {
{
namespace: "chats",
path: "/chats",
},
{
namespace: "music",
path: "/music",
}
// {
// namespace: "payments",
// path: "/payments",
// }
]
}