File size: 4,968 Bytes
ee826ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
 * ============================================
 * πŸ“¦ extractor.js β€” Extractor Integrado v0.1+v0.2
 * ============================================
 * Zelin puede extraer datos del servidor cuando
 * necesita actualizar su conocimiento.
 * Combina la lΓ³gica de los bots v0.1 y v0.2.
 */

import { ChannelType, PermissionsBitField } from 'discord.js';
import * as db from './db.js';
import { sleep } from './utils.js';

// ── ExtracciΓ³n de informaciΓ³n del servidor ────────────────────────────────────

export async function extractServerInfo(guild) {
  return {
    id          : guild.id,
    name        : guild.name,
    description : guild.description,
    ownerId     : guild.ownerId,
    memberCount : guild.memberCount,
    boostLevel  : guild.premiumTier,
    boostCount  : guild.premiumSubscriptionCount ?? 0,
    features    : guild.features,
    createdAt   : guild.createdAt.toISOString(),
    extractedAt : new Date().toISOString(),
  };
}

// ── ExtracciΓ³n y sync de roles ────────────────────────────────────────────────

export async function syncAllRoles(guild) {
  let count = 0;
  for (const role of guild.roles.cache.values()) {
    await db.upsertRole(role);
    count++;
  }
  console.log(`[Extractor] ${count} roles sincronizados`);
  return count;
}

// ── ExtracciΓ³n y sync de canales ─────────────────────────────────────────────

export async function syncAllChannels(guild) {
  let count = 0;
  for (const channel of guild.channels.cache.values()) {
    await db.upsertChannel(channel);
    count++;
  }
  console.log(`[Extractor] ${count} canales sincronizados`);
  return count;
}

// ── ExtracciΓ³n y sync de miembros ─────────────────────────────────────────────

export async function syncAllMembers(guild, onProgress = null) {
  await guild.members.fetch();
  const members = [...guild.members.cache.values()];
  let count = 0;

  for (const member of members) {
    await db.upsertUser(member);
    const roleIds = [...member.roles.cache.keys()].filter(id => id !== guild.id);
    await db.syncUserRoles(member.id, roleIds);
    count++;

    if (onProgress && count % 50 === 0) {
      await onProgress(count, members.length);
    }

    await sleep(50); // Rate limit suave
  }

  console.log(`[Extractor] ${count} miembros sincronizados`);
  return count;
}

// ── ExtracciΓ³n de historial de mensajes de un canal ───────────────────────────

export async function extractChannelMessages(channel, limit = 10000) {
  let lastId = null;
  let total  = 0;
  let hasMore = true;

  while (hasMore && total < limit) {
    const options = { limit: 100 };
    if (lastId) options.before = lastId;

    let batch;
    try {
      batch = await channel.messages.fetch(options);
    } catch {
      break;
    }

    if (batch.size === 0) break;

    for (const message of batch.values()) {
      if (message.author.bot) continue;
      try {
        await db.saveMessage(message);
        total++;
      } catch {
        // Mensaje ya guardado o error, continuar
      }
    }

    lastId  = batch.last()?.id;
    hasMore = batch.size === 100;

    await sleep(1200); // Rate limit Discord
  }

  return total;
}

// ── ExtracciΓ³n completa del servidor (para uso interno de Zelin) ──────────────

export async function runFullExtraction(guild, notifyProgress) {
  console.log('[Extractor] Iniciando extracciΓ³n completa...');
  const results = { members: 0, roles: 0, channels: 0, messages: 0 };

  // Roles
  if (notifyProgress) await notifyProgress('🎭 Sincronizando roles...');
  results.roles = await syncAllRoles(guild);

  // Canales
  if (notifyProgress) await notifyProgress('πŸ“ Sincronizando canales...');
  results.channels = await syncAllChannels(guild);

  // Miembros
  if (notifyProgress) await notifyProgress('πŸ‘₯ Sincronizando miembros...');
  results.members = await syncAllMembers(guild, async (done, total) => {
    if (notifyProgress) await notifyProgress(`πŸ‘₯ Miembros: ${done}/${total}`);
  });

  // Mensajes (canales de texto pΓΊblicos, ΓΊltimos 5000)
  if (notifyProgress) await notifyProgress('πŸ’¬ Extrayendo mensajes recientes...');
  const textChannels = [...guild.channels.cache.values()]
    .filter(c => c.type === ChannelType.GuildText);

  for (const channel of textChannels) {
    try {
      const count = await extractChannelMessages(channel, 5000);
      results.messages += count;
    } catch {
      // Canal sin permisos, continuar
    }
  }

  console.log('[Extractor] ExtracciΓ³n completa:', results);
  return results;
}