Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relate trophy titles and user titles #162

Open
zuinqstudio opened this issue Apr 18, 2024 · 11 comments
Open

Relate trophy titles and user titles #162

zuinqstudio opened this issue Apr 18, 2024 · 11 comments

Comments

@zuinqstudio
Copy link

I'm trying to gather some basic player statistics from gamelist/v2/users/$accountId/titles and trophy/v1/users/$accountId/trophyTitles. Unfortunately, conceptId appears only in the first request, and the best field to find a game by its name from the second one trophyTitleName, which slightly differs from the original game title in lots of cases.

Is there any known api to establish this relationship?

@zuinqstudio
Copy link
Author

Answering my own question, just in case might be useful for someone. I found this.-

https://m.np.playstation.com/api/trophy/v1/users/{userId}|me/titles/trophyTitles?npTitleIds={ids}

Which returns objects that include npTitleId and npCommunicationId. This npCommunicationId can later be used in trophyGroups methods.

@platinumachievements
Copy link

platinumachievements commented Jun 1, 2024

Answering my own question, just in case might be useful for someone. I found this.-

https://m.np.playstation.com/api/trophy/v1/users/{userId}|me/titles/trophyTitles?npTitleIds={ids}

Which returns objects that include npTitleId and npCommunicationId. This npCommunicationId can later be used in trophyGroups methods.

what is the best way of getting the titleids for this, i cant seem to find a good way

@pacMakaveli
Copy link

Hey, if you're looking for something like this, hit me up. games.directory does a lot of mapping between titles and games.
Let me know your use-case and I can provide an API endpoint.

@pradella
Copy link

Hey @pacMakaveli, I tried to sign up on the games directory, but it seems to ben't working atm. Do you have any tips on how to get the titleId from npCommunicationId?

I am currently struggling to get media and playDuration for PS3 games.

@pacMakaveli
Copy link

pacMakaveli commented Jul 31, 2024

@pradella sorry about that. If you want to, please let me know what didn't work so I can fix it :D

As for the npCommunicationId to titleId, it really depends on the use case and what data you've got available. This is a ruby script that I use:

require 'json'

class String
  def levenshtein_distance(other)
    m, n = self.length, other.length
    return m if n == 0
    return n if m == 0
    d = Array.new(m+1) {Array.new(n+1)}

    (0..m).each {|i| d[i][0] = i}
    (0..n).each {|j| d[0][j] = j}
    (1..n).each do |j|
      (1..m).each do |i|
        d[i][j] = if self[i-1] == other[j-1]
                    d[i-1][j-1]
                  else
                    [d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+1].min
                  end
      end
    end
    d[m][n]
  end
end

def parse_list(content)
  content.split("\n").map do |line|
    id, name = line.split(': ', 2)
    [id.strip, name.strip]
  end
end

def normalize_name(name)
  name.downcase.gsub(/[^a-z0-9]+/, '')
end

def calculate_accuracy(distance, max_length)
  1 - (distance.to_f / max_length)
end

def find_best_match(entry, list)
  normalized_entry = normalize_name(entry[1])
  best_match = list.max_by do |item|
    normalized_item = normalize_name(item[1])
    accuracy = calculate_accuracy(
      normalized_entry.levenshtein_distance(normalized_item),
      [normalized_entry.length, normalized_item.length].max
    )
    accuracy
  end
  
  accuracy = calculate_accuracy(
    normalized_entry.levenshtein_distance(normalize_name(best_match[1])),
    [normalized_entry.length, normalize_name(best_match[1]).length].max
  )
  
  [best_match, accuracy]
end

def check(entry, list)
  best_match, accuracy = find_best_match(entry, list)
  
  {
    entry: entry[0],
    match: best_match[0],
    accuracy: accuracy,
    labels: [entry[1], best_match[1]]
  }
end

def cross_reference(list1, list2)
  result = {}

  list1.each do |entry|
    match = check(entry, list2)
    
    # You may want to adjust this threshold
    if match[:accuracy] >= 0.7
      key = normalize_name(entry[1])
      result[key] ||= []
      result[key] << match
    end
  end

  result.map { |k, v| { k => v } }
end

# Function to read file content
def read_file(filename)
  File.read(filename)
rescue Errno::ENOENT
  puts "File #{filename} not found."
  exit
end

# Main execution
if ARGV.length == 2
  # Cross-reference two lists
  games_content = read_file(ARGV[0])
  titles_content = read_file(ARGV[1])

  games = parse_list(games_content)
  titles = parse_list(titles_content)

  result = cross_reference(games, titles)
elsif ARGV.length == 3 && ARGV[0] == 'check'
  # Check a single entry against a list
  entry_content = read_file(ARGV[1])
  list_content = read_file(ARGV[2])

  entry = parse_list(entry_content).first
  list = parse_list(list_content)

  result = check(entry, list)
else
  puts "Usage: ruby script.rb [games_file] [titles_file]"
  puts "   or: ruby script.rb check [entry_file] [list_file]"
  exit
end

# Output the result as JSON
puts JSON.pretty_generate(result)

And then these are the files I would give it.

games =>

PPSA01923_00: Fortnite
CUSA01433_00: Rocket League®
CUSA15495_00: Surviving the Aftermath
PPSA01463_00: Borderlands 3
CUSA08025_00: Borderlands 3

titles =>

NPWR21035_00: Borderlands® 3
NPWR28289_00: XDefiant
NPWR28312_00: Crime Boss: Rockay City
NPWR29727_00: The Outer Worlds: Spacer's Choice Edition

The gist of it is that it tried to match them by name. If no match is found, it will use the levenshtein algorithm to find the closes match, otherwise it will return nothing.

playDuration is not available for PS3 games. This data was exposed when PS5 was released and AFAIK, there was no tracking of this kind for PS3 titles.
media, what sort of media are you looking for?

https://splash.games.directory/
https://splash.games.directory/i?q%5Bslug_cont_any%5D=fortnite

has probably more than what you need. It doesn't currently allow you to retrieve via some sort of API, but it's something I can add quickly.
If you want to do it yourself though, you will need to query the concept API. I don't think this repo has any documentation (not really looked into it tbh).
These are the endpoints I personally use in my projects. They use the same authentication method as the app.

https://m.np.playstation.net/api/catalog/v2/concepts/fetch?country=GB&language=en&age=999&limit=1000&offset=1
https://m.np.playstation.net/api/catalog/v2/concepts/10000470
https://m.np.playstation.net/api/catalog/v2/titles/CUSA18182_00/?country=GB&language=en&age=999

@pradella
Copy link

Wow, thanks for sharing those additional endpoints @pacMakaveli !

You're right; PS3 playDuration does not exist (I completely forgot it).

Indeed I could find npCommunicationId from titleId; but not the opposite (titleId from npCommunicationId).

For example: trophy/v1/users/${accountId}/trophyTitles brings all games (including PS3, but only npCommunicationId, no titleId. And to fetch media Im using this one gamelist/v2/users/${accountId}/titles/${titleId}

I'm guessing you have previously mapped all those npCommunicationId with titleId based on the history scan your app made?

@pacMakaveli
Copy link

@pradella correct. My database spans 9 years of constantly syncing the API and more importantly, users. One easy way to map these is to scan someone that you know has completed/owns lots of games. Hakoom, Ikemenzi etc.. (https://psnprofiles.com/dav1d_123) you can get their account ids from the search endpoint.

Again, if there's a way I can provide this data to you via games.directory's API, please let me know. I'd be more than happy as one of my goals with this is to provide little snippets of APIs for these kind of use cases. (obviously free, just in case it's not clear)

@platinumachievements
Copy link

@pacMakaveli Hi! Sorry to quickly hijack this thread, I would love to get in contact with you about possibly using your api for a pet project of mine and my gamer groups. I think I tried a while ago to send a message on the webpage but it never went through. What's the best wway to get in touch?

@pacMakaveli
Copy link

vlad [at] games [dot] directory

@pradella
Copy link

@pradella correct. My database spans 9 years of constantly syncing the API and more importantly, users. One easy way to map these is to scan someone that you know has completed/owns lots of games. Hakoom, Ikemenzi etc.. (https://psnprofiles.com/dav1d_123) you can get their account ids from the search endpoint.

Again, if there's a way I can provide this data to you via games.directory's API, please let me know. I'd be more than happy as one of my goals with this is to provide little snippets of APIs for these kind of use cases. (obviously free, just in case it's not clear)

Such an inspiration you are, thanks bro @pacMakaveli

@pacMakaveli
Copy link

Also, feel free to join https://discord.gg/k4QRsfmA . It'll be easier to communicate and plan ahead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants