mirror of
https://github.com/spacebarchat/server.git
synced 2026-04-26 10:57:31 +00:00
Initial work on quests
This commit is contained in:
@@ -51,6 +51,8 @@ export const NO_AUTHORIZATION_ROUTES = [
|
||||
"GET /policies/instance/",
|
||||
// Oauth callback
|
||||
"/oauth2/callback",
|
||||
// get quest config
|
||||
/^GET \/quests\/\d+/,
|
||||
// Asset delivery
|
||||
/^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/,
|
||||
/^(GET|HEAD) \/guilds\/\d+\/shield\.svg/,
|
||||
|
||||
86
src/api/routes/quests/#quest_id/claim-reward.ts
Normal file
86
src/api/routes/quests/#quest_id/claim-reward.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { EntitlementSpecialSourceType, EntitlementType, QuestClaimRewardRequestSchema, QuestClaimRewardResponseSchema } from "@spacebar/schemas/quests";
|
||||
import { emitEvent } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({
|
||||
description:
|
||||
"Claims the quest's rewards, setting the completed_at and claimed_at fields of the quest user status to the current timestamp. Fires a Quests User Status Update Gateway event.",
|
||||
requestBody: "QuestClaimRewardRequestSchema",
|
||||
responses: {
|
||||
200: {
|
||||
body: "QuestClaimRewardResponseSchema",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { quest_id } = req.params;
|
||||
const { location, platform } = req.body as QuestClaimRewardRequestSchema;
|
||||
|
||||
// TODO: implement
|
||||
console.debug(`POST /quests/${quest_id}/claim-reward is incomplete`);
|
||||
|
||||
await emitEvent({
|
||||
event: "QUESTS_USER_STATUS_UPDATE",
|
||||
data: {
|
||||
user_status: {
|
||||
claimed_at: null,
|
||||
claimed_tier: null,
|
||||
completed_at: null,
|
||||
dismissed_quest_content: 0,
|
||||
enrolled_at: new Date().toISOString(),
|
||||
last_stream_heartbeat_at: null,
|
||||
progress: {},
|
||||
quest_id,
|
||||
stream_progress_seconds: 0,
|
||||
user_id: req.user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
res.json({
|
||||
claimed_at: new Date().toISOString(),
|
||||
entitlement_expiration_metadata: {},
|
||||
entitlements: [
|
||||
{
|
||||
id: "1453600390154162249",
|
||||
sku_id: "1287881739531976815",
|
||||
application_id: "1287870191526613112",
|
||||
user_id: req.user.id,
|
||||
deleted: false,
|
||||
starts_at: null,
|
||||
ends_at: null,
|
||||
type: EntitlementType.QUEST_REWARD,
|
||||
tenant_metadata: {},
|
||||
source_type: EntitlementSpecialSourceType.QUEST_REWARD,
|
||||
gift_code_flags: 0, // PAYMENT_SOURCE_REQUIRED, todo: make a bitfield enum
|
||||
promotion_id: null,
|
||||
},
|
||||
],
|
||||
errors: [],
|
||||
} as QuestClaimRewardResponseSchema);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
62
src/api/routes/quests/#quest_id/enroll.ts
Normal file
62
src/api/routes/quests/#quest_id/enroll.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { QuestEnrollRequestSchema, QuestUserStatusSchema } from "@spacebar/schemas";
|
||||
import { emitEvent } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({
|
||||
description: "Accepts a quest and returns a quest user status object. Fires a Quests User Status Update Gateway event.",
|
||||
requestBody: "QuestEnrollRequestSchema",
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { quest_id } = req.params;
|
||||
const { location } = req.body as QuestEnrollRequestSchema;
|
||||
|
||||
// TODO: implement
|
||||
console.debug(`POST /quests/${quest_id}/enroll is incomplete`);
|
||||
|
||||
const status: QuestUserStatusSchema = {
|
||||
claimed_at: null,
|
||||
claimed_tier: null,
|
||||
completed_at: null,
|
||||
dismissed_quest_content: 0,
|
||||
enrolled_at: new Date().toISOString(),
|
||||
last_stream_heartbeat_at: null,
|
||||
progress: {},
|
||||
quest_id,
|
||||
stream_progress_seconds: 0,
|
||||
user_id: req.user.id,
|
||||
};
|
||||
|
||||
await emitEvent({
|
||||
event: "QUESTS_USER_STATUS_UPDATE",
|
||||
data: {
|
||||
user_status: status,
|
||||
},
|
||||
});
|
||||
|
||||
res.json(status);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
141
src/api/routes/quests/#quest_id/index.ts
Normal file
141
src/api/routes/quests/#quest_id/index.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { QuestAssignmentMethod, QuestConfigResponseSchema, QuestEventType, QuestFeature, QuestPlatformType, QuestRewardType, QuestSharePolicy } from "@spacebar/schemas";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
description: "Returns a quest config object for the specified quest. Quest must be currently active.",
|
||||
responses: {
|
||||
200: {
|
||||
body: "QuestConfigResponseSchema",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { quest_id } = req.params;
|
||||
// TODO: implement
|
||||
console.debug(`GET /quests/${quest_id} is incomplete`);
|
||||
|
||||
// just a dummy response for now
|
||||
res.json({
|
||||
id: "1333839522189938740",
|
||||
config_version: 2,
|
||||
starts_at: "2025-07-14T14:00:00+00:00",
|
||||
expires_at: "2099-08-15T23:00:00+00:00",
|
||||
features: [QuestFeature.QUEST_BAR_V2, QuestFeature.REWARD_HIGHLIGHTING, QuestFeature.DISMISSAL_SURVEY, QuestFeature.QUESTS_CDN, QuestFeature.PACING_CONTROLLER],
|
||||
application: {
|
||||
link: "https://spacebar.chat",
|
||||
id: "545364944258990091",
|
||||
name: "Spacebar",
|
||||
},
|
||||
colors: {
|
||||
primary: "#5865F2",
|
||||
secondary: "#000000",
|
||||
},
|
||||
assets: {
|
||||
hero: "quests/1333839522189938740/orbs_quest_card_banner_4.jpeg",
|
||||
hero_video: null,
|
||||
quest_bar_hero: "quests/1333839522189938740/orbs_quest_bar.png",
|
||||
quest_bar_hero_video: null,
|
||||
game_tile: "discord_game_tile.png",
|
||||
logotype: "discord_logo.png",
|
||||
game_tile_light: "quests/1333839522189938740/1417603112168067182.png",
|
||||
game_tile_dark: "quests/1333839522189938740/1417603112742551603.png",
|
||||
logotype_light: "quests/1333839522189938740/1417603113304719540.png",
|
||||
logotype_dark: "quests/1333839522189938740/1417603113791131668.png",
|
||||
},
|
||||
messages: {
|
||||
quest_name: "Spacebar Bars Intro",
|
||||
game_title: "Spacebar",
|
||||
game_publisher: "Spacebar",
|
||||
},
|
||||
task_config_v2: {
|
||||
tasks: {
|
||||
[QuestEventType.WATCH_VIDEO]: {
|
||||
type: QuestEventType.WATCH_VIDEO,
|
||||
target: 31,
|
||||
assets: {
|
||||
video: {
|
||||
url: "quests/1410358070831480904/1420884840815005717_1080.mp4",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
video_low_res: {
|
||||
url: "quests/1410358070831480904/1420884840815005717_720.mp4",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
video_hls: {
|
||||
url: "quests/1410358070831480904/1420884840815005717.m3u8",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
video_title: "Intro to Spacebar Bars",
|
||||
// video_end_cta_title: "Learn more about Bars",
|
||||
// video_end_cta_subtitle: "Head to our Help Center for more information.",
|
||||
},
|
||||
},
|
||||
},
|
||||
join_operator: "or",
|
||||
},
|
||||
rewards_config: {
|
||||
assignment_method: QuestAssignmentMethod.ALL,
|
||||
rewards: [
|
||||
{
|
||||
type: QuestRewardType.VIRTUAL_CURRENCY,
|
||||
sku_id: "1287881739531976815",
|
||||
messages: {
|
||||
name: "150 Bars",
|
||||
name_with_article: "150 Bars",
|
||||
redemption_instructions_by_platform: {
|
||||
[QuestPlatformType.CROSS_PLATFORM]: "Default",
|
||||
},
|
||||
},
|
||||
orb_quantity: 150,
|
||||
},
|
||||
],
|
||||
rewards_expire_at: "2099-02-01T00:00:27+00:00",
|
||||
platforms: [QuestPlatformType.CROSS_PLATFORM],
|
||||
},
|
||||
share_policy: QuestSharePolicy.SHAREABLE_EVERYWHERE,
|
||||
cta_config: {
|
||||
link: "https://spacebar.chat",
|
||||
button_label: "Learn More",
|
||||
subtitle: "Head to our Help Center for more information.",
|
||||
},
|
||||
} as QuestConfigResponseSchema);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
50
src/api/routes/quests/#quest_id/reward-code.ts
Normal file
50
src/api/routes/quests/#quest_id/reward-code.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { QuestPlatformType, QuestRewardCodeResponseSchema } from "@spacebar/schemas/quests";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
description: "Retrieves the reward code for the specified platform. Returns a quest reward code object on success.",
|
||||
responses: {
|
||||
200: {
|
||||
body: "QuestRewardCodeResponseSchema",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { quest_id } = req.params;
|
||||
|
||||
// TODO: implement
|
||||
console.debug(`GET /quests/${quest_id}/reward-code is incomplete`);
|
||||
|
||||
res.json({
|
||||
quest_id,
|
||||
claimed_at: "2025-08-01T12:00:00+00:00",
|
||||
code: "REWARD-CODE-1234",
|
||||
platform: QuestPlatformType.CROSS_PLATFORM,
|
||||
user_id: req.user.id,
|
||||
} as QuestRewardCodeResponseSchema);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
68
src/api/routes/quests/#quest_id/video-progress.ts
Normal file
68
src/api/routes/quests/#quest_id/video-progress.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { QuestUserStatusSchema, QuestVideoProgressRequestSchema } from "@spacebar/schemas/quests";
|
||||
import { emitEvent } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({
|
||||
description:
|
||||
"Tells the server to update the value field of the current video task. Used for keeping track of how long the video has been watched for, and for checking if the user has met the task duration requirement. Returns a quest user status object on success. Fires a Quests User Status Update Gateway event.",
|
||||
requestBody: "QuestVideoProgressRequestSchema",
|
||||
responses: {
|
||||
200: {
|
||||
body: "QuestVideoProgressResponseSchema",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { quest_id } = req.params;
|
||||
const { timestamp } = req.body as QuestVideoProgressRequestSchema;
|
||||
|
||||
// TODO: implement
|
||||
console.debug(`POST /quests/${quest_id}/video-progress is incomplete`);
|
||||
|
||||
const status: QuestUserStatusSchema = {
|
||||
claimed_at: null,
|
||||
claimed_tier: null,
|
||||
completed_at: null,
|
||||
dismissed_quest_content: 0,
|
||||
enrolled_at: new Date().toISOString(),
|
||||
last_stream_heartbeat_at: null,
|
||||
progress: {},
|
||||
quest_id,
|
||||
stream_progress_seconds: 0,
|
||||
user_id: req.user.id,
|
||||
};
|
||||
|
||||
await emitEvent({
|
||||
event: "QUESTS_USER_STATUS_UPDATE",
|
||||
data: {
|
||||
user_status: status,
|
||||
},
|
||||
});
|
||||
|
||||
res.json(status);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
340
src/api/routes/quests/@me.ts
Normal file
340
src/api/routes/quests/@me.ts
Normal file
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { ClaimedQuestsResponseSchema, QuestAssignmentMethod, QuestEventType, QuestFeature, QuestPlatformType, QuestRewardType, QuestSharePolicy } from "@spacebar/schemas";
|
||||
import { QuestsResponseSchema } from "@spacebar/schemas/responses";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
description: "Returns information on the current quests for the current user.",
|
||||
responses: {
|
||||
200: {
|
||||
body: "QuestsResponseSchema",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
// TODO: pull quests from config
|
||||
|
||||
console.debug("GET /quests/@me is incomplete");
|
||||
|
||||
res.json({
|
||||
excluded_quests: [],
|
||||
quest_enrollment_blocked_until: null,
|
||||
quests: [
|
||||
{
|
||||
id: "1333839522189938740",
|
||||
config: {
|
||||
id: "1333839522189938740",
|
||||
config_version: 2,
|
||||
starts_at: "2025-07-14T14:00:00+00:00",
|
||||
expires_at: "2099-08-15T23:00:00+00:00",
|
||||
features: [
|
||||
QuestFeature.QUEST_BAR_V2,
|
||||
QuestFeature.REWARD_HIGHLIGHTING,
|
||||
QuestFeature.DISMISSAL_SURVEY,
|
||||
QuestFeature.QUESTS_CDN,
|
||||
QuestFeature.PACING_CONTROLLER,
|
||||
],
|
||||
application: {
|
||||
link: "https://spacebar.chat",
|
||||
id: "545364944258990091",
|
||||
name: "Spacebar",
|
||||
},
|
||||
colors: {
|
||||
primary: "#5865F2",
|
||||
secondary: "#000000",
|
||||
},
|
||||
assets: {
|
||||
hero: "quests/1333839522189938740/orbs_quest_card_banner_4.jpeg",
|
||||
hero_video: null,
|
||||
quest_bar_hero: "quests/1333839522189938740/orbs_quest_bar.png",
|
||||
quest_bar_hero_video: null,
|
||||
game_tile: "discord_game_tile.png",
|
||||
logotype: "discord_logo.png",
|
||||
game_tile_light: "quests/1333839522189938740/1417603112168067182.png",
|
||||
game_tile_dark: "quests/1333839522189938740/1417603112742551603.png",
|
||||
logotype_light: "quests/1333839522189938740/1417603113304719540.png",
|
||||
logotype_dark: "quests/1333839522189938740/1417603113791131668.png",
|
||||
},
|
||||
messages: {
|
||||
quest_name: "Spacebar Bars Intro",
|
||||
game_title: "Spacebar",
|
||||
game_publisher: "Spacebar",
|
||||
},
|
||||
task_config_v2: {
|
||||
tasks: {
|
||||
[QuestEventType.WATCH_VIDEO]: {
|
||||
type: QuestEventType.WATCH_VIDEO,
|
||||
target: 31,
|
||||
assets: {
|
||||
video: {
|
||||
url: "quests/1410358070831480904/1420884840815005717_1080.mp4",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
video_low_res: {
|
||||
url: "quests/1410358070831480904/1420884840815005717_720.mp4",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
video_hls: {
|
||||
url: "quests/1410358070831480904/1420884840815005717.m3u8",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
video_title: "Intro to Spacebar Bars",
|
||||
// video_end_cta_title: "Learn more about Bars",
|
||||
// video_end_cta_subtitle: "Head to our Help Center for more information.",
|
||||
},
|
||||
},
|
||||
},
|
||||
join_operator: "or",
|
||||
},
|
||||
rewards_config: {
|
||||
assignment_method: QuestAssignmentMethod.ALL,
|
||||
rewards: [
|
||||
{
|
||||
type: QuestRewardType.VIRTUAL_CURRENCY,
|
||||
sku_id: "1287881739531976815",
|
||||
messages: {
|
||||
name: "150 Bars",
|
||||
name_with_article: "150 Bars",
|
||||
redemption_instructions_by_platform: {
|
||||
[QuestPlatformType.CROSS_PLATFORM]: "Default",
|
||||
},
|
||||
},
|
||||
orb_quantity: 150,
|
||||
},
|
||||
],
|
||||
rewards_expire_at: "2099-02-01T00:00:27+00:00",
|
||||
platforms: [QuestPlatformType.CROSS_PLATFORM],
|
||||
},
|
||||
share_policy: QuestSharePolicy.SHAREABLE_EVERYWHERE,
|
||||
cta_config: {
|
||||
link: "https://spacebar.chat",
|
||||
button_label: "Learn More",
|
||||
subtitle: "Head to our Help Center for more information.",
|
||||
},
|
||||
},
|
||||
// user_status: {
|
||||
// user_id: "498989696412549120",
|
||||
// quest_id: "1333839522189938740",
|
||||
// enrolled_at: "2025-07-14T21:20:04.640772+00:00",
|
||||
// completed_at: "2025-07-14T21:21:05.941315+00:00",
|
||||
// claimed_at: "2025-07-14T21:21:26.983570+00:00",
|
||||
// claimed_tier: null,
|
||||
// last_stream_heartbeat_at: null,
|
||||
// stream_progress_seconds: 0,
|
||||
// dismissed_quest_content: 0,
|
||||
// progress: {
|
||||
// WATCH_VIDEO: {
|
||||
// value: 31,
|
||||
// event_name: "WATCH_VIDEO",
|
||||
// updated_at: "2025-07-14T21:21:05.941317+00:00",
|
||||
// completed_at: "2025-07-14T21:21:05.941315+00:00",
|
||||
// heartbeat: null,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
user_status: null,
|
||||
},
|
||||
{
|
||||
id: "1410358070831480904",
|
||||
config: {
|
||||
id: "1410358070831480904",
|
||||
config_version: 2,
|
||||
starts_at: "2025-11-17T16:30:27+00:00",
|
||||
expires_at: "2099-01-02T00:00:27+00:00",
|
||||
features: [
|
||||
QuestFeature.QUEST_BAR_V2,
|
||||
QuestFeature.REWARD_HIGHLIGHTING,
|
||||
QuestFeature.DISMISSAL_SURVEY,
|
||||
QuestFeature.MOBILE_QUEST_DOCK,
|
||||
QuestFeature.QUESTS_CDN,
|
||||
QuestFeature.PACING_CONTROLLER,
|
||||
QuestFeature.VIDEO_QUEST_FORCE_HLS_VIDEO,
|
||||
QuestFeature.VIDEO_QUEST_FORCE_END_CARD_CTA_SWAP,
|
||||
QuestFeature.MOBILE_ONLY_QUEST_PUSH_TO_MOBILE,
|
||||
],
|
||||
application: {
|
||||
link: "https://spacebar.chat",
|
||||
id: "545364944258990091",
|
||||
name: "Spacebar",
|
||||
},
|
||||
assets: {
|
||||
hero: "quests/1410358070831480904/1440427094801911919.jpg",
|
||||
hero_video: null,
|
||||
quest_bar_hero: "quests/1410358070831480904/1440368088196579358.jpg",
|
||||
quest_bar_hero_video: null,
|
||||
game_tile: "687100d5-5958-43aa-b221-dfe4f2b79e14.png",
|
||||
logotype: "0c58c77e-4ab4-482e-b784-ea4646b992ca.png",
|
||||
game_tile_light: "quests/1410358070831480904/1417602409018163210.png",
|
||||
game_tile_dark: "quests/1410358070831480904/1417602409668153406.png",
|
||||
logotype_light: "quests/1410358070831480904/1417602410142105680.png",
|
||||
logotype_dark: "quests/1410358070831480904/1417602410733375498.png",
|
||||
},
|
||||
colors: {
|
||||
primary: "#4752C4",
|
||||
secondary: "#000000",
|
||||
},
|
||||
messages: {
|
||||
quest_name: "Mobile Bars Intro",
|
||||
game_title: "Earn from Quests. Spend in Shop. Reward Your Play.",
|
||||
game_publisher: "Spacebar",
|
||||
},
|
||||
// task_config: {
|
||||
// type: 1,
|
||||
// join_operator: "or",
|
||||
// tasks: {
|
||||
// WATCH_VIDEO_ON_MOBILE: {
|
||||
// event_name: "WATCH_VIDEO_ON_MOBILE",
|
||||
// target: 31,
|
||||
// external_ids: [],
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
task_config_v2: {
|
||||
tasks: {
|
||||
[QuestEventType.WATCH_VIDEO_ON_MOBILE]: {
|
||||
type: QuestEventType.WATCH_VIDEO_ON_MOBILE,
|
||||
target: 31,
|
||||
assets: {
|
||||
video: {
|
||||
url: "quests/1410358070831480904/1420884840815005717_1080.mp4",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
video_low_res: {
|
||||
url: "quests/1410358070831480904/1420884840815005717_720.mp4",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
video_hls: {
|
||||
url: "quests/1410358070831480904/1420884840815005717.m3u8",
|
||||
width: 1080,
|
||||
height: 1920,
|
||||
thumbnail: "quests/1410358070831480904/1421253196549984267.png",
|
||||
caption: "quests/1410358070831480904/1410370389451866112.vtt",
|
||||
transcript: "quests/1410358070831480904/1410370413032374293.txt",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
video_title: "Intro to Spacebar Bars",
|
||||
// video_end_cta_title: "Learn more about Bars",
|
||||
// video_end_cta_subtitle: "Head to our Help Center for more information.",
|
||||
},
|
||||
},
|
||||
},
|
||||
join_operator: "or",
|
||||
},
|
||||
rewards_config: {
|
||||
assignment_method: QuestAssignmentMethod.ALL,
|
||||
rewards: [
|
||||
{
|
||||
type: QuestRewardType.VIRTUAL_CURRENCY,
|
||||
sku_id: "1287881739531976815",
|
||||
messages: {
|
||||
name: "150 Bars",
|
||||
name_with_article: "150 Bars",
|
||||
redemption_instructions_by_platform: {
|
||||
[QuestPlatformType.CROSS_PLATFORM]: "Default",
|
||||
},
|
||||
},
|
||||
orb_quantity: 150,
|
||||
},
|
||||
],
|
||||
rewards_expire_at: "2099-02-01T00:00:27+00:00",
|
||||
platforms: [QuestPlatformType.CROSS_PLATFORM],
|
||||
},
|
||||
share_policy: QuestSharePolicy.SHAREABLE_EVERYWHERE,
|
||||
cta_config: {
|
||||
link: "https://spacebar.chat",
|
||||
button_label: "Learn More",
|
||||
subtitle: "Head to our Help Center for more information.",
|
||||
},
|
||||
},
|
||||
// user_status: {
|
||||
// user_id: "498989696412549120",
|
||||
// quest_id: "1410358070831480904",
|
||||
// enrolled_at: "2025-11-19T04:17:07.950593+00:00",
|
||||
// completed_at: "2025-11-19T04:17:45.920547+00:00",
|
||||
// claimed_at: "2025-11-19T04:17:51.972621+00:00",
|
||||
// claimed_tier: null,
|
||||
// last_stream_heartbeat_at: null,
|
||||
// stream_progress_seconds: 0,
|
||||
// dismissed_quest_content: 0,
|
||||
// progress: {
|
||||
// WATCH_VIDEO_ON_MOBILE: {
|
||||
// value: 31,
|
||||
// event_name: "WATCH_VIDEO_ON_MOBILE",
|
||||
// updated_at: "2025-11-19T04:17:45.920548+00:00",
|
||||
// completed_at: "2025-11-19T04:17:45.920547+00:00",
|
||||
// heartbeat: null,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
user_status: null,
|
||||
targeted_content: [],
|
||||
preview: false,
|
||||
},
|
||||
],
|
||||
} as QuestsResponseSchema);
|
||||
},
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/claimed",
|
||||
route({
|
||||
description: "Returns information on the claimed quests for the current user.",
|
||||
responses: {
|
||||
200: {
|
||||
body: "ClaimedQuestsResponseSchema",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
// TODO: implement
|
||||
res.json({
|
||||
quests: [],
|
||||
} as ClaimedQuestsResponseSchema);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
63
src/api/routes/quests/decision.ts
Normal file
63
src/api/routes/quests/decision.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { QuestPlacementArea, QuestPlacementResponseSchema } from "@spacebar/schemas";
|
||||
import { FieldErrors } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
description: "Returns the sponsored quest that should be shown to the user in a specific placement.",
|
||||
query: {
|
||||
placement: {
|
||||
type: "number",
|
||||
description: "The quest placement area to get the quest for",
|
||||
},
|
||||
client_heartbeat_session_id: {
|
||||
type: "string",
|
||||
description: "A client-generated UUID representing the current persisted analytics heartbeat",
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
body: "QuestPlacementResponseSchema",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { placement, client_heartbeat_session_id } = req.query;
|
||||
// check if placement is a valid QuestPlacementArea
|
||||
if (!Object.values(QuestPlacementArea).includes(placement as unknown as number)) {
|
||||
throw FieldErrors({
|
||||
placement: {
|
||||
code: "ENUM_TYPE_COERCE",
|
||||
message: req.t("common:field.ENUM_TYPE_COERCE", {
|
||||
value: placement,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
res.json({} as QuestPlacementResponseSchema);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
39
src/api/routes/users/@me/virtual-currency/balance.ts
Normal file
39
src/api/routes/users/@me/virtual-currency/balance.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
responses: {
|
||||
200: {
|
||||
body: "VirtualCurrencyResponseSchema",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
res.json({
|
||||
balance: req.user.currency,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user