I've seen an unusually high number of requests for information about this lately, so I thought I'd post a general guide.

A couple of points to start out with:

1. Blizzard will hopefully fix the (poorly designed) talent inspection system at some point (nUI's author has reportedly heard that they plan on doing so ...hopefully that will happen sooner than "soon" but who knows...In any case, when that happens it will hopefully make most of this obsolete.

2. Many people can get the work done for them by using a library instead of doing it yourself. Obviously I'm assuming here that you want to do it yourself, though.

3. You will need to be familiar with events and event handlers. See the wiki if you're not:

...which will print the name, etc. of every talent, and demonstrates the basic use of everything involved in reading all player talents. For the sake of simplification, let's turn that into:

Code:

local function DoInspect()
local nameTalent = GetTalentInfo(1,1)
DEFAULT_CHAT_FRAME:AddMessage(nameTalent)
end

...which will print the name of the first talent in the first tree of the local player.

It looks like we can get the talents from another player by just passing true as the third argument, but note that the player that the talents will be read from is the inspected player, that is the player that was last inspected by the local player. You cannot read the talents of anyone other than the local player, or the most recently inspected player.

The good news is that addons can trigger an inspection of the target or any party/raid member without opening the inspection window, or doing anything that the person playing will notice. You do this with NotifyInspect("unit") -- note that you must first make sure the unit to inspect is within range, etc. Assuming we want to get the talents of our current target, this gives us:

Code:

--NB: This code will not work as expected!
local function DoInspect()
if CanInspect("target") then
NotifyInspect("target")
local nameTalent = GetTalentInfo(1, 1, true)
DEFAULT_CHAT_FRAME:AddMessage(nameTalent)
end
end

This, however, is incomplete. The problem is that NotifyInspect doesn't work instantly. You must stop for now and wait for the WoW client to send the request to the server, and get a response. By far the best way to do this is to register for the event INSPECT_TALENT_READY, which fires when a player has been inspected successfully and data can be read for them. Therefore our example is now:

Code:

--NB: This code will still not work as expected!
local f = CreateFrame("frame")
f:SetScript("OnEvent", function(self)
self:UnregisterEvent("INSPECT_TALENT_READY") --we only care about the first event after we call NotifyInspect
local nameTalent = GetTalentInfo(1, 1, true)
DEFAULT_CHAT_FRAME:AddMessage(nameTalent)
end)
local function DoInspect()
if CanInspect("target") then
NotifyInspect("target")
f:RegisterEvent("INSPECT_TALENT_READY")
end
end

This will at least produce correct results if the player is not running any other addons and doesn't manually inspect anyone; but, now for the bad news: only one player can be inspected at a time, and there is no way to know who the currently inspected player is. Even if you just called NotifyInspect, any other addon or the default UI can call it between your call and the time the server responds, in which case you will never get a response. Even worse, the other addon *will* and it will still come in the form of the INSPECT_TALENT_READY event; meaning that if you call GetTalentInfo now, you will be reading data from whoever the other addon inspected!

To correct for this, we need to make sure that no one has called NotifyInspect since the last time we did. This can be done with hooksecurefunc:http://www.wowwiki.com/API_hooksecurefunc
...briefly, when you call hooksecurefunc, you specify a function to watch. Every time that funciton gets called by anyone, it will trigger a call to a second function that you specify. In this case we want to watch for anyone calling NotifyInspect and, if anyone does, note that it was called so that we can ignore the result and try again. This gives us:

Code:

--NB: This code will work most of the time but is not perfect yet.
local inspectTainted
hooksecurefunc("NotifyInspect", function() inspectTainted = true end)
local f = CreateFrame("frame")
f:SetScript("OnEvent", function(self)
self:UnregisterEvent("INSPECT_TALENT_READY")
if inspectTainted then
if CanInspect("target") then -- in this case we could just call DoInspect (if it was higher in the file), but i'll write it out for the example
self:RegisterEvent("INSPECT_TALENT_READY")
NotifyInspect("target")
inspectTainted = false
end
else
local nameTalent = GetTalentInfo(1, 1, true)
DEFAULT_CHAT_FRAME:AddMessage(nameTalent)
end
end)
local function DoInspect()
if CanInspect("target") then
NotifyInspect("target")
inspectTainted = false
f:RegisterEvent("INSPECT_TALENT_READY")
end
end

And there you have it. To be 100% accurate, you'll need some way of dealing with the possibility that the target doesn't exist, or is out of range. Also be wary of latency issues which sometimes cause INSPECT_TALENT_READY to not fire. If you are going to scan many players at once (like a full raid), you will want to set a timeout on each attempt and/or player (as well as timers between inspections, and conflict detection/backoff timers if you want to play nice). If you're just scanning one person, this may not be as important, but you can still use an OnUpdate script to try again if you don't get any result after a certain period of time.