solveBulk method

  1. @override
Future<Map<String, String?>> solveBulk(
  1. String playerUrl,
  2. Map<JSChallengeType, List<String>> requests
)
override

Solves JavaScript challenges in bulk. The requests parameter is a map where the key is the type of challenge and the value is a list of challenge strings to be solved.

Returns a map where each challenge string maps to its solved result or null if unsolved.

Implementation

@override
Future<Map<String, String?>> solveBulk(
    String playerUrl, Map<JSChallengeType, List<String>> requests) async {
  // Filter out already cached challenges
  final uncachedRequests = <JSChallengeType, List<String>>{};
  final cachedResults = <String, String?>{};

  for (final entry in requests.entries) {
    final type = entry.key;
    final challenges = entry.value;
    final uncached = <String>[];

    for (final challenge in challenges) {
      final key = (playerUrl, challenge, type);
      if (_sigCache.containsKey(key)) {
        cachedResults[challenge] = _sigCache[key]!;
      } else {
        uncached.add(challenge);
      }
    }

    if (uncached.isNotEmpty) {
      uncachedRequests[type] = uncached;
    }
  }

  // If all challenges are cached, return early
  if (uncachedRequests.isEmpty) {
    return cachedResults;
  }

  // Get player script (from cache or fetch)
  late String playerScript;
  var isPreprocessed = false;
  if (_preprocPlayer.containsKey(playerUrl)) {
    playerScript = _preprocPlayer[playerUrl]!;
    isPreprocessed = true;
  } else if (_playerCache.containsKey(playerUrl)) {
    playerScript = _playerCache[playerUrl]!;
  } else {
    final resp = await http.get(Uri.parse(playerUrl));
    playerScript = _playerCache[playerUrl] = resp.body;
  }

  final jsCall = EJSBuilder.buildJSCall(playerScript, uncachedRequests,
      isPreprocessed: isPreprocessed);

  final resultJson = await executeJavaScript(jsCall);

  final data = json.decode(resultJson) as Map<String, dynamic>;

  if (data['type'] != 'result') {
    throw Exception('Unexpected response type: ${data['type']}');
  }

  // Store preprocessed player if available
  if (data['preprocessed_player'] != null) {
    _preprocPlayer[playerUrl] = data['preprocessed_player'] as String;
  }

  // Process all responses
  final responses = data['responses'] as List;
  for (final response in responses) {
    if (response['type'] != 'result') {
      throw Exception('Unexpected item response type: ${response['type']}');
    }

    final responseData = response['data'] as Map<String, dynamic>;
    for (final entry in responseData.entries) {
      final challenge = entry.key;
      final decoded = entry.value as String?;

      // Find the type for this challenge
      JSChallengeType? challengeType;
      for (final typeEntry in uncachedRequests.entries) {
        if (typeEntry.value.contains(challenge)) {
          challengeType = typeEntry.key;
          break;
        }
      }

      if (challengeType != null) {
        final key = (playerUrl, challenge, challengeType);
        if (decoded != null) {
          _sigCache[key] = decoded;
          cachedResults[challenge] = decoded;
        } else {
          cachedResults[challenge] = null;
        }
      }
    }
  }

  return cachedResults;
}