View difference between Paste ID: 7x7fQjeA and 68hwGptW
SHOW: | | - or go back to the newest paste.
1
// ==UserScript==
2
// @name       	TF2 Profile Script
3
// @namespace  	tfprofile
4
// @version    	1.1.3
5
// @description Mouse over profile links (steamcommunity/etf2l/wireplay/teamfortress.tv) to get links their profiles on otherwebsites
6
// @downloadURL https://github.com/CasualX/UserScripts/raw/master/tfprofile.user.js
7
// @updateURL   https://github.com/CasualX/UserScripts/raw/master/tfprofile.user.js
8
// @include     http://*
9
// @include     https://*
10
// @grant		GM_xmlhttpRequest
11
// @run-at document-end
12
// ==/UserScript==
13
14
// Base conversion of arbitrary precision integers
15
// Minified from http://danvk.org/hex2dec.html
16
function convertBase(e,t,n){function r(e,t,n){var r=[];var i=Math.max(e.length,t.length);var s=0;var o=0;while(o<i||s){var u=o<e.length?e[o]:0;var a=o<t.length?t[o]:0;var f=s+u+a;r.push(f%n);s=Math.floor(f/n);o++}return r}function i(e,t,n){if(e<0)return null;if(e==0)return[];var i=[];var s=t;while(true){if(e&1){i=r(i,s,n)}e=e>>1;if(e===0)break;s=r(s,s,n)}return i}function s(e,t){var n=e.split("");var r=[];for(var i=n.length-1;i>=0;i--){var s=parseInt(n[i],t);if(isNaN(s))return null;r.push(s)}return r}var o=s(e,t);if(o===null)return null;var u=[];var a=[1];for(var f=0;f<o.length;f++){if(o[f]){u=r(u,i(o[f],a,n),n)}a=i(t,a,n)}var l="";for(var f=u.length-1;f>=0;f--){l+=u[f].toString(n)}return l}
17
// Googled this for Chrome compat, minified
18
function addStyle(e){var t=document.createElement("style");t.type="text/css";t.appendChild(document.createTextNode(e));document.getElementsByTagName("head")[0].appendChild(t)}
19
20
//----------------------------------------------------------------
21
// Steam ID container
22
function CSteamID()
23
{
24
	this.unAccountID = 0;
25
	this.unAccountInstance = 0;
26
	this.EAccountType = CSteamID.EAccountType.k_EAccountTypeInvalid;
27
	this.EUniverse = CSteamID.EUniverse.k_EUniverseInvalid;
28
}
29
CSteamID.prototype.setID = function( sid, type, uni )
30
{
31
	this.unAccountID = sid;
32
	this.unAccountInstance = 1;
33
	this.EAccountType = type || CSteamID.EAccountType.k_EAccountTypeIndividual;
34
	this.EUniverse = uni || CSteamID.EUniverse.k_EUniversePublic;
35
}
36
CSteamID.prototype.setID64 = function( id )
37
{
38
	var hex = "0000000000000000" + convertBase( id, 10, 16 );
39
40
	// Break down the (hexadecimal) community id
41
	this.unAccountID = parseInt(hex.substr(-8,8),16);
42
	this.unAccountInstance = parseInt(hex.substr(-13,5),16);
43
	this.EAccountType = parseInt(hex.substr(-14,1),16);
44
	this.EUniverse = parseInt(hex.substr(-16,2),16);
45
}
46
CSteamID.prototype.render = function()
47
{
48
	// Old style U:x:y format
49
	return "U:" + (this.unAccountID%2) + ":" + (this.unAccountID>>1);
50
}
51
CSteamID.prototype.toString = function()
52
{
53
	function s( num, len )
54
	{
55
		var str = num.toString(16);
56
		return "0000000000000000".substr(0,len-str.length)+str;
57
	}
58
	var hex = s(this.EUniverse,2) + s(this.EAccountType,1) + s(this.unAccountInstance,5) + s(this.unAccountID,8);
59
	return convertBase( hex, 16, 10 );
60
}
61
CSteamID.EUniverse = { k_EUniverseInvalid:0, k_EUniversePublic:1, k_EUniverseBeta:2, k_EUniverseInternal:3, k_EUniverseDev:4, k_EUniverseRC:5, k_EUniverseMax:6 };
62
CSteamID.EAccountType = { k_EAccountTypeInvalid:0, k_EAccountTypeIndividual:1, k_EAccountTypeMultiseat:2, k_EAccountTypeGameServer:3, k_EAccountTypeAnonGameServer:4, k_EAccountTypePending:5, k_EAccountTypeContentServer:6, k_EAccountTypeClan:7, k_EAccountTypeChat:8, k_EAccountTypeP2PSuperSeeder:9, k_EAccountTypeMax:10 };
63
CSteamID.parse = function( str )
64
{
65
	var re, sid = null;
66-
	// SteamID old format, 2nd part is how etf2l formats it.
66+
	// Old SteamID with and without prefix in any of three exciting flavours
67-
	if ( re = /^(?:U)?0\:([01])\:(\d+)$/.exec( str ) )
67+
	if ( re = /^(?:STEAM_|Steam_|steam_)?0\:([01])\:(\d+)$/.exec( str ) )
68
	{
69
		sid = new CSteamID();
70
		sid.setID( parseInt(re[2])*2 + parseInt(re[1]) );
71
	}
72-
	// Looks like a standard 64bit steamid
72+
	// New SteamID with and without square brackets
73
	else if ( re = /^\[?U:1:(\d+)\]?$/.exec( str ) )
74
	{
75
		sid = new CSteamID();
76
		sid.setID( parseInt(re[1]) );
77
	}
78
	// SteamID64
79
	else if ( re = /^\d+$/.exec( str ) )
80
	{
81
		sid = new CSteamID();
82
		sid.setID64( str );
83
	}
84
	return sid;
85
}
86
//----------------------------------------------------------------
87
88
// Find profiles with search engine because they don't have an API...
89
function searchEngine( site, title, content, fn )
90
{
91
	function search( engine )
92
	{
93
		GM_xmlhttpRequest( {
94
			method: "GET",
95
			url: engine.qurl + encodeURIComponent( 'site:'+site+' title:"'+title+'" "'+content+'"' ),
96
			onload: function( resp ) { match( resp.responseText, engine ); },
97
			onerror: function( resp ) { match( "", engine ); }
98
		} );
99
	}
100
	function match( text, engine )
101
	{
102
		var r;
103
		while ( r = engine.regex.exec( text ) )
104
		{
105
			// Found a valid result
106
			if ( r[1].indexOf(site)>=0 )
107
				return fn( r );
108
		}
109
		// Not found, try another search engine
110
		if ( engine.next ) search( engine.next );
111
		// Tried all engines, nothing found
112
		else fn( false );
113
	}
114
	var engines = {
115
		qurl: "https://ixquick.com/do/search?q=",
116
		regex: /<a href='([^']*)' id='title_\d'/,
117
		next: {
118
		qurl: "https://startpage.com/do/search?q=",
119
		regex: /<a href='([^']*)' id='title_\d'/,
120
	}
121
	};
122
	search( engines );
123
}
124
125
//----------------------------------------------------------------
126
// Websites supported
127
//----------------------------------------------------------------
128
// Each website may have these 3 functions:
129
//  match: Given an url return a non false value if you can handle this profile link
130
//  source: Source the player's steamid from this profile url, directly called after match with its returned value (eg, regex result). Callback player.initialize with the steamid on success.
131
//  query: Find out given the steamid if this player has a profile on this website. Callback player.addLink with the url and description on success.
132
133
function siteSetLink( p, url, desc, html )
134
{
135
	var a = document.createElement('a');
136
	a.href = url;
137
	a.target = "_blank";
138
	if ( html ) a.innerHTML = html;
139
	var text = document.createTextNode(desc);
140
	if ( a.firstChild ) a.insertBefore( text, a.firstChild );
141
	else a.appendChild( text );
142
	p.innerHTML = '';
143
	p.appendChild( a );
144
	p.className = 'TFProfile_Done';
145
}
146
function siteSetMissing( p )
147
{
148
	p.className = 'TFProfile_Missing';
149
	// Hide the parent article if it only has missing links...
150
	if ( p.parentNode.children.length==p.parentNode.querySelectorAll(".TFProfile_Missing").length )
151
		p.parentNode.parentNode.removeChild( p.parentNode );
152
}
153
154
var sites = {
155
// Steam community support
156
"steamcommunity.com": {
157
	group: "steam",
158
	match: function( url ) { return /^https?\:\/\/steamcommunity\.com\/(?:profiles|id)\/[^\/]*\/?$/.exec(url); },
159
	source: function( re, player )
160
	{
161
		GM_xmlhttpRequest( {
162
			method: "GET",
163
			url: re[0] + "?xml=1",
164
			onload: function( resp )
165
			{
166
				var parser = new DOMParser();
167
				var doc = parser.parseFromString( resp.responseText, "text/xml" ).documentElement;
168
				var str = doc.querySelector( "steamID64" ).textContent;
169
				player.initialize( CSteamID.parse( str ) );
170
			},
171
			onerror: function( resp )
172
			{
173
				player.error( resp.responseText );
174
			}
175
		} );
176
	},
177
	query: function( sid, player, el )
178
	{
179
		el.textContent = 'steamcommunity.com';
180
		
181
		var commid = sid.toString();
182
		GM_xmlhttpRequest( {
183
			method: "GET",
184
			url: "https://steamcommunity.com/profiles/"+commid+"?xml=1",
185
			onload: function( resp )
186
			{
187
				var parser = new DOMParser();
188
				doc = parser.parseFromString( resp.responseText, "text/xml" ).documentElement;
189
				
190
				// Profiles that haven't been set up do not have a name, in this case, steamID will be empty
191
				var desc = "Steam Community", name = doc.querySelector("steamID"), online = doc.querySelector("onlineState");
192
				if ( name && name.textContent ) desc += " ("+name.textContent+")";
193
				siteSetLink( el, "https://steamcommunity.com/profiles/"+commid, desc, online?'<span style="padding-left:5px;font-size:xx-small;">'+online.textContent+'</span>':undefined );
194
				
195
				var gameIP = doc.querySelector("inGameServerIP");
196
				var gameName = doc.querySelector("inGameInfo>gameName")
197
				var gameJoin = doc.querySelector("inGameInfo>gameJoinLink");
198
				if ( gameIP && gameIP.textContent && gameName && gameName.textContent && gameJoin && gameJoin.textContent )
199
				{
200
					var a = document.createElement('a');
201
					a.href = gameJoin.textContent;
202
					a.innerHTML = "In-Game: "+gameName.textContent;
203
					el.appendChild( a );
204
				}
205
			}
206
		} );
207
	}
208
},
209
// ETF2L Support
210
"etf2l.org": {
211
	group: "comptf2",
212
	match: function( url ) { return /^http\:\/\/etf2l.org\/forum\/user\/(\d+)\/?$/.exec(url); },
213
	source: function( re, player )
214
	{
215
		GM_xmlhttpRequest( {
216
			method: "GET",
217
			url: "http://etf2l.org/feed/player/?id=" + re[1],
218
			onload: function( resp )
219
			{
220
				var parser = new DOMParser();
221
				var doc = parser.parseFromString( resp.responseText, "text/xml" ).documentElement;
222
				var str = doc.querySelector( "player" );
223
				player.initialize( CSteamID.parse( str ? str.getAttribute("steamid") : "" ) );
224
			},
225
			onerror: function( resp )
226
			{
227
				player.error( resp.responseText );
228
			}
229
		} );
230
	},
231
	query: function( sid, player, el )
232
	{
233
		el.textContent = 'etf2l.org';
234
		
235
		GM_xmlhttpRequest( {
236
			method: "GET",
237
			url: "http://etf2l.org/feed/player/?steamid=" + sid.render(),
238
			onload: function( resp )
239
			{
240
				try {
241
					var parser = new DOMParser();
242
					var doc = parser.parseFromString( resp.responseText, "text/xml" ).documentElement;
243
					var id = doc.querySelector("player").getAttribute("id");
244
					var name = doc.querySelector("displayname").textContent;
245
					siteSetLink( el, "http://etf2l.org/forum/user/"+id+"/", "ETF2L Profile ("+name+")" );
246
				} catch(e) {
247
					siteSetMissing( el );
248
				}
249
			}
250
		} );
251
	}
252
},
253
// Wireplay TF2 League
254
"tf2.wireplay.co.uk": {
255
	group: "comptf2",
256
	match: function( url ) { return /^https?\:\/\/tf2\.wireplay\.co\.uk\/.*?index\.php\?pg=profile\&action=viewprofile\&aid=(\d+)/.exec(url); },
257
	source: function( re, player )
258
	{
259
		GM_xmlhttpRequest( {
260
			method: "GET",
261
			url: re[0],
262
			onload: function( resp )
263
			{
264
				var r = /<td align=left>(U\:[01]\:\d+)<\/td>/.exec(resp.responseText);
265
				player.initialize( CSteamID.parse( r?r[1]:"" ) );
266
			},
267
			onerror: function( resp )
268
			{
269
				player.error( resp.responseText );
270
			}
271
		} );
272
	},
273
	query: function( sid, player, el )
274
	{
275
		el.textContent = 'tf2.wireplay.co.uk';
276
		
277
		GM_xmlhttpRequest( {
278
			method: "POST",
279
			url: "http://tf2.wireplay.co.uk/index.php?pg=search",
280
			data: "clantag=false&clanname=false&playername=false&steamid=true&searchterm=" + encodeURIComponent(sid.render()),
281
			headers: { "Content-Type": "application/x-www-form-urlencoded" },
282
			onload: function( resp )
283
			{
284
				try {
285
					var r = /<td><a href="(index.php\?pg=profile\&action=viewprofile\&aid=\d+)">([^<]*)<\/a><\/td>/.exec(resp.responseText);
286
					siteSetLink( el, "http://tf2.wireplay.co.uk/"+r[1], "Wireplay Profile ("+r[2]+")" );
287
				} catch(e) {
288
					siteSetMissing( el );
289
				}
290
			}
291
		} );
292
	}
293
},
294
// UGC League
295
"ugcleague.com": {
296
	group: "comptf2",
297
	match: function( url ) { return /^https?\:\/\/www.ugcleague.com\/players_page\.cfm\?player_id=(\d+)$/.exec(url); },
298
	source: function( re, player ) { player.initialize( CSteamID.parse( re[1] ) ); },
299
	query: function( sid, player, el )
300
	{
301
		el.textContent = 'ugcleague.com';
302
		
303
		// Assumption: Just check if the player's steamid is on the page, that means he's played in a team.
304
		// FIXME! Use their player search page instead? http://www.ugcleague.com/playersearch.cfm
305
		GM_xmlhttpRequest( {
306
			method: "GET",
307
			url: "http://www.ugcleague.com/players_page.cfm?player_id="+sid.toString(),
308
			onload: function( resp )
309
			{
310
				if ( resp.responseText.indexOf( "<td>"+sid.render().substr(6)+"</td>" )>=0 )
311
					siteSetLink( el, "http://www.ugcleague.com/players_page.cfm?player_id="+sid.toString(), "UGC League Profile" );
312
				else
313
					siteSetMissing( el );
314
			}
315
		} );
316
	}
317
},
318
// TF2Lobby.com
319
"tf2lobby.com": {
320
	group: "lobby",
321
	match: function( url ) { return /^http\:\/\/(?:www.)?tf2lobby\.com\/profile\?(f?id)=(\d+)$/.exec(url); },
322
	source: function( re, player )
323
	{
324
		if ( re[1]==='fid' )
325
		{
326
			player.initialize( CSteamID.parse( re[2] ) );
327
			return;
328
		}
329
		
330
		GM_xmlhttpRequest( {
331
			method: "GET",
332
			url: "http://www.tf2lobby.com/profile?id="+re[2],
333
			onload: function( resp )
334
			{
335
				var dom = new DOMParser(), doc, el, sid;
336
				if ( ( doc = parser.parseFromString( resp.responseText, "text/xml" ).documentElement ) &&
337
					 ( el = doc.querySelector("#otherSites>ul>li>a") ) )
338
				{
339
					sid = CSteamID.parse( /\d+/.exec( el.href )[0] );
340
				}
341
				player.initialize( sid );
342
			},
343
			onerror: function( resp )
344
			{
345
				player.error( resp.responseText );
346
			}
347
		} );
348
	},
349
	query: function( sid, player, el )
350
	{
351
		el.textContent = 'tf2lobby.com';
352
		
353
		GM_xmlhttpRequest( {
354
			method: "GET",
355
			url: "http://www.tf2lobby.com/profile?fid="+sid.toString(),
356
			onload: function( resp )
357
			{
358
				try {
359
					var dom = new DOMParser();
360
					var doc = dom.parseFromString( resp.responseText, "text/html" ).documentElement;
361
					var name = ( doc.querySelector("#otherSites") && doc.querySelector("#player p") ).textContent;
362
					siteSetLink( el, "http://www.tf2lobby.com/profile?fid="+sid.toString(), "TF2Lobby Profile ("+name+")" );
363
				} catch(e) {
364
					siteSetMissing( el );
365
				}
366
			}
367
		} );
368
	}
369
},
370
// TeamFortress.tv profiles (barely works...)
371
"teamfortress.tv": {
372
	group: "forum",
373
	match: function( url ) { return /^https?\:\/\/teamfortress\.tv\/profile\/user\/[^\/]*\/?$/.exec(url); },
374
	source: function( re, player )
375
	{
376
		GM_xmlhttpRequest( {
377
			method: "GET",
378
			url: re[0],
379
			onload: function( resp )
380
			{
381
				var r = /(U\:[01]\:\d+)/.exec( resp.responseText );
382
				player.initialize( CSteamID.parse( r?r[1]:0 ) );
383
			},
384
			onerror: function( resp )
385
			{
386
				player.error( resp.responseText );
387
			}
388
		} );
389
	},
390
	query: function( sid, player, el )
391
	{
392
		el.textContent = 'teamfortress.tv';
393
		
394
		// Cannot query by steamid...
395
		searchEngine( "teamfortress.tv", "Profile", sid.render(), function(r) {
396
			try {
397
				var url = r[1];
398
				var name = /profile\/user\/(.*?)\/?$/.exec(url)[1];
399
				siteSetLink( el, url, "TeamFortress.tv ("+name+")" );
400
			} catch(e) {
401
				siteSetMissing( el );
402
			}
403
		} );
404
	}
405
},
406
// logs.tf
407
"logs.tf": {
408
	group: "stats",
409
	match: function( url ) { return /^https?\:\/\/(?:www.)?logs\.tf\/profile\/(\d+)\/?$/.exec(url); },
410
	source: function( re, player ) { player.initialize( CSteamID.parse( re[1] ) ); },
411
	query: function( sid, player, el )
412
	{
413
		el.textContent = 'logs.tf';
414
		
415
		GM_xmlhttpRequest( {
416
			method: "GET",
417
			url: "http://logs.tf/profile/"+sid.toString(),
418
			onload: function( resp )
419
			{
420
				if ( !(/<h5>None found\.<\/h5>/.test(resp.responseText)) )
421
					siteSetLink( el, "http://logs.tf/profile/"+sid.toString(), "Logs.tf Profile" );
422
				else
423
					siteSetMissing( el );
424
			}
425
		} );
426
	}
427
},
428
// SizzlingStats.com (FIXME! Figure out their query api)
429
"sizzlingstats.com": {
430
	group: "stats",
431
	match: function( url ) { return /^https?\:\/\/(?:www.)?sizzlingstats\.com\/player\/(\d+)\/?$/.exec(url); },
432
	source: function( re, player ) { player.initialize( CSteamID.parse( re[1] ) ); },
433
	query: function( sid, player ) { return false; }
434
},
435
// TF2Logs.com (does anyone still use this?)
436
"tf2logs.com": {
437
	group: "stats",
438
	match: function( url ) { return /^https?\:\/\/(?:www.)?tf2logs\.com\/players\/(\d+)\/?$/.exec(url); },
439
	source: function( re, player ) { player.initialize( CSteamID.parse( re[1] ) ); },
440
	query: function( sid, player, el )
441
	{
442
		el.textContent = 'tf2logs.com';
443
		
444
		GM_xmlhttpRequest( {
445
			method: "GET",
446
			url: "http://tf2logs.com/players/"+sid.toString(),
447
			onload: function( resp )
448
			{
449
				var re = /<meta name="title" content="([^>]*?) - TF2Logs.com" \/>/.exec(resp.responseText);
450
				if ( re && re[1]!='Welcome' )
451
					siteSetLink( el, "http://tf2logs.com/players/"+sid.toString(), "TF2Logs.com Profile" );
452
				else
453
					siteSetMissing( el );
454
			}
455
		} );
456
	}
457
},
458
};
459
460
//----------------------------------------------------------------
461
// A link resource
462
//----------------------------------------------------------------
463
function linkPlayer( a )
464
{
465
	this.anchor = a;
466
	
467
	// Generate html
468
	var div = document.createElement('div');
469
	this.div = div;
470
	div.classList.add( 'TFProfile' );
471
	div.innerHTML = '<p>Pending...</p>';
472
}
473
// Initialize from a steamid
474
linkPlayer.prototype.initialize = function( sid )
475
{
476
	if ( sid )
477
	{
478
		// Show steam id
479
		var span = this.div.querySelector("p");
480
		span.innerHTML = '';
481
		span.appendChild( document.createTextNode( sid.render() ) );
482
		// Collect information about other websites
483
		for ( var it in sites )
484
		{
485
			var site = sites[it];
486
			
487
			// Find the group this belongs in
488
			var group = this.div.querySelector("article.TFProfile_"+site.group);
489
			if ( !group )
490
			{
491
				group = document.createElement('article');
492
				group.className = "TFProfile_"+site.group;
493
				this.div.appendChild( group );
494
			}
495
			
496
			var p = document.createElement('p');
497
			p.className = 'TFProfile_Pending';
498
			
499
			if ( site.query( sid, this, p )!==false )
500
			{
501
				group.appendChild( p );
502
			}
503
		}
504
	}
505
	else
506
	{
507
		this.error( "Invalid SteamID!" );
508
	}
509
}
510
// Error happened
511
linkPlayer.prototype.error = function( desc )
512
{
513
	var p = this.div.querySelector("p");
514
	p.innerHTML = '';
515
	p.appendChild( document.createTextNode( 'Error! ' + desc ) );
516
}
517
// Delay the query on mouse over
518
linkPlayer.prototype.source = function( a, re, site )
519
{
520
	this.show( a, function() {
521
		if ( !this.sourced )
522
		{
523
			this.sourced = true;
524
			site.source( re, this );
525
		}
526
	} );
527
}
528
// Show the UI, delay loading as needed
529
linkPlayer.prototype.show = function( a, fn )
530
{
531
	var self = this;
532
	function hover()
533
	{
534
		// Begin sourcing
535
		fn.call( self );
536
		// Show our overlay only
537
		Array.prototype.forEach.call( document.querySelectorAll(".TFProfile"), function(div) { div.style.display="none"; } );
538
		clear();
539
		// Compute position of the tooltip
540
		var r = a.getBoundingClientRect();
541
		var bottom = r.bottom + ( document.documentElement.scrollTop || document.body.scrollTop );
542
		var left = r.left + ( document.documentElement.scrollLeft || document.body.scrollLeft );
543
		self.div.style.top = bottom + "px";
544
		self.div.style.left = left + "px";
545
		// Show it
546
		self.div.style.display = "block";
547
		document.body.appendChild( self.div );
548
	}
549
	function clear()
550
	{
551
		if ( self.timer )
552
		{
553
			window.clearTimeout( self.timer );
554
			self.timer = false;
555
		}
556
	}
557
	function timer( fn, ms )
558
	{
559
		clear();
560
		self.timer = window.setTimeout( fn, ms );
561
	}
562
	function leave()
563
	{
564
		self.div.style.display = "none";
565
		clear();
566
	}
567
	function related( parent, child )
568
	{
569
		return !( !child || ( child!==parent && !parent.contains( child ) ) );
570
	}
571
	a.addEventListener( 'mouseover', function(e) { timer( hover, 500 ); }, false );
572
	a.addEventListener( 'mouseout', function(e) { if ( !related(this,e.relatedTarget) ) timer( leave, 500 ); }, false );
573
	this.div.addEventListener( 'mouseover', clear, false );
574
	this.div.addEventListener( 'mouseout', function(e) { if ( !related(this,e.relatedTarget ) ) timer( leave, 200 ); }, false );
575
576
	// Work around for mouseleave not working for chrome...
577
	var img = document.createElement('img');
578
	img.alt = "x";
579
	//img.src = "data:image/gif;base64,R0lGODlhDwAPANUAAAAAAP////7+/v39/fz8/Pv7+/j4+Pf39/X19fHx8e/v7+7u7u3t7evr6+rq6ufn5+bm5uXl5eLi4uDg4N/f39zc3Nra2tnZ2dTU1NPT09LS0tDQ0M/Pz87Ozs3NzbOzs39/f3V1dVdXVz4+PiMjI////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACUALAAAAAAPAA8AAAaawJKwpJhgPJiJYjg0SCqNg+DQqEgMzc2DMOh2CY8NtiSBFM5oNERCtBwMAIBhHjccLMUIAsEBkBAkABx7EUYNCYgfACMAH4gJDUcMC5QLcQCVCwxIEQ6eIQCgIZ4OEUkVEBAgACIQIgAgqRVKGhQUcba3ALYaSxIZF8HCwhlsJQYeGxjLzBgbHmPHEh4dHNYdHldMQkVHSUtDQQA7";
580
	img.addEventListener( 'click', leave, false );
581
	this.div.insertBefore( img, this.div.firstChild );
582
}
583
584
//----------------------------------------------------------------
585
// Apply to all links in the document
586
//----------------------------------------------------------------
587
// FIXME! Does not work on content loaded after page load!
588
Array.prototype.forEach.call( document.querySelectorAll("a"), function(link)
589
{
590
	var url = link.href;
591
	for ( var it in sites )
592
	{
593
		var site = sites[it];
594
		var re = site.match && site.match( url );
595
		if ( re )
596
		{
597
			(new linkPlayer( link )).source( link, re, site );
598
		}
599
	}
600
} );
601
602
// Make it all look pretty
603
addStyle('\
604
div.TFProfile { \
605
position:absolute !important; \
606
z-index:9999999 !important; \
607
background-color:#F8F8FF !important; \
608
border: solid 1px #C0C0C0 !important; \
609
min-width:200px !important; \
610
padding:5px 10px; \
611
-webkit-box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.3); \
612
box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.3); } \
613
\
614
div.TFProfile img { \
615
float:right !important;\
616
margin:-5px 0px 0px 0px !important;\
617
background-color: #cbcbcb;\
618
display: block;\
619
height: 18px;\
620
width: 36px;\
621
font-family: Verdana, Arial, Helvetica, sans-serif;\
622
font-size: 11px;\
623
font-weight: bold;color: #fff;\
624
text-decoration: none;\
625
text-align:center;\
626
cursor: pointer;\
627
line-height: 16px;\
628
border: none;\
629
-webkit-transition: background 100ms ease-in-out;\
630
-moz-transition: background 100ms ease-in-out;\
631
-ms-transition: background 100ms ease-in-out;\
632
-o-transition: background 100ms ease-in-out;\
633
transition: background 100ms ease-in-out; } \
634
\
635
div.TFProfile img:hover { \
636
background-color: #de5044;\
637
} \
638
\
639
div.TFProfile p { \
640
letter-spacing:0px !important; \
641
text-align:left !important; \
642
color:#555!important; \
643
padding:0!important; \
644
margin:0px!important; \
645
display: block !important; \
646
border: none !important; \
647
font-family: Verdana, Arial, Helvetica, sans-serif; \
648
font-size: 10px; \
649
font-style: normal; \
650
line-height: normal; \
651
font-weight: normal; \
652
font-variant: normal; \
653
color: #4c4c4c; \
654
text-decoration: none; } \
655
\
656
div.TFProfile p.TFProfile_Done>a { \
657
font-family:Verdana, Arial, Helvetica, sans-serif; \
658
font-size:9px; \
659
font-style:normal; \
660
line-height:normal; \
661
font-weight:700; \
662
font-variant:normal; \
663
text-decoration:none; \
664
letter-spacing:0 !important; \
665
text-align:left !important; \
666
color:#f8f8f8; \
667
border:1px solid #679bf3; \
668
background-color:#77a7f9; \
669
width:auto; \
670
height:auto; \
671
display:block!important; \
672
margin:2px 0px!important; \
673
padding:5px!important } \
674
\
675
div.TFProfile p.TFProfile_Done>a:hover { \
676
color:#fff; \
677
border:1px solid #4585f3; \
678
-webkit-box-shadow:1px 1px 2px 0 rgba(0,0,0,0.1);\
679
box-shadow:1px 1px 2px 0 rgba(0,0,0,0.1);\
680
background: #77a7f9;\
681
background: -moz-linear-gradient(top, #77a7f9 0%, #699cf2 100%);\
682
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#77a7f9), color-stop(100%,#699cf2));\
683
background: -webkit-linear-gradient(top, #77a7f9 0%,#699cf2 100%);\
684
background: -o-linear-gradient(top, #77a7f9 0%,#699cf2 100%);\
685
background: -ms-linear-gradient(top, #77a7f9 0%,#699cf2 100%);\
686
background: linear-gradient(to bottom, #77a7f9 0%,#699cf2 100%);\
687
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr="#77a7f9", endColorstr="#699cf2",GradientType=0 );} \
688
\
689
div.TFProfile p.TFProfile_Pending { \
690
font-family:Verdana, Arial, Helvetica, sans-serif !important; \
691
font-size:9px !important; \
692
font-style:normal !important; \
693
line-height:normal !important; \
694
font-weight:700 !important; \
695
font-variant:normal !important; \
696
text-decoration:none !important; \
697
letter-spacing:0 !important; \
698
text-align:left !important; \
699
color:#b1b1b1 !important; \
700
border:1px solid #cbcbcb !important; \
701
background-color:#e5e5e5 !important; \
702
width:auto !important; \
703
height:auto !important; \
704
display:block !important; \
705
margin:2px 0px !important;\
706
padding:5px !important } \
707
\
708
div.TFProfile p.TFProfile_Missing { \
709
display:none!important; \
710
} \
711
div.TFProfile article { \
712
	border-bottom: 1px dotted #C0C0C0; \
713
	padding: 5px 0px; \
714
} \
715
div.TFProfile article:last-child { \
716
	border-bottom: none; \
717
} \
718
');