Skip to main content
Learn how to integrate with DRIP’s powerful quest system to create engaging, gamified experiences for your community members.

Overview

DRIP’s quest system allows you to create structured challenges and tasks that members can complete to earn rewards. Quests can be simple one-time tasks or complex multi-step adventures.
Quests in DRIP are highly customizable and can integrate with external systems, making them perfect for building comprehensive gamification strategies.

Quest Structure

Understanding Quest Components

Components:
  • Quest: The overall challenge or objective
  • Tasks: Individual steps within the quest
  • Triggers: Events that activate task checking
  • Conditions: Requirements that must be met
  • Actions: What happens when tasks are completed

Reading Quest Data

Get Available Quests

async function getAvailableQuests(client, memberId = null) {
  const endpoint = memberId 
    ? `/realm/${client.realmId}/members/${memberId}/quests`
    : `/realm/${client.realmId}/quests`;
  
  const quests = await client.request('GET', endpoint);
  
  return quests.data.map(quest => ({
    id: quest.id,
    name: quest.name,
    description: quest.description,
    status: quest.status,
    tasks: quest.tasks?.length || 0,
    rewards: quest.rewards || [],
    startDate: quest.startDate,
    endDate: quest.endDate
  }));
}

// Get all quests in the realm
const allQuests = await getAvailableQuests(client);

// Get quests for a specific member (with progress)
const memberQuests = await getAvailableQuests(client, 'member123');

Get Quest Details

async function getQuestDetails(client, questId) {
  const quest = await client.request('GET', 
    `/realm/${client.realmId}/quests/${questId}`
  );
  
  return {
    id: quest.id,
    name: quest.name,
    description: quest.description,
    status: quest.status,
    tasks: quest.tasks.map(task => ({
      id: task.id,
      name: task.name,
      description: task.description,
      type: task.type,
      conditions: task.conditions,
      rewards: task.rewards,
      isCompleted: task.isCompleted
    })),
    totalRewards: quest.rewards,
    participants: quest.participants || 0,
    completionRate: quest.completionRate || 0
  };
}

Quest Progress Tracking

Check Member Quest Progress

async function getMemberQuestProgress(client, memberId, questId = null) {
  const endpoint = questId 
    ? `/realm/${client.realmId}/members/${memberId}/quests/${questId}`
    : `/realm/${client.realmId}/members/${memberId}/quests`;
  
  const progress = await client.request('GET', endpoint);
  
  if (questId) {
    // Single quest progress
    return {
      questId: progress.questId,
      status: progress.status,
      completedTasks: progress.completedTasks,
      totalTasks: progress.totalTasks,
      progress: progress.progress, // 0-1
      startedAt: progress.startedAt,
      completedAt: progress.completedAt,
      taskProgress: progress.tasks?.map(task => ({
        taskId: task.id,
        name: task.name,
        isCompleted: task.isCompleted,
        completedAt: task.completedAt
      }))
    };
  } else {
    // All quest progress for member
    return progress.data.map(quest => ({
      questId: quest.questId,
      questName: quest.questName,
      status: quest.status,
      progress: quest.progress,
      completedTasks: quest.completedTasks,
      totalTasks: quest.totalTasks
    }));
  }
}

// Usage
const allProgress = await getMemberQuestProgress(client, 'member123');
const specificProgress = await getMemberQuestProgress(client, 'member123', 'quest456');

Quest Analytics

class QuestAnalytics {
  constructor(client) {
    this.client = client;
  }

  async getQuestStats(questId) {
    const quest = await this.client.request('GET',
      `/realm/${this.client.realmId}/quests/${questId}/analytics`
    );

    return {
      totalParticipants: quest.totalParticipants,
      completedCount: quest.completedCount,
      completionRate: quest.completionRate,
      averageCompletionTime: quest.averageCompletionTime,
      taskCompletionRates: quest.taskCompletionRates,
      dropOffPoints: quest.dropOffPoints // Where people stop
    };
  }

  async getPopularQuests(limit = 10) {
    const quests = await this.client.request('GET',
      `/realm/${this.client.realmId}/quests/analytics/popular?limit=${limit}`
    );

    return quests.data.map(quest => ({
      id: quest.id,
      name: quest.name,
      participants: quest.participants,
      completionRate: quest.completionRate,
      averageRating: quest.averageRating
    }));
  }

  async getMemberQuestHistory(memberId) {
    const history = await this.client.request('GET',
      `/realm/${this.client.realmId}/members/${memberId}/quests/history`
    );

    return history.data.map(entry => ({
      questId: entry.questId,
      questName: entry.questName,
      status: entry.status,
      startedAt: entry.startedAt,
      completedAt: entry.completedAt,
      rewardsEarned: entry.rewardsEarned
    }));
  }
}

External Quest Integration

Triggering Quest Progress

For external integrations, you can manually trigger quest progress:
async function triggerQuestProgress(client, memberId, questId, taskId, data = {}) {
  const result = await client.request('POST',
    `/realm/${client.realmId}/members/${memberId}/quests/${questId}/tasks/${taskId}/trigger`,
    {
      triggerData: data,
      timestamp: new Date().toISOString()
    }
  );

  return result;
}

// Example: Trigger progress when member completes external action
await triggerQuestProgress(client, 'member123', 'quest456', 'task789', {
  actionType: 'external_purchase',
  amount: 99.99,
  productId: 'premium-subscription'
});

Custom Quest Conditions

Check if custom conditions are met:
async function checkCustomCondition(client, memberId, conditionType, conditionData) {
  switch (conditionType) {
    case 'external_api_check':
      return await checkExternalAPI(memberId, conditionData);
    
    case 'social_media_follow':
      return await checkSocialMediaFollow(memberId, conditionData);
    
    case 'website_visit':
      return await checkWebsiteVisit(memberId, conditionData);
    
    case 'email_verification':
      return await checkEmailVerification(memberId);
    
    default:
      return false;
  }
}

async function checkExternalAPI(memberId, conditionData) {
  try {
    const response = await fetch(conditionData.apiUrl, {
      headers: {
        'Authorization': `Bearer ${conditionData.apiKey}`,
        'Content-Type': 'application/json'
      }
    });
    
    const data = await response.json();
    
    // Check if condition is met based on API response
    return data.userStatus === conditionData.requiredStatus;
  } catch (error) {
    console.error('External API check failed:', error);
    return false;
  }
}

Building Quest-Driven Features

Quest Dashboard

Create a quest dashboard for members:
class QuestDashboard {
  constructor(client, memberId) {
    this.client = client;
    this.memberId = memberId;
  }

  async getDashboardData() {
    const [activeQuests, completedQuests, availableQuests] = await Promise.all([
      this.getActiveQuests(),
      this.getCompletedQuests(),
      this.getAvailableQuests()
    ]);

    return {
      activeQuests,
      completedQuests,
      availableQuests,
      stats: await this.getQuestStats()
    };
  }

  async getActiveQuests() {
    const quests = await getMemberQuestProgress(this.client, this.memberId);
    return quests.filter(quest => quest.status === 'active');
  }

  async getCompletedQuests() {
    const analytics = new QuestAnalytics(this.client);
    const history = await analytics.getMemberQuestHistory(this.memberId);
    return history.filter(quest => quest.status === 'completed');
  }

  async getAvailableQuests() {
    const allQuests = await getAvailableQuests(this.client);
    const memberQuests = await getMemberQuestProgress(this.client, this.memberId);
    
    const memberQuestIds = new Set(memberQuests.map(q => q.questId));
    
    return allQuests.filter(quest => 
      quest.status === 'active' && !memberQuestIds.has(quest.id)
    );
  }

  async getQuestStats() {
    const history = await new QuestAnalytics(this.client)
      .getMemberQuestHistory(this.memberId);
    
    const completed = history.filter(q => q.status === 'completed');
    const totalRewards = completed.reduce((sum, quest) => 
      sum + (quest.rewardsEarned?.points || 0), 0
    );

    return {
      totalCompleted: completed.length,
      totalStarted: history.length,
      completionRate: history.length > 0 ? completed.length / history.length : 0,
      totalRewardsEarned: totalRewards,
      currentStreak: await this.getCurrentStreak()
    };
  }

  async getCurrentStreak() {
    // Calculate current daily quest completion streak
    const history = await new QuestAnalytics(this.client)
      .getMemberQuestHistory(this.memberId);
    
    const dailyQuests = history.filter(q => 
      q.questName.toLowerCase().includes('daily') && q.status === 'completed'
    );
    
    // Calculate streak logic here
    return this.calculateStreak(dailyQuests);
  }

  calculateStreak(completedQuests) {
    if (completedQuests.length === 0) return 0;
    
    // Sort by completion date (most recent first)
    const sorted = completedQuests.sort((a, b) => 
      new Date(b.completedAt) - new Date(a.completedAt)
    );
    
    let streak = 0;
    let currentDate = new Date();
    
    for (const quest of sorted) {
      const completedDate = new Date(quest.completedAt);
      const daysDiff = Math.floor((currentDate - completedDate) / (1000 * 60 * 60 * 24));
      
      if (daysDiff <= streak + 1) {
        streak++;
        currentDate = completedDate;
      } else {
        break;
      }
    }
    
    return streak;
  }
}

Quest Notifications

Set up notifications for quest events:
class QuestNotificationSystem {
  constructor(client) {
    this.client = client;
    this.subscribers = new Map();
  }

  subscribe(memberId, notificationTypes) {
    this.subscribers.set(memberId, notificationTypes);
  }

  async handleQuestEvent(event) {
    const { type, data } = event;
    
    switch (type) {
      case 'quest.started':
        await this.notifyQuestStarted(data);
        break;
      case 'quest.completed':
        await this.notifyQuestCompleted(data);
        break;
      case 'task.completed':
        await this.notifyTaskCompleted(data);
        break;
      case 'quest.expired':
        await this.notifyQuestExpired(data);
        break;
    }
  }

  async notifyQuestStarted(data) {
    const { member, quest } = data;
    
    if (this.shouldNotify(member.id, 'quest.started')) {
      await this.sendNotification(member.id, {
        title: 'Quest Started!',
        message: `You've started "${quest.name}". Good luck!`,
        type: 'success',
        questId: quest.id
      });
    }
  }

  async notifyQuestCompleted(data) {
    const { member, quest, rewards } = data;
    
    if (this.shouldNotify(member.id, 'quest.completed')) {
      const rewardText = rewards.map(r => 
        `${r.amount} ${r.realmPoint?.emoji || 'points'}`
      ).join(', ');
      
      await this.sendNotification(member.id, {
        title: 'Quest Completed! 🎉',
        message: `Congratulations! You completed "${quest.name}" and earned ${rewardText}!`,
        type: 'celebration',
        questId: quest.id
      });
    }
  }

  shouldNotify(memberId, eventType) {
    const preferences = this.subscribers.get(memberId);
    return preferences && preferences.includes(eventType);
  }

  async sendNotification(memberId, notification) {
    // Implement your notification delivery method
    // Could be Discord DM, email, push notification, etc.
    console.log(`📬 Notification for ${memberId}:`, notification);
  }
}

Advanced Quest Patterns

Dynamic Quest Generation

Create quests programmatically based on member behavior:
class DynamicQuestGenerator {
  constructor(client) {
    this.client = client;
  }

  async generatePersonalizedQuest(memberId) {
    // Analyze member activity
    const memberStats = await this.analyzeMemberActivity(memberId);
    
    // Generate quest based on their patterns
    const questTemplate = this.selectQuestTemplate(memberStats);
    
    // Create personalized quest
    const quest = await this.createQuest(questTemplate, memberStats);
    
    return quest;
  }

  async analyzeMemberActivity(memberId) {
    const analytics = new QuestAnalytics(this.client);
    const history = await analytics.getMemberQuestHistory(memberId);
    
    return {
      preferredQuestTypes: this.getPreferredTypes(history),
      averageCompletionTime: this.getAverageCompletionTime(history),
      lastActiveDate: this.getLastActiveDate(history),
      completionRate: this.getCompletionRate(history),
      favoriteRewards: this.getFavoriteRewards(history)
    };
  }

  selectQuestTemplate(memberStats) {
    // Select quest template based on member preferences
    if (memberStats.preferredQuestTypes.includes('daily')) {
      return this.getDailyQuestTemplate();
    } else if (memberStats.preferredQuestTypes.includes('social')) {
      return this.getSocialQuestTemplate();
    } else {
      return this.getGeneralQuestTemplate();
    }
  }

  async createQuest(template, memberStats) {
    // Customize template with member-specific parameters
    const questData = {
      ...template,
      name: this.personalizeName(template.name, memberStats),
      description: this.personalizeDescription(template.description, memberStats),
      rewards: this.personalizeRewards(template.rewards, memberStats),
      difficulty: this.calculateDifficulty(memberStats)
    };

    // Create quest via API
    return await this.client.request('POST',
      `/realm/${this.client.realmId}/quests`,
      questData
    );
  }
}

Quest Chains and Series

Create interconnected quest series:
class QuestChainManager {
  constructor(client) {
    this.client = client;
    this.chains = new Map();
  }

  async createQuestChain(chainConfig) {
    const chain = {
      id: chainConfig.id,
      name: chainConfig.name,
      description: chainConfig.description,
      quests: [],
      requirements: chainConfig.requirements || {}
    };

    // Create individual quests in the chain
    for (let i = 0; i < chainConfig.quests.length; i++) {
      const questConfig = chainConfig.quests[i];
      
      // Add prerequisite requirements (previous quest must be completed)
      if (i > 0) {
        questConfig.prerequisites = [chain.quests[i - 1].id];
      }
      
      const quest = await this.client.request('POST',
        `/realm/${this.client.realmId}/quests`,
        questConfig
      );
      
      chain.quests.push(quest);
    }

    this.chains.set(chain.id, chain);
    return chain;
  }

  async getChainProgress(chainId, memberId) {
    const chain = this.chains.get(chainId);
    if (!chain) throw new Error('Chain not found');

    const progress = [];
    
    for (const quest of chain.quests) {
      const questProgress = await getMemberQuestProgress(
        this.client, memberId, quest.id
      );
      
      progress.push({
        questId: quest.id,
        questName: quest.name,
        status: questProgress.status,
        progress: questProgress.progress,
        isUnlocked: await this.isQuestUnlocked(quest.id, memberId)
      });
    }

    return {
      chainId,
      chainName: chain.name,
      totalQuests: chain.quests.length,
      completedQuests: progress.filter(p => p.status === 'completed').length,
      currentQuest: progress.find(p => p.status === 'active'),
      nextQuest: progress.find(p => p.isUnlocked && p.status === 'available'),
      progress
    };
  }

  async isQuestUnlocked(questId, memberId) {
    // Check if prerequisites are met
    const quest = await this.client.request('GET',
      `/realm/${this.client.realmId}/quests/${questId}`
    );

    if (!quest.prerequisites || quest.prerequisites.length === 0) {
      return true;
    }

    // Check if all prerequisite quests are completed
    for (const prereqId of quest.prerequisites) {
      const prereqProgress = await getMemberQuestProgress(
        this.client, memberId, prereqId
      );
      
      if (prereqProgress.status !== 'completed') {
        return false;
      }
    }

    return true;
  }
}

Quest Performance Optimization

Caching Quest Data

class CachedQuestManager {
  constructor(client, cacheTtl = 300000) { // 5 minutes
    this.client = client;
    this.cacheTtl = cacheTtl;
    this.questCache = new Map();
    this.progressCache = new Map();
  }

  async getQuest(questId) {
    const cacheKey = `quest:${questId}`;
    const cached = this.questCache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTtl) {
      return cached.data;
    }

    const quest = await getQuestDetails(this.client, questId);
    this.questCache.set(cacheKey, {
      data: quest,
      timestamp: Date.now()
    });

    return quest;
  }

  async getMemberProgress(memberId, questId) {
    const cacheKey = `progress:${memberId}:${questId}`;
    const cached = this.progressCache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTtl) {
      return cached.data;
    }

    const progress = await getMemberQuestProgress(this.client, memberId, questId);
    this.progressCache.set(cacheKey, {
      data: progress,
      timestamp: Date.now()
    });

    return progress;
  }

  invalidateCache(type, id) {
    if (type === 'quest') {
      this.questCache.delete(`quest:${id}`);
    } else if (type === 'progress') {
      // Invalidate all progress entries for this member
      const keysToDelete = Array.from(this.progressCache.keys())
        .filter(key => key.startsWith(`progress:${id}:`));
      
      keysToDelete.forEach(key => this.progressCache.delete(key));
    }
  }
}

Best Practices

Design engaging and achievable quests:
const questDesignPrinciples = {
  clarity: {
    name: "Clear, descriptive quest names",
    description: "Detailed but concise descriptions",
    tasks: "Specific, measurable tasks"
  },
  
  progression: {
    difficulty: "Gradual difficulty increase",
    rewards: "Meaningful and proportional rewards",
    feedback: "Clear progress indicators"
  },
  
  engagement: {
    variety: "Mix of different quest types",
    personalization: "Tailored to member preferences",
    social: "Include collaborative elements"
  }
};
Optimize quest system performance:
// Batch quest progress checks
async function batchCheckProgress(client, memberIds, questId) {
  const promises = memberIds.map(memberId =>
    getMemberQuestProgress(client, memberId, questId)
  );
  
  return await Promise.all(promises);
}

// Use efficient data structures
const questProgressMap = new Map();
memberProgresses.forEach(progress => {
  questProgressMap.set(progress.memberId, progress);
});
Handle quest operations gracefully:
async function safeQuestOperation(operation, fallback = null) {
  try {
    return await operation();
  } catch (error) {
    console.error('Quest operation failed:', error.message);
    
    // Log error for debugging
    await logQuestError(error);
    
    // Return fallback value
    return fallback;
  }
}

Next Steps

Building quest systems? Join our Discord community to share your quest designs and get feedback from other developers! 🗺️